@north-light/crouter 0.3.332 → 0.3.333
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/README.md +2 -2
- package/dist/api/command-manifest/manifest.js +1 -1
- package/dist/build-root.js +1 -1
- package/dist/builtin-memory/crouter-concepts/INDEX.md +13 -0
- package/dist/builtin-memory/crouter-concepts/README.md +19 -0
- package/dist/builtin-memory/crouter-concepts/lifecycle-and-wakes.md +28 -0
- package/dist/builtin-memory/crouter-concepts/memory.md +36 -0
- package/dist/builtin-memory/crouter-concepts/nodes-and-the-canvas.md +30 -0
- package/dist/builtin-memory/crouter-concepts/profiles-kinds-and-modes.md +33 -0
- package/dist/builtin-memory/crouter-concepts/scopes-and-trust.md +34 -0
- package/dist/builtin-memory/crouter-concepts/why-a-daemon.md +31 -0
- package/dist/builtin-memory/crouter-plugin/README.md +7 -7
- package/dist/builtin-memory/crouter-plugin/commands.md +1 -1
- package/dist/builtin-memory/crouter-plugin/deploying.md +1 -1
- package/dist/builtin-memory/crouter-plugin/getting-started.md +1 -1
- package/dist/clients/attach/input/capabilities.js +1 -1
- package/dist/clients/attach/overlays/help.js +1 -1
- package/dist/clients/attach/slash/dispatch.js +1 -1
- package/dist/clients/attach/viewer.js +413 -413
- package/dist/commands/sys/branch.js +1 -1
- package/dist/commands/sys/setup-core.d.ts +1 -1
- package/dist/commands/sys/setup-core.js +4 -4
- package/dist/commands/sys/tutorial/branch.d.ts +1 -0
- package/dist/commands/sys/tutorial/branch.js +1 -0
- package/dist/commands/sys/tutorial/lessons.d.ts +6 -0
- package/dist/commands/sys/tutorial/lessons.js +12 -0
- package/dist/commands/sys/tutorial/scenario.d.ts +1 -0
- package/dist/commands/sys/tutorial/scenario.js +2 -0
- package/dist/commands/sys/tutorial/tracks.d.ts +2 -0
- package/dist/commands/sys/tutorial/tracks.js +4 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/runtime/boot-root.d.ts +7 -0
- package/dist/core/runtime/boot-root.js +4 -4
- package/dist/core/runtime/first-run-offer.d.ts +11 -0
- package/dist/core/runtime/first-run-offer.js +5 -0
- package/dist/core/runtime/front-door.js +3 -3
- package/dist/types.js +1 -1
- package/docs/cli/README.md +16 -0
- package/docs/cli/canvas-and-dashboard.md +56 -0
- package/docs/cli/first-session.md +55 -0
- package/docs/cli/human-inbox.md +34 -0
- package/docs/cli/memory-and-preferences.md +50 -0
- package/docs/cli/the-viewer.md +79 -0
- package/docs/concepts/README.md +17 -0
- package/docs/concepts/lifecycle-and-wakes.md +26 -0
- package/docs/concepts/memory.md +34 -0
- package/docs/concepts/nodes-and-the-canvas.md +28 -0
- package/docs/concepts/profiles-kinds-and-modes.md +31 -0
- package/docs/concepts/scopes-and-trust.md +32 -0
- package/docs/concepts/why-a-daemon.md +29 -0
- package/package.json +8 -8
- package/runtime.lock.json +196 -196
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ crtr node new "Inspect this repository and report the failing tests."
|
|
|
18
18
|
|
|
19
19
|
## Use crouter from an application
|
|
20
20
|
|
|
21
|
-
Install [`@north-light/crouter-sdk`](https://www.npmjs.com/package/@north-light/crouter-sdk) to create agent runs, receive typed results, stream activity, and use the daemon's other application-facing APIs. It connects to the local daemon or to a remote daemon with a bearer token. The [SDK guide](https://
|
|
21
|
+
Install [`@north-light/crouter-sdk`](https://www.npmjs.com/package/@north-light/crouter-sdk) to create agent runs, receive typed results, stream activity, and use the daemon's other application-facing APIs. It connects to the local daemon or to a remote daemon with a bearer token. The [SDK guide](https://crouter-docs.vercel.app/docs/sdk) has working examples and the full API reference.
|
|
22
22
|
|
|
23
23
|
## Extend crouter
|
|
24
24
|
|
|
25
|
-
- Build an HTTP plugin with [`@north-light/crouter-plugin`](https://www.npmjs.com/package/@north-light/crouter-plugin). Its [authoring guide](https://
|
|
25
|
+
- Build an HTTP plugin with [`@north-light/crouter-plugin`](https://www.npmjs.com/package/@north-light/crouter-plugin). Its [authoring guide](https://crouter-docs.vercel.app/docs/plugin) covers commands, parameters, output, errors, deployment, and plugin memory documents.
|
|
26
26
|
- Install plugins from the [crouter official marketplace](https://github.com/crouton-labs/crouter-official-marketplace). `crtr sys setup` offers to install and register it; then use `crtr pkg market browse crouter-official-marketplace` to see its plugins.
|
|
27
27
|
|
|
28
28
|
## Source and contributing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var j=Object.defineProperty;var h=(e,n)=>j(e,"name",{value:n,configurable:!0});import{validateCommandNode as U}from"./schema.js";import{isRecord as y}from"../../shared/predicates.js";const g=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,E=new Set(["GET","POST","PUT","PATCH","DELETE"]);function b(e){return e===null?"null":Array.isArray(e)?"array":typeof e}h(b,"typeName");function L(e,n){const
|
|
1
|
+
var j=Object.defineProperty;var h=(e,n)=>j(e,"name",{value:n,configurable:!0});import{validateCommandNode as U}from"./schema.js";import{isRecord as y}from"../../shared/predicates.js";const g=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,E=new Set(["GET","POST","PUT","PATCH","DELETE"]);function b(e){return e===null?"null":Array.isArray(e)?"array":typeof e}h(b,"typeName");function L(e,n){const t=[],a=h((o,v,k,A,x,_)=>{t.push({code:o,message:v,received:k,expected:A,next:x,..._?{path:_}:{}})},"issue");if(!y(e))return a("command_manifest_invalid","manifest must be an object",b(e),n.extensionFragment===!0?"{ schemaVersion, transport, mounts }":n.transport==="http"?"{ schemaVersion, baseUrl?, timeouts?, mounts, helpAddenda? }":"{ schemaVersion, mounts, helpAddenda? }","Provide a valid JSON manifest object."),{issues:t};const s=Object.keys(e),r=n.extensionFragment===!0?new Set(["schemaVersion","transport","mounts"]):n.transport==="http"?new Set(["schemaVersion","baseUrl","timeouts","mounts","helpAddenda"]):new Set(["schemaVersion","mounts","helpAddenda"]),l=s.filter(o=>!r.has(o));if(l.length>0)return a("command_manifest_invalid","unknown top-level keys",l.join(", "),n.extensionFragment===!0?"only: schemaVersion, transport, mounts":n.transport==="http"?"only: schemaVersion, baseUrl, timeouts, mounts, helpAddenda":"only: schemaVersion, mounts, helpAddenda","Remove the unknown keys."),{issues:t};if(e.schemaVersion!==1)return a("command_schema_version","schemaVersion must be exactly 1",String(e.schemaVersion),"1","Update the manifest schema version to 1.","schemaVersion"),{issues:t};let d;if(n.transport==="http"&&e.baseUrl!==void 0){if(typeof e.baseUrl!="string")return a("command_manifest_invalid","baseUrl must be a string",b(e.baseUrl),"an absolute HTTP(S) URL or omitted","Provide a valid baseUrl or remove it.","baseUrl"),{issues:t};try{const o=new URL(e.baseUrl);if(!["http:","https:"].includes(o.protocol))return a("command_manifest_invalid","baseUrl must be http: or https:",o.protocol,"http: or https:","Use an HTTP(S) URL.","baseUrl"),{issues:t};d=e.baseUrl}catch{return a("command_manifest_invalid","baseUrl must be a valid absolute URL",e.baseUrl,"a valid URL","Fix the baseUrl.","baseUrl"),{issues:t}}}let m;if(n.transport==="http"&&e.timeouts!==void 0){const o=$(e.timeouts,a);if(o===null)return{issues:t};m=o}let i;if(e.helpAddenda!==void 0){const o=M(e.helpAddenda,n.coreCommandPaths,a);if(o===null)return{issues:t};Object.keys(o).length>0&&(i=o)}const c=e.mounts;if(!Array.isArray(c))return a("command_manifest_invalid","mounts must be an array",b(c),"a non-empty array of { parent, node } contributions","Provide at least one mount.","mounts"),{issues:t};if(c.length===0)return a("command_manifest_invalid","mounts must be a non-empty array","empty array","at least one contribution","Add at least one mount.","mounts"),{issues:t};const u=[];for(let o=0;o<c.length;o++){const v=R(c[o],o,n.transport,a,n.extensionFragment===!0?{topLevelRootEntry:"forbidden",allowPassthrough:!1,allowExtensible:!1}:void 0);if(v===null)return{issues:t};u.push(v)}const f=u.filter(o=>o.parent.length>0&&n.extensibleCoreBranches?.has(o.parent.join(" "))===!0);for(const o of f){const v=o.parent.join(" ");if(n.coreCommandPaths?.has(`${v} ${o.node.name}`)===!0)return a("command_collision",`contributed command "${o.node.name}" conflicts with a child the extensible core branch already owns`,o.node.name,`a name not already owned by the ${v} core branch`,"Rename the contributed command or remove this mount.",`${o.parent.join(".")}.${o.node.name}`),{issues:t}}const p=F(u.filter(o=>!f.includes(o)),n.reservedCoreNames,n.extensibleCoreBranches,a);return p===null?{issues:t}:t.length===0?{manifest:{schemaVersion:1,...d!==void 0?{baseUrl:d}:{},...m!==void 0?{timeouts:m}:{},...i!==void 0?{helpAddenda:i}:{},roots:p,coreMounts:f},issues:[]}:{issues:t}}h(L,"validateCommandManifest");function $(e,n){if(!y(e))return n("command_manifest_invalid","timeouts must be an object",b(e),"{ connectMs?, requestMs?, streamIdleMs? }","Fix timeouts.","timeouts"),null;const t=new Set(["connectMs","requestMs","streamIdleMs"]),a=Object.keys(e).filter(r=>!t.has(r));if(a.length>0)return n("command_manifest_invalid","unknown timeouts keys",a.join(", "),"only: connectMs, requestMs, streamIdleMs","Remove the unknown keys.","timeouts"),null;const s={};for(const r of["connectMs","requestMs","streamIdleMs"])if(e[r]!==void 0){if(typeof e[r]!="number"||!Number.isInteger(e[r])||e[r]<=0)return n("command_manifest_invalid",`${r} must be a positive integer`,String(e[r]),"a positive integer",`Fix ${r}.`,`timeouts.${r}`),null;s[r]=e[r]}return s}h($,"validateTimeouts");function M(e,n,t){if(!y(e))return t("command_manifest_invalid","helpAddenda must be an object",b(e),"a map of core command path \u2192 addendum text","Fix helpAddenda.","helpAddenda"),null;const a={};for(const[s,r]of Object.entries(e)){if(s.split(" ").some(l=>!g.test(l)))return t("command_help_addendum_invalid","helpAddenda key must be a space-separated core command path",s,'kebab tokens separated by single spaces (e.g. "cron" or "cron add")',"Fix the helpAddenda key.",`helpAddenda.${s}`),null;if(typeof r!="string"||r.length===0)return t("command_manifest_invalid","helpAddenda value must be a non-empty string",typeof r=="string"?"empty string":b(r),"non-empty addendum text","Fix the helpAddenda value.",`helpAddenda.${s}`),null;if(n!==void 0&&!n.has(s))return t("command_help_addendum_invalid","helpAddenda key names no crtr core command path",s,'an existing core command path (e.g. "cron", "cron add")',"Fix the key or remove the addendum.",`helpAddenda.${s}`),null;a[s]=r}return a}h(M,"validateHelpAddenda");function R(e,n,t,a,s){const r=`mounts[${n}]`;if(!y(e))return a("command_manifest_invalid","mount must be an object",b(e),"{ parent, node }","Fix the mount.",r),null;const l=new Set(["parent","node"]),d=Object.keys(e).filter(p=>!l.has(p));if(d.length>0)return a("command_manifest_invalid","unknown mount keys",d.join(", "),"only: parent, node","Remove the unknown keys.",r),null;const m=e.parent;if(!Array.isArray(m))return a("command_manifest_invalid","mount parent must be an array",b(m),"an array of kebab tokens, or [] for a top-level mount","Fix parent.",`${r}.parent`),null;for(let p=0;p<m.length;p++){const o=m[p];if(typeof o!="string"||!g.test(o))return a("command_manifest_invalid","parent token must be a kebab token",String(o),"a kebab-case identifier","Fix the parent path.",`${r}.parent[${p}]`),null}const i=m,c=e.node,u=i.length===0,f=U(c,[`${r}.node`],u,t,a,s);return f===null?null:{parent:i,node:f}}h(R,"validateMount");function F(e,n,t,a){const s=e.filter(i=>i.parent.length===0),r=e.filter(i=>i.parent.length>0);for(const i of s)if(n.has(i.node.name))return a("command_collision","node name conflicts with a crtr core command",i.node.name,`a name not in core (not one of: ${[...n].join(", ")})`,"Rename the node or remove this mount.",`${i.node.name}`),null;const l=s.map(i=>i.node.kind==="branch"?{...i.node,children:[...i.node.children]}:{...i.node});for(const i of r)if(n.has(i.parent[0]))return a("command_parent_invalid","parent path starts with a core branch that is not extensible",i.parent[0],`an extensible core branch (one of: ${[...t??[]].join(", ")})`,`Mount below one of the extensible core branches: ${[...t??[]].join(", ")}.`,i.parent.join(".")),null;for(const i of r)if(!S(l,i,a))return null;const d=new Set,m=h((i,c)=>{const u=c.join(".");return d.has(u)?(a("command_node_invalid","duplicate node path",u,"unique paths within the manifest","Remove the duplicate node.",u),!1):(d.add(u),i.kind==="branch"?i.children.every(f=>m(f,[...c,f.name])):!0)},"indexPaths");for(const i of l)if(!m(i,[i.name]))return null;return l}h(F,"materializeForest");function S(e,n,t){const[a,...s]=n.parent,r=e.find(d=>d.name===a);if(!r)return t("command_parent_invalid","parent path starts with unknown branch",a,"a branch this manifest contributes","Fix the parent path or add the parent branch.",n.parent.join(".")),!1;if(r.kind==="leaf")return t("command_parent_invalid","parent path resolves to a leaf",a,"a branch","Change the parent to point to a branch.",n.parent.join(".")),!1;let l=r;for(const d of s){const m=l.children.find(i=>i.name===d);if(!m)return t("command_parent_invalid","parent path contains unknown branch",d,"a branch this manifest contributes","Fix the parent path or add the intermediate branch.",n.parent.join(".")),!1;if(m.kind==="leaf")return t("command_parent_invalid","parent path resolves to a leaf",d,"a branch","Change the parent to point to a branch.",n.parent.join(".")),!1;l=m}return l.passthrough!==void 0?(t("command_parent_invalid","parent path resolves to a passthrough branch",n.parent.join("."),"a branch without passthrough","Remove the nested mount or remove passthrough from its parent branch.",n.parent.join(".")),!1):l.children.some(d=>d.name===n.node.name)?(t("command_node_invalid","duplicate child name",n.node.name,"unique child names within a branch","Rename the node or remove the duplicate.",`${n.parent.join(".")}.${n.node.name}`),!1):(l.children.push(n.node),!0)}h(S,"resolveAndAttachMount");export{L as validateCommandManifest};
|
package/dist/build-root.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var L=Object.defineProperty;var t=(e,n)=>L(e,"name",{value:n,configurable:!0});import{defineBranch as T,defineRoot as l,GLOBAL_TOKENS as _}from"./core/command.js";import{createCoreHookCatalog as b}from"./core/command-hooks/catalog.js";import{composeCoreHooks as C}from"./core/command-hooks/compose.js";import{mark as m}from"./core/timing.js";import{envScopes as j,scopeAllowed as A}from"./shared/env.js";const p="crtr: agentic runtime.",f=[{name:"--json",desc:"stdout as the leaf's declared outputs in raw JSON instead of prose \u2014 for scripts (a cron's bash, external tooling) that must branch on canvas state; prose stays the contract for what you read yourself."}],d=Object.assign(Object.create(null),{memory:t(async()=>(await import("./commands/memory.js")).registerMemory(),"memory"),profile:t(async()=>(await import("./commands/profile.js")).registerProfile(),"profile"),pkg:t(async()=>(await import("./commands/pkg.js")).registerPkg(),"pkg"),human:t(async()=>(await import("./commands/human.js")).registerHuman(),"human"),sys:t(async()=>(await import("./commands/sys.js")).registerSys(),"sys"),node:t(async()=>(await import("./commands/node.js")).registerNode(),"node"),push:t(async()=>(await import("./commands/push.js")).registerPush(),"push"),cron:t(async()=>(await import("./commands/cron.js")).registerCron(),"cron"),canvas:t(async()=>(await import("./commands/canvas.js")).registerCanvas(),"canvas"),surface:t(async()=>(await import("./commands/surface.js")).registerSurface(),"surface")}),g=Object.freeze(Object.keys(d)),H={human:["ask"],cron:["schedule"],memory:["memory:read","memory:write"]};function x(e){const n=j();return n===null?e:e.filter(r=>{const o=H[r.name];return o===void 0||o.some(s=>A(n,s))})}t(x,"allowedSubtrees");let E;function u(){return E??=Promise.all(g.map(e=>d[e]())),E}t(u,"loadCoreSubtrees");let P;function M(){return m("cli.hooks.catalog"),P??=u().then(b),P}t(M,"coreHookCatalog");function v(){let e;return()=>(e??=(async()=>{const{compileHookRegistry:n,effectiveHookPlugins:r}=await import("./core/command-hooks/discovery.js"),o=r();return m("cli.hooks.effective_plugins",{count:o.length}),o.length===0?(m("cli.hooks.empty_registry"),n(b([]),[])):n(await M(),o)})(),e)}t(v,"hookRegistryLoader");function h(e){const n=new Set,r=t((o,s)=>{if(!(o.kind==="branch"&&o.passthrough!==void 0)&&(n.add(s),o.kind==="branch"))for(const i of o.children)r(i,`${s} ${i.name}`)},"visit");for(const o of e)r(o,o.name);return n}t(h,"commandPathsFor");async function J(){return h(await u())}t(J,"coreCommandPaths");function N(e){const n=[],r=t((o,s)=>{o.extensible===!0&&n.push(s);for(const i of o.children)i.kind==="branch"&&r(i,[...s,i.name])},"visit");for(const o of e)r(o,[o.name]);return n}t(N,"extensibleBranchPaths");async function B(){return new Set(N(await u()).map(e=>e.join(" ")))}t(B,"extensibleCoreBranches");function U(e,n){const{listing:r,...o}=e.help;return T({name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:o,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:n})}t(U,"rebuildBranch");async function O(e,n){if(n.size===0)return e;const[{buildExternalCommandSnapshot:r},{composeCoreBranchMounts:o}]=await Promise.all([import("./core/command-plugins/discovery.js"),import("./core/command-plugins/compose.js")]),s=r(new Set(g),void 0,void 0,h(await u()),n),i=t((a,y)=>{let w=!1;const R=a.children.map(c=>{if(c.kind!=="branch")return c;const k=i(c,[...y,c.name]);return k!==c&&(w=!0),k}),S=a.extensible===!0?o(s.coreMounts,y):[];return w||S.length>0?U(a,[...R,...S]):a},"composeBranch");return e.map(a=>i(a,[a.name]))}t(O,"composeCorePluginMounts");function K(e){const n=e.slice(2).filter(r=>!_.has(r));return n[0]==="sys"&&n[1]==="daemon"}t(K,"isDaemonControlInvocation");async function Q(){const{registerDaemonControlSys:e}=await import("./commands/sys/daemon.js");return l({tagline:p,globals:f,subtrees:[e()]})}t(Q,"resolveDaemonControlRoot");async function V(e){const n=e!==void 0?d[e]:void 0;if(n!==void 0){const r=x(await O([await n()],await B()));if(r.length>0)return l({tagline:p,globals:f,subtrees:C(r,v())})}return D()}t(V,"resolveRoot");async function D(){const e=await u(),[{buildExternalCommandSnapshot:n},{composeExternalSubtrees:r}]=await Promise.all([import("./core/command-plugins/discovery.js"),import("./core/command-plugins/compose.js")]),o=await B(),s=await O(e,o),i=n(new Set(g),void 0,void 0,h(e),o),a=r(i);return l({tagline:p,globals:f,subtrees:[...C(x(s),v()),...a]})}t(D,"buildRoot");export{g as SUBTREE_NAMES,D as buildRoot,J as coreCommandPaths,M as coreHookCatalog,B as extensibleCoreBranches,K as isDaemonControlInvocation,Q as resolveDaemonControlRoot,V as resolveRoot};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
description: Why crouter is shaped the way it is — durable nodes, the canvas graph, dormancy, memory tiers, profiles, scoped authority, and one daemon
|
|
4
|
+
when-and-why-to-read: When you are choosing the shape of an agent application on crtr — whether work needs a durable node, whether it should finish or stay resident, where memory belongs, or why only the daemon writes canvas state — this section should be read because picking an SDK method or plugin field before understanding the runtime model produces an application that fights the runtime instead of using it.
|
|
5
|
+
short-form: The concepts behind the crtr runtime, read before choosing an SDK method or plugin field — nodes and the canvas, lifecycle and wakes, memory, profiles/kinds/modes, scopes and trust, and why a daemon.
|
|
6
|
+
surfaces:
|
|
7
|
+
- on: boot
|
|
8
|
+
at: name
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# `crouter-concepts/` — choose the crouter runtime shape before using the SDK
|
|
12
|
+
|
|
13
|
+
Read `crouter-concepts/README` for the section overview. These pages explain why crouter uses durable nodes, the canvas graph, free dormancy, memory tiers, profiles, scoped authority, and one daemon before an application chooses an SDK method or plugin field.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When building with crtr, read this section because its
|
|
4
|
+
pages explain which runtime parts solve each problem before you choose an SDK
|
|
5
|
+
method or plugin field.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Concepts
|
|
9
|
+
|
|
10
|
+
Use these pages to choose the shape of an agent application before you start wiring it. They explain the durable runtime model behind the SDK and plugin surfaces, then point to the detailed guides for each surface.
|
|
11
|
+
|
|
12
|
+
| Page | Decide |
|
|
13
|
+
|---|---|
|
|
14
|
+
| [[crouter-concepts/nodes-and-the-canvas]] | Whether work needs a durable node rather than one request and response |
|
|
15
|
+
| [[crouter-concepts/lifecycle-and-wakes]] | Whether a node should finish, remain resident, delegate, or wait |
|
|
16
|
+
| [[crouter-concepts/memory]] | What an agent should know or embody, who owns it, and where it belongs |
|
|
17
|
+
| [[crouter-concepts/profiles-kinds-and-modes]] | Which run-shaping dial matches an application concern |
|
|
18
|
+
| [[crouter-concepts/scopes-and-trust]] | What an application or node may do and why only the daemon writes canvas state |
|
|
19
|
+
| [[crouter-concepts/why-a-daemon]] | Why a broker survives a terminal and why viewers are clients rather than hosts |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When an agent must react to later work, read this because
|
|
4
|
+
lifecycle and wake choices let it sleep without a process while preserving its
|
|
5
|
+
goal and the event that should resume it.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Lifecycle and wakes
|
|
9
|
+
|
|
10
|
+
Choose a node’s lifecycle from the kind of relationship it has with work. A **terminal** node owes a final report when it is done. A **resident** node is for an ongoing conversation with a person: it can become dormant and be messaged again without first finalizing. Resident does not mean “keep a process running.” Dormant nodes have no live broker or model turn and cost no context window or compute.
|
|
11
|
+
|
|
12
|
+
Mode answers a different question. A **base** node works hands-on and may delegate a clearly separate piece. An **orchestrator** owns enough independent parallel work that decomposition, delegation, and integration are now its main job. Long or sequential work is not enough reason to orchestrate; keep one hands-on owner and give it a fresh context when needed. Lifecycle answers whether a conversation remains open; mode answers who does the work.
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart LR
|
|
16
|
+
Active[Active node] -->|nothing to do now| Dormant[Dormant: no broker]
|
|
17
|
+
Child[Child report] --> Inbox
|
|
18
|
+
Message[Message or human answer] --> Inbox
|
|
19
|
+
Cron[Cron or deadline] --> Inbox
|
|
20
|
+
Inbox -->|wake| Active
|
|
21
|
+
Active -->|terminal final| Finished[Finished]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A node wakes when work actually arrives: a subscribed child pushes a report, another node or an application sends a message, a person answers a human request, or a cron action delivers work. A deadline is a scheduled wake that races an otherwise unpushable wait. The inbox is the durable path for these triggers, so a node can stop between them without losing its goal.
|
|
25
|
+
|
|
26
|
+
Do not keep a node active to poll a child, a person, or a message. Creation automatically subscribes a parent to its child, and the runtime delivers the child’s outcome. Waiting for something the canvas can push is free: end the turn and let the node become dormant. Schedule a cron only for recurring work or an external condition that nothing can push into the canvas, such as checking a CI run. A timer added “just in case” a child does not report duplicates a runtime guarantee and hides a runtime defect.
|
|
27
|
+
|
|
28
|
+
A broker crash does not erase the node. The daemon retains the durable node row, conversation, waits, and outstanding inbox entries, then applies its recovery policy. This is what makes a resident event-driven assistant practical: it can wait for a webhook, be dormant for hours, and resume its saved work only when the webhook produces a message. Use [[crouter-sdk/nodes]] to deliver that external event, and run `crtr memory read internal/nodes-and-canvas` for lifecycle operations.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When shaping an agent without rewriting its prompt, read
|
|
4
|
+
this because memory documents separate facts to consult from behavior to
|
|
5
|
+
embody and let each owner control who receives them.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Memory
|
|
9
|
+
|
|
10
|
+
Memory shapes a node without adding the same instructions to every prompt. A memory document says what it contains, who owns it, and when it should enter context. The result is durable guidance that can be discovered when relevant instead of a growing startup prompt.
|
|
11
|
+
|
|
12
|
+
There are two kinds. **Knowledge** is something an agent consults: a procedure, fact, or technical reference. A **preference** is behavior the agent should embody: a standing directive or correction. This is a use-based split. A procedure and a fact are both knowledge because the agent reads either one to answer a question; a preference changes how it acts.
|
|
13
|
+
|
|
14
|
+
| Tier | Owner | Put here |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Node | One running node | A note needed across that node’s fresh contexts |
|
|
17
|
+
| Project | One repository or workspace | Repository facts and procedures |
|
|
18
|
+
| Profile | One application identity and its purview | Application-wide conventions and knowledge |
|
|
19
|
+
| User | One person | Facts and preferences that follow them everywhere |
|
|
20
|
+
| Builtin | crouter | Runtime documentation that ships to every user |
|
|
21
|
+
|
|
22
|
+
Choose the narrowest tier that reaches the next agent who needs the document. For an application author, the profile store is the usual home for knowledge shared by that application’s nodes across repositories. [[crouter-sdk/memory]] names a target for every operation, so the daemon resolves the application’s profile rather than its own current directory. A node’s `memory:read` and `memory:write` scopes can respectively permit reading while denying changes.
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart LR
|
|
26
|
+
Doc[Memory document] --> Surface[Surface entry: event, gate, rung]
|
|
27
|
+
Surface -->|event matches| Preview[Name, preview, or full body]
|
|
28
|
+
Preview --> Route[Routing line tells the agent why to read]
|
|
29
|
+
Route --> Read[Explicit full read when needed]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
A surface entry is the delivery mechanism. A document is not loaded because it sits in a particular folder or because its routing line resembles the task. Its frontmatter names an event such as boot, workspace-open, file read, memory read, command, or pre-command; that entry can also match a path or gate on the node’s shape and selects a rung: its name, a preview, or its full content. A document with no surface entry stays in its directory listing until an agent deliberately finds or reads it.
|
|
33
|
+
|
|
34
|
+
The routing line is the preview at the middle rung, not a trigger. For example, a profile document can have a file-read surface that delivers a preview when an order record is opened. Its line — “When handling a refund request, read this because the eligibility window is not in the order record” — then tells the agent why an explicit full read is useful. The event delivers the preview; the line helps the agent decide whether to read the body without pretending to replace it.
|
|
35
|
+
|
|
36
|
+
Memory therefore supports progressive disclosure. You can record knowledge freely, but it costs future contexts only when an explicit surface route delivers it. Read `crtr memory write -h` to author a document and `crtr memory read internal/memory-loading` for the routing mechanics. The SDK [[crouter-sdk/memory]] shows how an application creates and revises its profile documents.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When deciding whether an agent task should outlive one
|
|
4
|
+
request, read this because a node gives the task durable identity, context,
|
|
5
|
+
reports, and a path for later messages.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Nodes and the canvas
|
|
9
|
+
|
|
10
|
+
Use a node when the work may need a follow-up, a report, a child, or time without a caller holding a request open. A node is a durable unit of agent work: it has an identity, a goal, graph relationships, a context directory for artifacts, and a broker that hosts its agent engine. It is not an HTTP request with an LLM response attached.
|
|
11
|
+
|
|
12
|
+
The canvas is the durable graph of those nodes. It makes an agent's work and its relationship to other work visible after the process that created it has returned. This is why [[crouter-sdk/nodes]] returns a node you can retrieve, message, stream, or wait on instead of only returning generated text. An application can create a root node, show its progress, and later call `nodes.message` when a person or an external event has more work for it.
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TD
|
|
16
|
+
App[Application] -->|creates or messages| Daemon[crtrd]
|
|
17
|
+
Daemon --> Node[Durable node]
|
|
18
|
+
Node --> Broker[Broker and agent engine]
|
|
19
|
+
Node --> Context[Context directory and artifacts]
|
|
20
|
+
Child[Child node] -->|pushes a report| Node
|
|
21
|
+
Node -->|pushes a report| Parent[Subscriber]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A graph edge is not just a visual parent-child line. The management relationship records who owns a child, while subscriptions carry report delivery. On normal child creation, the parent subscribes to the child, so the child’s final report wakes the parent. A node can have other subscribers too; report delivery is intentionally separate from hierarchy.
|
|
25
|
+
|
|
26
|
+
The one way work reports upward is a **push**. A push writes a durable report and puts a reference in each subscriber’s feed. Nothing is reported merely because a node stopped producing text. That makes a report an explicit claim a subscriber can inspect, rather than an inference from terminal output. The parent can then integrate the child’s result instead of repeating the work.
|
|
27
|
+
|
|
28
|
+
A node owns an outcome, not merely an artifact. It may write files and reports while working, but its terminal result is credible only when it has evidence that the requested goal was met. The canvas supports that responsibility: it preserves the goal, durable artifacts, reports, and relationships across fresh contexts and broker replacement.
|
|
29
|
+
|
|
30
|
+
Use a one-shot SDK call such as [[crouter-sdk/nodes]] when work is bounded and its only useful output is a typed result. Use `nodes.create` when the application needs a continuing conversation or must observe work while it runs. For the operational graph and report model, run `crtr memory read internal/nodes-and-canvas`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When designing an agent run, read this because profiles,
|
|
4
|
+
kinds, modes, and memory tiers solve different problems and prevent a long
|
|
5
|
+
prompt from becoming an unstable substitute for an application identity.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Profiles, kinds, and modes
|
|
9
|
+
|
|
10
|
+
Shape a run by choosing the dial that owns the decision. The four dials are independent: a useful profile does not imply an orchestrator, and a specialist kind does not decide where its knowledge lives.
|
|
11
|
+
|
|
12
|
+
| Dial | It answers | Use it when |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Profile | Which application identity, project purview, environment, and profile memory apply? | An application or body of work has stable directories and conventions |
|
|
15
|
+
| Kind | What standing role, model tier, tools, and expertise should the agent have? | The work matches a recurring role such as developer or reviewer |
|
|
16
|
+
| Mode | Does this node work hands-on or coordinate independent children? | Parallel work makes coordination the main job |
|
|
17
|
+
| Memory tier | Who should receive a document? | Guidance must reach one node, project, profile, user, or all crouter users |
|
|
18
|
+
|
|
19
|
+
A profile is not a label on a run. It is a stable agent identity with its own memory store and a purview of project directories. It lets an application create nodes from the same target context even if the caller runs elsewhere. Create a profile when an app has a durable set of directories, environment values, and conventions worth sharing. Do not make a profile for every repository or individual request. Use [[crouter-sdk/resources]] and the SDK’s `profile` create field to select the application’s identity.
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart LR
|
|
23
|
+
Profile[Profile: purview, environment, memory] --> Node
|
|
24
|
+
Kind[Kind: role and tools] --> Node
|
|
25
|
+
Mode[Mode: base or orchestrator] --> Node
|
|
26
|
+
Tier[Memory tier: reach] --> Node
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
A kind is a recurring role, not a decorative name. It carries a role-specific posture and may choose a suitable model tier and tools. A custom kind beats a long prompt when the role recurs and needs standing discipline that should survive every run: for example, an application’s compliance reviewer that always needs the same tools, expertise, and model choice. A one-off instruction belongs in the node’s prompt, where it does not create a permanent persona to maintain.
|
|
30
|
+
|
|
31
|
+
Base mode is the normal choice: the node owns and performs the work, using a child only for a separable part. Promote to orchestrator only when independent parts can proceed in parallel and the benefits outweigh coordination and integration. A terminal orchestrator still finishes normally; residency is separate and belongs to a person-facing ongoing conversation.
|
|
32
|
+
|
|
33
|
+
These choices keep the application prompt focused. Identity belongs in a profile, standing role in a kind, task-specific intent in the prompt, coordination in mode, and reusable knowledge in the narrowest memory tier. For the full selection rules, run `crtr memory read internal/agent-shaping`; [[crouter-sdk/nodes]] documents the profile, kind, and mode fields an application passes.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When giving an application or node less authority, read
|
|
4
|
+
this because a scope list is an allow-list enforced by the daemon and a bearer
|
|
5
|
+
token can set the maximum authority for every run it creates.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Scopes and trust
|
|
9
|
+
|
|
10
|
+
Use scopes to give a node or remote application only the authority it needs. A node carries a scope list; a scoped bearer token carries a ceiling. Omitting a node scope list gives it the inherited runtime vocabulary. Supplying one narrows that authority. A token cannot create a node with scopes outside its own ceiling.
|
|
11
|
+
|
|
12
|
+
| Scope family | It gates |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `ask` | Human requests and review actions |
|
|
15
|
+
| `act` | Node creation on behalf of a node and node messaging |
|
|
16
|
+
| `schedule` | Creating scheduled work |
|
|
17
|
+
| `memory:read` / `memory:write` | Reading or changing memory through a node target |
|
|
18
|
+
| `llm`, `net`, and parameterized forms such as `files:<dir>` | Declared capability categories; some performers are recorded rather than enforced in the current beta |
|
|
19
|
+
|
|
20
|
+
This is an allow-list, not a claim that code will behave. The daemon checks the scope at the route that performs the action and rejects an unavailable one. The SDK’s [[crouter-sdk/nodes]] `scopes` field narrows a run; `client.memory` additionally checks `memory:read` or `memory:write` when you make a request on behalf of a node. A node may be allowed to consult its application’s knowledge while being unable to rewrite it.
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
flowchart LR
|
|
24
|
+
Token[Bearer token ceiling] --> Request[SDK request]
|
|
25
|
+
Request --> Daemon[crtrd]
|
|
26
|
+
Daemon -->|within ceiling| Node[Node with narrowed scopes]
|
|
27
|
+
Daemon -->|outside ceiling| Denied[403 scope_denied]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Scopes rely on a more basic trust boundary: `crtrd` is the sole writer of durable canvas state and the sole owner of broker lifecycle. SDK clients, the CLI, and viewers call its API; they do not open the canvas database or launch their own broker. One owner serializes lifecycle changes, makes the same API usable locally and remotely, and keeps a client from silently creating a second state authority.
|
|
31
|
+
|
|
32
|
+
For a browser or remote process, run `crtr sys connect`. It enables the daemon’s TCP listener when needed and returns `base_url` plus a bearer `token` to give the application. `crtr sys connect --scopes …` mints a new scoped token instead of returning the owner token. Treat either token as a credential: the remote app sends it as `Authorization: Bearer <token>`, and its scope list is the ceiling for scope-gated calls and newly created nodes.
|
|
33
|
+
|
|
34
|
+
The remote API is an explicit boundary, not permission to reach around it. Keep application code on the SDK or `/v1` contract, and let the daemon own state transitions. Run `crtr memory read internal/nodes-and-canvas` for the operational ownership model.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: knowledge
|
|
3
|
+
when-and-why-to-read: When deciding how an application should host or reconnect
|
|
4
|
+
to an agent, read this because the daemon keeps the durable canvas and broker
|
|
5
|
+
lifecycle in one place while terminals and SDK clients come and go.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Why a daemon
|
|
9
|
+
|
|
10
|
+
A terminal is not an agent host. A broker is the detached process that hosts one node’s agent engine and session; a viewer is only a terminal presentation attached to that broker. The daemon, `crtrd`, owns the durable canvas and starts, stops, and recovers brokers. This separation lets a node continue after the terminal that created or displayed it has gone away.
|
|
11
|
+
|
|
12
|
+
```mermaid
|
|
13
|
+
flowchart LR
|
|
14
|
+
CLI[crtr CLI] --> API[/v1 API]
|
|
15
|
+
SDK[SDK application] --> API
|
|
16
|
+
Viewer[Terminal viewer] --> API
|
|
17
|
+
API --> Daemon[crtrd]
|
|
18
|
+
Daemon --> Canvas[Canvas state]
|
|
19
|
+
Daemon --> Broker[Detached broker]
|
|
20
|
+
Viewer -. live session .-> Broker
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The broker has one job: host a node’s agent session. The daemon has the broader job: preserve the node graph, state transitions, waits, reports, and broker lifecycle. A viewer may stream a broker’s live session, but it does not become the authority that decides whether that node is active, dormant, or revived. Closing a viewer closes a view, not the node’s durable identity.
|
|
24
|
+
|
|
25
|
+
This is why a process crash is recoverable rather than an automatic loss of work. The daemon retains the node row, session information, artifacts, waits, and inbox state, then applies recovery to the broker execution. A later wake or explicit revival can continue the node’s saved conversation. The application should create nodes through [[crouter-sdk/nodes]], not start an LLM process itself, because the daemon is the component that keeps the engine and durable canvas state coordinated.
|
|
26
|
+
|
|
27
|
+
The costs are real. A daemon must be running, and canvas state has one home instead of being scattered across terminals and application processes. Local clients use its owner-only Unix socket. Remote clients use its configured TCP listener and bearer token. A network interruption can therefore fail a mutation loudly rather than encouraging a client to replay it and risk doing the action twice.
|
|
28
|
+
|
|
29
|
+
Those costs buy a simpler model: one state owner, one broker launcher, and clients that can reconnect. A CLI command, an SDK application, and a terminal viewer all use the same API for canvas state. They can come and go without creating competing writers or losing the graph that explains what each agent is doing.
|
|
30
|
+
|
|
31
|
+
For the lower-level runtime model, run `crtr memory read internal/nodes-and-canvas`. For a complete remote application setup, start with the SDK [[crouter-sdk/getting-started]].
|
|
@@ -11,12 +11,12 @@ when-and-why-to-read: When you need Plugin overview, this knowledge should be
|
|
|
11
11
|
|
|
12
12
|
Start with the `node_modules/@north-light/crouter-plugin/README.md` for a complete plugin that a developer can paste into an empty file. Then read the page that matches the work at hand:
|
|
13
13
|
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
14
|
+
- [Getting started](getting-started.md) — the deployment and install sequence.
|
|
15
|
+
- [Commands](commands.md) — command trees and descriptions that let an agent choose a command.
|
|
16
|
+
- [Parameters and handler input](parameters.md) — every `param.*` builder and the inferred handler input type.
|
|
17
|
+
- [Output](output.md) — every `field.*` builder and handler result validation.
|
|
18
|
+
- [Errors and streaming](errors.md) — `LeafError`, HTTP errors, and NDJSON streaming leaves.
|
|
19
|
+
- [Deployment](deploying.md) — Fetch frameworks, mount paths, proxies, authentication, and archive compression.
|
|
20
|
+
- [Bundles and memory docs](bundles-and-memory.md) — generating an archive and shipping memory docs.
|
|
21
21
|
|
|
22
22
|
The package exports only `LeafError`, `ManifestInvalidError`, `PluginDefinitionError`, `buildBundle`, `buildCommandManifest`, `createFetchHandler`, `defineBranch`, `defineLeaf`, `definePlugin`, `defineStreamingLeaf`, `field`, `kebab`, and `param`. Those are the complete authoring surface.
|
|
@@ -13,6 +13,6 @@ when-and-why-to-read: When you need Commands, this knowledge should be read
|
|
|
13
13
|
|
|
14
14
|
Use `defineBranch` to group commands and give it `children`. Use `defineLeaf` for a request that returns one object. A leaf requires `params`, `output`, `effects`, and `handler`; `params` may be omitted when the command has no input. `effects` is a non-empty list shown to the agent. Say whether a command mutates an application, creates billable resources, or is read-only.
|
|
15
15
|
|
|
16
|
-
`defineStreamingLeaf` has the same declaration shape, but its handler returns `AsyncIterable<object>`. It marks the generated REST mapping as streaming and is covered in [
|
|
16
|
+
`defineStreamingLeaf` has the same declaration shape, but its handler returns `AsyncIterable<object>`. It marks the generated REST mapping as streaming and is covered in [Errors and streaming](errors.md).
|
|
17
17
|
|
|
18
18
|
All command and parameter keys are derived into the manifest. Do not add a route path, HTTP method, REST mapping, or separate manifest to a command definition. The package always generates `POST` with all parameters in the JSON body.
|
|
@@ -27,4 +27,4 @@ Pass `token` to require `Authorization: Bearer <token>` on archive and command r
|
|
|
27
27
|
|
|
28
28
|
The install response is an uncompressed tar with `Content-Type: application/x-tar`. Do not configure a proxy, CDN, or framework middleware to gzip or otherwise compress it. Crtr rejects compressed archive bytes. The handler sends an ETag and supports conditional `If-None-Match` requests automatically.
|
|
29
29
|
|
|
30
|
-
A non-streaming successful command response is a bare JSON result object. Error responses are JSON error envelopes on non-2xx statuses. See [
|
|
30
|
+
A non-streaming successful command response is a bare JSON result object. Error responses are JSON error envelopes on non-2xx statuses. See [Errors and streaming](errors.md) for the exact error shape.
|
|
@@ -22,4 +22,4 @@ The endpoint receives `GET` to provide the install archive and `POST` to run a c
|
|
|
22
22
|
|
|
23
23
|
Run `crtr pkg plugin show acme` after installation to inspect the installed command tree, and `crtr sys doctor` to check the installed package. A plugin name collision with crtr or another installed plugin is determined during installation and cannot be predicted from an application repository alone.
|
|
24
24
|
|
|
25
|
-
Continue with [
|
|
25
|
+
Continue with [deployment](deploying.md) before mounting the handler below the origin root or behind a proxy.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var o=Object.defineProperty;var r=(e,s)=>o(e,"name",{value:s,configurable:!0});function l(e){const s=e.remote,t=s?"observer":e.role,a=new Set;return s||(a.add("local-canvas-inspect"),a.add("canvas-inbox")),t==="controller"&&(a.add("engine-drive"),a.add("dequeue"),a.add("wake-dormant"),a.add("auth"),a.add("canvas-resume")),{role:t,remote:s,nodeId:e.nodeId,profileId:e.profileId,permittedOperations:a,permits:r(n=>a.has(n),"permits"),refusal:r((n,i)=>{const c=i===void 0?"":`/${i} `;if(e.isBrokerOffline?.()===!0&&(n==="engine-drive"||n==="dequeue"||n==="auth"))return`${c}needs a live broker; this node is parked \u2014 submit a message to wake it`;if(!a.has(n))return s&&(n==="canvas-resume"||n==="local-canvas-inspect"||n==="canvas-inbox")?`${c}Read-only remote attach \u2014 not available with --canvas because it acts on this machine's local canvas`:s?`${c}Read-only remote attach \u2014 not available with --canvas (view-only)`:`${c}is not available to a read-only observer attach (--observer) \u2014 it would drive this node`},"refusal")}}r(l,"createViewerCapabilities");function u(e){switch(e.type){case"prompt":case"steer":case"follow_up":case"abort":case"bash":case"set_model":case"cycle_model":case"cycle_ladder":case"cycle_thinking":case"set_thinking_level":case"set_auto_retry":case"set_auto_compaction":case"compact":case"new_session":case"switch_session":case"fork":case"set_session_name":case"navigate_tree":case"reload":case"export":case"clone":case"share":case"reload_auth":return"engine-drive";default:return}}r(u,"viewerOperationForFrame");function v(e,s,t){if(t)switch(e){case"resume":return"canvas-resume";case"context":case"node-metadata":return"local-canvas-inspect";case"plugins":return;case"inbox":return"canvas-inbox";case"clear":case"promote":case"rename":case"resume-session":case"mcp":return"engine-drive";default:return}if(s)switch(e){case"copy":case"session":case"changelog":case"hotkeys":case"quit":return;case"login":case"logout":return"auth";default:return"engine-drive"}}r(v,"viewerOperationForSlash");export{l as createViewerCapabilities,u as viewerOperationForFrame,v as viewerOperationForSlash};
|
|
1
|
+
var o=Object.defineProperty;var r=(e,s)=>o(e,"name",{value:s,configurable:!0});function l(e){const s=e.remote,t=s?"observer":e.role,a=new Set;return s||(a.add("local-canvas-inspect"),a.add("canvas-inbox")),t==="controller"&&(a.add("engine-drive"),a.add("dequeue"),a.add("wake-dormant"),a.add("auth"),a.add("canvas-resume")),{role:t,remote:s,nodeId:e.nodeId,profileId:e.profileId,permittedOperations:a,permits:r(n=>a.has(n),"permits"),refusal:r((n,i)=>{const c=i===void 0?"":`/${i} `;if(e.isBrokerOffline?.()===!0&&(n==="engine-drive"||n==="dequeue"||n==="auth"))return`${c}needs a live broker; this node is parked \u2014 submit a message to wake it`;if(!a.has(n))return s&&(n==="canvas-resume"||n==="local-canvas-inspect"||n==="canvas-inbox")?`${c}Read-only remote attach \u2014 not available with --canvas because it acts on this machine's local canvas`:s?`${c}Read-only remote attach \u2014 not available with --canvas (view-only)`:`${c}is not available to a read-only observer attach (--observer) \u2014 it would drive this node`},"refusal")}}r(l,"createViewerCapabilities");function u(e){switch(e.type){case"prompt":case"steer":case"follow_up":case"abort":case"bash":case"set_model":case"cycle_model":case"cycle_ladder":case"cycle_thinking":case"set_thinking_level":case"set_auto_retry":case"set_auto_compaction":case"compact":case"new_session":case"switch_session":case"fork":case"set_session_name":case"navigate_tree":case"reload":case"export":case"clone":case"share":case"reload_auth":return"engine-drive";default:return}}r(u,"viewerOperationForFrame");function v(e,s,t){if(t)switch(e){case"resume":return"canvas-resume";case"context":case"node-metadata":return"local-canvas-inspect";case"plugins":return;case"inbox":return"canvas-inbox";case"clear":case"promote":case"rename":case"resume-session":case"tutorial":case"mcp":return"engine-drive";default:return}if(s)switch(e){case"copy":case"session":case"changelog":case"hotkeys":case"quit":return;case"login":case"logout":return"auth";default:return"engine-drive"}}r(v,"viewerOperationForSlash");export{l as createViewerCapabilities,u as viewerOperationForFrame,v as viewerOperationForSlash};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var w=Object.defineProperty;var d=(g,t)=>w(g,"name",{value:t,configurable:!0});import{matchesKey as h,truncateToWidth as c,visibleWidth as m}from"@earendil-works/pi-tui";import{ATTACH_CONTROL_BINDINGS as x,BINDING_CATALOG as T,formatBinding as $,matchesPiTuiInput as M}from"../../../core/keybindings/index.js";const R=.72,v={anchor:"center",width:"72%",minWidth:48,maxHeight:"72%",margin:1};class C{static{d(this,"HelpOverlay")}tui;pal;handle;bindings;scrollTop=0;lastContentRows=1;constructor(t,e){this.tui=t,this.pal=e}setBindings(t){this.bindings=t,this.handle!==void 0&&this.tui.requestRender()}toggle(){this.handle===void 0?this.open():this.close()}close(){this.handle!==void 0&&(this.handle.hide(),this.handle=void 0,this.tui.requestRender())}isOpen(){return this.handle!==void 0}invalidate(){this.handle!==void 0&&this.tui.requestRender()}render(t){const e=Math.max(1,t);if(e<5)return this.renderNarrow(e);const i=e-4,s=this.body(i),o=Math.max(1,this.tui.terminal.rows),n=Math.max(3,Math.min(Math.floor(o*R),Math.max(3,o-2))),r=Math.max(1,n-2),l=s.length>r,u=l&&r>=3?r-2:1;this.lastContentRows=u,this.scrollTop=Math.max(0,Math.min(this.scrollTop,Math.max(0,s.length-u)));const p=Math.min(s.length,this.scrollTop+u),a=[];for(l&&r>=3&&a.push(this.scrollTop>0?this.pal.muted(`\u2191 ${this.scrollTop} more`):""),a.push(...s.slice(this.scrollTop,p)),l&&r>=3?a.push(p<s.length?this.pal.muted(`\u2193 ${s.length-p} more`):""):l&&r===2&&a.push(p<s.length?this.pal.muted(`\u2193 ${s.length-p} more`):"");a.length<r;)a.push("");const y=this.pal.bold(this.pal.accent("\u2328 attach help")),b=this.pal.muted("esc dismiss \xB7 \u2191\u2193 scroll");return[this.borderRow("\u256D","\u256E",y,e),...a.map(f=>this.boxRow(f,e)),this.borderRow("\u2570","\u256F",b,e)].map(this.pal.surface)}handleInput(t){return h(t,"escape")||h(t,"q")?(this.close(),!0):h(t,"up")||this.matchesBinding("crtr.attach.scroll.up",t)?this.scrollBy(-1):h(t,"down")||this.matchesBinding("crtr.attach.scroll.down",t)?this.scrollBy(1):h(t,"pageUp")||this.matchesBinding("crtr.attach.scroll.page-up",t)?this.scrollBy(-this.pageSize()):h(t,"pageDown")||this.matchesBinding("crtr.attach.scroll.page-down",t)?this.scrollBy(this.pageSize()):!0}open(){this.scrollTop=0,this.handle=this.tui.showOverlay(this,v)}matchesBinding(t,e){return this.bindings!==void 0&&M(this.bindings,t,e,(i,s)=>h(i,s))}scrollBy(t){const e=this.body(Math.max(1,this.tui.terminal.columns-4)).length,i=Math.max(0,e-this.lastContentRows),s=Math.max(0,Math.min(i,this.scrollTop+t));return s!==this.scrollTop&&(this.scrollTop=s,this.tui.requestRender()),!0}pageSize(){return Math.max(1,this.lastContentRows-1)}body(t){return this.lines().flatMap(e=>this.wrap(e.text,t).map(i=>this.style(i,e.style)))}lines(){const t=d((n,r)=>({text:`${this.binding(n)} ${r}`,style:"plain"}),"binding"),e=d(n=>({text:n,style:"heading"}),"heading"),i=d(n=>({text:n,style:"muted"}),"muted"),s={text:"",style:"plain"},o=x.map(({actionId:n})=>{const r=T.find(l=>l.id===n)?.name??n;return t(n,r)});return[e("Scrolling"),t("crtr.attach.scroll.up","scroll one line up"),t("crtr.attach.scroll.down","scroll one line down"),t("crtr.attach.scroll.page-up","scroll one page up"),t("crtr.attach.scroll.page-down","scroll one page down"),t("crtr.attach.scroll.live","return to the live bottom"),i("mouse wheel (when reporting is on)"),s,e("Searching the transcript"),{text:`${this.binding("crtr.attach.search")} search what the transcript is currently showing. Type to jump as you go, enter to browse the matches.`,style:"plain"},{text:`${this.binding("crtr.attach.search.next")} / ${this.binding("crtr.attach.search.previous")} next and previous match \xB7 ${this.binding("crtr.attach.search.edit")} edit the query \xB7 ${this.binding("crtr.attach.search.close")} close. Typing anything else closes search and returns to the editor.`,style:"plain"},{text:"A lowercase query ignores case; any uppercase letter makes it case-sensitive.",style:"plain"},{text:"Folded tool output is not on screen, so it is not searched \u2014 unfold it to include it.",style:"muted"},s,e("Selection and copy"),{text:"Drag with the mouse to select; releasing copies the selection to the clipboard (and the tmux buffer). Any key clears the highlight.",style:"plain"},{text:"prefix + [ tmux copy-mode selects and copies the visible screenful; it works even while mouse reporting is on.",style:"plain"},{text:"Option-drag (iTerm2, Terminal.app) or Shift-drag (Ghostty, Alacritty, xterm) makes a true terminal selection that bypasses mouse reporting.",style:"plain"},{text:"Block-copy commands copy transcript content and are not capped at one screenful.",style:"plain"},s,e("Copy commands"),{text:"/copy last assistant message.",style:"plain"},{text:`/copy-message (${this.binding("crtr.attach.copy.message")}) newest copyable block entirely visible in the viewport; otherwise the newest one that intersects it.`,style:"plain"},{text:"/copy-tool newest tool call's result text.",style:"plain"},{text:"/copy-transcript every copyable block's text, oldest to newest, joined by blank lines.",style:"plain"},s,e("Mouse"),{text:`/mouse or ${this.binding("crtr.attach.mouse.toggle")} toggle mouse reporting \u2014 wheel scrolling and viewer drag-select. Turning it off restores tmux drag-select and copy-mode mouse.`,style:"plain"},s,e("Other attach keys"),...o]}binding(t){return this.bindings===void 0?"Loading\u2026":$(this.bindings,t)}wrap(t,e){if(t==="")return[""];const i=Math.max(1,e),s=t.split(/\s+/),o=[];let n="";for(const r of s){const l=n===""?r:`${n} ${r}`;n!==""&&m(l)>i?(o.push(c(n,i,"",!0)),n=` ${r}`):n=l}return n!==""&&o.push(c(n,i,"",!0)),o}style(t,e){switch(e){case"heading":return this.pal.bold(this.pal.accent(t));case"muted":return this.pal.muted(t);case"plain":return t}}boxRow(t,e){const i=e-4,s=c(t,i,"",!0),o=" ".repeat(Math.max(0,i-m(s)));return`${this.pal.border("\u2502")} ${s}${o} ${this.pal.border("\u2502")}`}borderRow(t,e,i,s){if(s<6)return c(`${this.pal.border(t)}${i}${this.pal.border(e)}`,s,"",!0);const o=s-5,n=c(i,o,"\u2026"),r=Math.max(0,s-5-m(n));return`${this.pal.border(`${t}\u2500`)} ${n} ${this.pal.border("\u2500".repeat(r)+e)}`}renderNarrow(t){const e=c(this.pal.bold(this.pal.accent("help")),t,"",!0);return[this.pal.surface(e)]}}export{C as HelpOverlay};
|
|
1
|
+
var w=Object.defineProperty;var d=(g,t)=>w(g,"name",{value:t,configurable:!0});import{matchesKey as h,truncateToWidth as c,visibleWidth as m}from"@earendil-works/pi-tui";import{ATTACH_CONTROL_BINDINGS as x,BINDING_CATALOG as T,formatBinding as $,matchesPiTuiInput as M}from"../../../core/keybindings/index.js";const R=.72,v={anchor:"center",width:"72%",minWidth:48,maxHeight:"72%",margin:1};class C{static{d(this,"HelpOverlay")}tui;pal;handle;bindings;scrollTop=0;lastContentRows=1;constructor(t,e){this.tui=t,this.pal=e}setBindings(t){this.bindings=t,this.handle!==void 0&&this.tui.requestRender()}toggle(){this.handle===void 0?this.open():this.close()}close(){this.handle!==void 0&&(this.handle.hide(),this.handle=void 0,this.tui.requestRender())}isOpen(){return this.handle!==void 0}invalidate(){this.handle!==void 0&&this.tui.requestRender()}render(t){const e=Math.max(1,t);if(e<5)return this.renderNarrow(e);const i=e-4,s=this.body(i),o=Math.max(1,this.tui.terminal.rows),n=Math.max(3,Math.min(Math.floor(o*R),Math.max(3,o-2))),r=Math.max(1,n-2),l=s.length>r,u=l&&r>=3?r-2:1;this.lastContentRows=u,this.scrollTop=Math.max(0,Math.min(this.scrollTop,Math.max(0,s.length-u)));const p=Math.min(s.length,this.scrollTop+u),a=[];for(l&&r>=3&&a.push(this.scrollTop>0?this.pal.muted(`\u2191 ${this.scrollTop} more`):""),a.push(...s.slice(this.scrollTop,p)),l&&r>=3?a.push(p<s.length?this.pal.muted(`\u2193 ${s.length-p} more`):""):l&&r===2&&a.push(p<s.length?this.pal.muted(`\u2193 ${s.length-p} more`):"");a.length<r;)a.push("");const y=this.pal.bold(this.pal.accent("\u2328 attach help")),b=this.pal.muted("esc dismiss \xB7 \u2191\u2193 scroll");return[this.borderRow("\u256D","\u256E",y,e),...a.map(f=>this.boxRow(f,e)),this.borderRow("\u2570","\u256F",b,e)].map(this.pal.surface)}handleInput(t){return h(t,"escape")||h(t,"q")?(this.close(),!0):h(t,"up")||this.matchesBinding("crtr.attach.scroll.up",t)?this.scrollBy(-1):h(t,"down")||this.matchesBinding("crtr.attach.scroll.down",t)?this.scrollBy(1):h(t,"pageUp")||this.matchesBinding("crtr.attach.scroll.page-up",t)?this.scrollBy(-this.pageSize()):h(t,"pageDown")||this.matchesBinding("crtr.attach.scroll.page-down",t)?this.scrollBy(this.pageSize()):!0}open(){this.scrollTop=0,this.handle=this.tui.showOverlay(this,v)}matchesBinding(t,e){return this.bindings!==void 0&&M(this.bindings,t,e,(i,s)=>h(i,s))}scrollBy(t){const e=this.body(Math.max(1,this.tui.terminal.columns-4)).length,i=Math.max(0,e-this.lastContentRows),s=Math.max(0,Math.min(i,this.scrollTop+t));return s!==this.scrollTop&&(this.scrollTop=s,this.tui.requestRender()),!0}pageSize(){return Math.max(1,this.lastContentRows-1)}body(t){return this.lines().flatMap(e=>this.wrap(e.text,t).map(i=>this.style(i,e.style)))}lines(){const t=d((n,r)=>({text:`${this.binding(n)} ${r}`,style:"plain"}),"binding"),e=d(n=>({text:n,style:"heading"}),"heading"),i=d(n=>({text:n,style:"muted"}),"muted"),s={text:"",style:"plain"},o=x.map(({actionId:n})=>{const r=T.find(l=>l.id===n)?.name??n;return t(n,r)});return[e("Learning crouter"),{text:"/tutorial the guided crouter tutorial \u2014 your agent teaches it, one lesson at a time",style:"plain"},s,e("Scrolling"),t("crtr.attach.scroll.up","scroll one line up"),t("crtr.attach.scroll.down","scroll one line down"),t("crtr.attach.scroll.page-up","scroll one page up"),t("crtr.attach.scroll.page-down","scroll one page down"),t("crtr.attach.scroll.live","return to the live bottom"),i("mouse wheel (when reporting is on)"),s,e("Searching the transcript"),{text:`${this.binding("crtr.attach.search")} search what the transcript is currently showing. Type to jump as you go, enter to browse the matches.`,style:"plain"},{text:`${this.binding("crtr.attach.search.next")} / ${this.binding("crtr.attach.search.previous")} next and previous match \xB7 ${this.binding("crtr.attach.search.edit")} edit the query \xB7 ${this.binding("crtr.attach.search.close")} close. Typing anything else closes search and returns to the editor.`,style:"plain"},{text:"A lowercase query ignores case; any uppercase letter makes it case-sensitive.",style:"plain"},{text:"Folded tool output is not on screen, so it is not searched \u2014 unfold it to include it.",style:"muted"},s,e("Selection and copy"),{text:"Drag with the mouse to select; releasing copies the selection to the clipboard (and the tmux buffer). Any key clears the highlight.",style:"plain"},{text:"prefix + [ tmux copy-mode selects and copies the visible screenful; it works even while mouse reporting is on.",style:"plain"},{text:"Option-drag (iTerm2, Terminal.app) or Shift-drag (Ghostty, Alacritty, xterm) makes a true terminal selection that bypasses mouse reporting.",style:"plain"},{text:"Block-copy commands copy transcript content and are not capped at one screenful.",style:"plain"},s,e("Copy commands"),{text:"/copy last assistant message.",style:"plain"},{text:`/copy-message (${this.binding("crtr.attach.copy.message")}) newest copyable block entirely visible in the viewport; otherwise the newest one that intersects it.`,style:"plain"},{text:"/copy-tool newest tool call's result text.",style:"plain"},{text:"/copy-transcript every copyable block's text, oldest to newest, joined by blank lines.",style:"plain"},s,e("Mouse"),{text:`/mouse or ${this.binding("crtr.attach.mouse.toggle")} toggle mouse reporting \u2014 wheel scrolling and viewer drag-select. Turning it off restores tmux drag-select and copy-mode mouse.`,style:"plain"},s,e("Other attach keys"),...o]}binding(t){return this.bindings===void 0?"Loading\u2026":$(this.bindings,t)}wrap(t,e){if(t==="")return[""];const i=Math.max(1,e),s=t.split(/\s+/),o=[];let n="";for(const r of s){const l=n===""?r:`${n} ${r}`;n!==""&&m(l)>i?(o.push(c(n,i,"",!0)),n=` ${r}`):n=l}return n!==""&&o.push(c(n,i,"",!0)),o}style(t,e){switch(e){case"heading":return this.pal.bold(this.pal.accent(t));case"muted":return this.pal.muted(t);case"plain":return t}}boxRow(t,e){const i=e-4,s=c(t,i,"",!0),o=" ".repeat(Math.max(0,i-m(s)));return`${this.pal.border("\u2502")} ${s}${o} ${this.pal.border("\u2502")}`}borderRow(t,e,i,s){if(s<6)return c(`${this.pal.border(t)}${i}${this.pal.border(e)}`,s,"",!0);const o=s-5,n=c(i,o,"\u2026"),r=Math.max(0,s-5-m(n));return`${this.pal.border(`${t}\u2500`)} ${n} ${this.pal.border("\u2500".repeat(r)+e)}`}renderNarrow(t){const e=c(this.pal.bold(this.pal.accent("help")),t,"",!0);return[this.pal.surface(e)]}}export{C as HelpOverlay};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var E=Object.defineProperty;var i=(o,e)=>E(o,"name",{value:e,configurable:!0});import{execFile as p}from"node:child_process";import{BUILTIN_SLASH_COMMANDS as h}from"../../../core/runtime/pi-vendored.js";import{surfaceTmuxStyleArgs as O}from"../../../core/runtime/surface-bg.js";import{viewerOperationForSlash as _}from"../input/capabilities.js";import{shellQuote as y}from"../../../shared/shell-quote.js";const g=[{name:"clear",description:"Clear the conversation and free context \u2014 a fresh, empty session (like /new)"},{name:"graph",description:"Toggle the canvas GRAPH view (your local subscription tree)"},{name:"inbox",description:"Toggle the human inbox \u2014 pending asks and reviews for you"},{name:"promote",description:"Promote this node to an orchestrator \u2014 /promote, or /promote <kind> to specialize"},{name:"resume",description:"Open the canvas navigator and swap THIS pane onto the chosen node; parked residents wake only on submitted input"},{name:"resume-session",description:"Switch this node onto one of its saved pi sessions \u2014 picker, or /resume-session <session-path>"},{name:"context",description:"Browse this node + its subscriptions\u2019 context dirs in a read-only nvim popup"},{name:"plugins",description:"Open the interactive plugin browser for this machine"},{name:"node-metadata",description:"Inspect this node \u2014 identity, lifecycle, runtime, graph, artifacts, paths, and agent surfaces"},{name:"mcp",description:"MCP servers \u2014 status from the adapter\u2019s config/cache; Enter (or /mcp <server>) connects, reconnects, or re-auths"},{name:"rename",description:"Rename this node everywhere \u2014 node row, viewer window, session chip, tmux pane: /rename <name>"},{name:"color",description:"Color this node \u2014 tmux pane outline + name chip: /color <color> (red, blue, #rrggbb, \u2026) or /color none"},{name:"mouse",description:"Toggle mouse wheel scrolling and drag-select \u2014 off restores tmux drag-select and copy-mode mouse"},{name:"copy-message",description:"Copy the newest message visible in the transcript viewport"},{name:"copy-tool",description:"Copy the newest tool call output"},{name:"copy-transcript",description:"Copy every copyable transcript block"},{name:"fold-tools",description:"View setting \u2014 fold every FINISHED tool call to its single call line (Alt+C \u2192 z \u2192 f)"}],$=new Map([["resume-node","resume"]]),w=new Set([...g.map(o=>o.name),...$.keys()]),U=new Set(["black","red","green","yellow","blue","magenta","cyan","white","brightred","brightgreen","brightyellow","brightblue","brightmagenta","brightcyan","orange","grey","gray","default"]),T=new Set(["trust"]),N=new Set(Object.keys({off:!0,minimal:!0,low:!0,medium:!0,high:!0,xhigh:!0,max:!0})),v=new Set(h.map(o=>o.name));function b(o){return o.capabilities.nodeId}i(b,"nodeIdFor");function H(o){return o.capabilities.profileId}i(H,"profileIdFor");function P(o,e){const n=_(o,v.has(o),w.has(o));return n===void 0?void 0:e.capabilities.refusal(n,o)}i(P,"readOnlyRefusal");function oe(){return new Set([...v,...w,...T])}i(oe,"locallyConsumedCommandNames");function j(o){const e=/^\/(\S+)\s*([\s\S]*)$/.exec(o.trim());return e===null?void 0:{name:e[1],arg:e[2].trim()}}i(j,"parseSlashCommand");function te(o){return o.trimStart().startsWith("/")}i(te,"isSlashCommand");function re(o,e){const n=j(o);if(n===void 0)return!1;const{arg:t}=n,r=$.get(n.name)??n.name;if(T.has(r))return e.notify(`/${r} is not supported in attach (Phase 4)`),!0;if(w.has(r)){const s=P(r,e);return s!==void 0?(e.notify(s),!0):X(r,t,e)}if(!v.has(r))return!1;const u=P(r,e);if(u!==void 0)return e.notify(u),!0;switch(r){case"new":return e.send({type:"new_session"}),!0;case"reload":return e.send({type:"reload"}),!0;case"compact":return e.send({type:"compact",instructions:t||void 0}),!0;case"quit":return e.onQuit?(e.onQuit(),!0):(e.send({type:"bye"}),e.notify("Detaching \u2014 the engine keeps running"),!0);case"name":return t?(e.send({type:"set_session_name",name:t}),!0):f(e,"/name <display name>");case"export":{const{path:s,format:a}=W(t,e.cwd??process.cwd());return e.send({type:"export",path:s,format:a}),e.notify(`Exporting session \u2192 ${s}`),!0}case"import":return t?(e.send({type:"switch_session",path:t}),!0):f(e,"/import <path-to-session.jsonl>");case"model":return t?(e.send({type:"set_model",model:t}),!0):m(e.openModelPicker,e,"model","/model <provider/id>");case"fork":return t?(e.send({type:"fork",entryId:t}),!0):m(e.openForkPicker,e,"fork point","/fork <entryId>");case"tree":return t?(e.send({type:"navigate_tree",targetId:t}),!0):m(e.openTreePicker,e,"tree","/tree <entryId>");case"scoped-models":return e.openScopedModelsPicker?(e.openScopedModelsPicker(),!0):(e.notify("The scoped-models view needs the read-op channel, which this viewer has not wired"),!0);case"settings":return Q(t,e);case"session":return e.notify(Y(e.state)),!0;case"copy":return e.onCopy?(e.onCopy(),!0):(e.notify("/copy is not available in this viewer"),!0);case"login":return e.openLoginPicker?(e.openLoginPicker(),!0):(e.notify("/login is not available in this viewer"),!0);case"logout":return e.openLogoutPicker?(e.openLogoutPicker(),!0):(e.notify("/logout is not available in this viewer"),!0);case"share":return e.send({type:"share"}),e.notify("Sharing session\u2026"),!0;case"clone":return e.send({type:"clone"}),e.notify("Cloning session\u2026"),!0;case"changelog":return e.notify("/changelog: see https://github.com/earendil-works/pi for release notes (no bundled changelog in this build)"),!0;case"hotkeys":return e.onHelp?(e.onHelp(),!0):(e.notify("/hotkeys is not available in this viewer"),!0);default:return e.notify(`/${r} is not available in attach (Phase 4)`),!0}}i(re,"dispatchSlashCommand");function M(o,e){const n=i(a=>{const l=_(a,!1,!0);return l===void 0||e.permits(l)},"permitted"),t=g.filter(a=>n(a.name)),r=new Map(g.map(a=>[a.name,a])),u=o.flatMap(a=>{const l=r.get(a.name);return l===void 0?[a]:n(a.name)?[l]:[]}),s=new Set(o.map(a=>a.name));return[...u,...t.filter(a=>!s.has(a.name))]}i(M,"withCanvasCommands");function ie(o,e){return M(o??h,e).map(n=>({name:n.name,description:n.description}))}i(ie,"slashCommandList");function se(o,e){return M(o??h,e).map(n=>({value:`/${n.name}`,label:`/${n.name}`,description:n.description}))}i(se,"commandAutocompleteItems");function X(o,e,n){switch(o){case"clear":return n.send({type:"new_session"}),!0;case"graph":return n.onGraph?n.onGraph():n.notify("The GRAPH overlay is not wired into the viewer yet"),!0;case"inbox":return n.onInbox?n.onInbox():n.notify("The inbox surface is not wired into the viewer yet"),!0;case"promote":return B(e,n),!0;case"resume":return e?f(n,"/resume \u2014 no argument; use /resume-session <path> to switch pi sessions"):G(n);case"resume-session":return e?(n.send({type:"switch_session",path:e}),!0):m(n.openSessionPicker,n,"session","/resume-session <session-path>");case"mcp":return n.openMcpPicker?n.openMcpPicker(e||void 0):n.notify("The MCP panel is not wired into this viewer"),!0;case"context":return V(n);case"plugins":return q(n);case"node-metadata":return n.onNodeMetadata?n.onNodeMetadata():n.notify("Node metadata is not wired into this viewer"),!0;case"rename":return F(e,n),!0;case"color":return R(e,n);case"fold-tools":return n.onFoldTools?n.onFoldTools():n.notify("The fold-tools view setting is not wired into this viewer"),!0;case"mouse":return n.onToggleMouse?n.onToggleMouse():n.notify("Mouse reporting is not wired into this viewer"),!0;case"copy-message":return n.onCopyTarget?n.onCopyTarget("message"):n.notify("/copy-message is not available in this viewer"),!0;case"copy-tool":return n.onCopyTarget?n.onCopyTarget("tool"):n.notify("/copy-tool is not available in this viewer"),!0;case"copy-transcript":return n.onCopyTarget?n.onCopyTarget("transcript"):n.notify("/copy-transcript is not available in this viewer"),!0;default:return n.notify(`/${o} is not available in attach`),!0}}i(X,"dispatchCanvasCommand");function B(o,e){const n=b(e).trim();if(n===""){e.notify("/promote: no node to promote (viewer has no node id)");return}const t=o.trim().toLowerCase();e.notify(t?`Promoting \u2192 ${t}\u2026`:"Promoting\u2026");const r=["node","promote","--node",n,"--json"];t!==""&&r.push("--kind",t),p("crtr",r,{timeout:15e3,maxBuffer:4*1024*1024},(s,a)=>{let l=null,d=null;try{l=JSON.parse(a)}catch{l=null}if(s&&l===null){const L=typeof s.stdout=="string"?s.stdout:"";try{const C=JSON.parse(L);d=typeof C.message=="string"?C.message:null}catch{d=null}d===null&&(d=s.message)}if(l===null){e.notify(`promote failed: ${d??"unknown error"}`);return}const S=(l.roadmap_path??"").trim();e.notify(`Promoted to ${l.kind??"orchestrator"} orchestrator \u2014 authoring roadmap${S!==""?` (${S})`:""}.`),e.send({type:"prompt",text:"You have just been promoted to an orchestrator. Your new-role guidance is arriving \u2014 read it, author your roadmap, and start delegating."})}).stdin?.end()}i(B,"promoteNode");function F(o,e){const n=b(e).trim();if(n===""){e.notify("/rename: no node to rename (viewer has no node id)");return}if(o===""){e.notify("Usage: /rename <name>");return}e.send({type:"set_session_name",name:o});const t=process.env.TMUX_PANE;process.env.TMUX!==void 0&&t!==void 0&&t!==""&&p("tmux",["select-pane","-t",t,"-T",o],()=>{}),p("crtr",["node","config","--node",n,"--name",o,"--json"],{timeout:15e3,maxBuffer:4*1024*1024},(r,u)=>{if(r){let s=null;try{const a=JSON.parse(u);s=typeof a.message=="string"?a.message:null}catch{s=null}e.notify(`rename failed: ${s??r.message}`);return}e.notify(`Renamed node \u2192 ${o}`)})}i(F,"renameNode");function c(o){return new Promise(e=>{p("tmux",o,{timeout:5e3},(n,t)=>e(n?"":t))})}i(c,"tmuxCall");async function D(o,e,n){if(n){await c(["set-option","-p","-u","-t",o,"@crtr_color"]);return}await c(["set-option","-p","-t",o,"@crtr_color",e]);for(const t of["pane-border-style","pane-active-border-style"]){const r=(await c(["show-options","-wqv","-t",o,t])).trim();if(r.includes("@crtr_color"))continue;let u=r!==""?r:(await c(["show-options","-gqv",t])).trim();u===""&&(u=t==="pane-active-border-style"?"fg=green":"default");const s=`#{?#{!=:#{@crtr_color},},fg=#{@crtr_color},${u.replace(/,/g,"#,")}}`;await c(["set-option","-w","-t",o,t,s])}}i(D,"paintPaneBorder");function R(o,e){const n=o.trim().toLowerCase();if(n==="")return e.notify("Usage: /color <color> \u2014 a name (red, blue, orange\u2026), colourN, or #rrggbb; /color none to clear"),!0;const t=n==="none"||n==="clear";if(!(t||U.has(n)||/^colour\d{1,3}$/.test(n)||/^#[0-9a-f]{6}$/.test(n)))return e.notify(`Invalid color "${n}" \u2014 use a tmux colour name (${["red","green","yellow","blue","magenta","cyan","orange"].join(", ")}, \u2026), colourN, or #rrggbb`),!0;const u=process.env.TMUX_PANE,s=process.env.TMUX!==void 0&&u!==void 0&&u!=="";return s&&D(u,n,t),e.onColor?.(t?null:n),e.notify(t?"Node color cleared":`Node colored ${n}${s?"":" (chip only \u2014 no tmux pane)"}`),!0}i(R,"colorNode");function G(o){const e=process.env.TMUX_PANE;if(process.env.TMUX===void 0||e===void 0||e==="")return o.notify("/resume needs tmux"),!0;const n=y(o.cwd??process.cwd()),t=H(o),r=t===void 0||t===""?"":` --profile-id ${y(t)}`,u=`crtr canvas browse --return-pane ${e} --cwd ${n}${r}`;return k(u,{w:"97%",h:"95%"}),!0}i(G,"resumeNode");function V(o){if(process.env.TMUX===void 0)return o.notify("/context needs tmux"),!0;const e=b(o).trim();return e===""?(o.notify("/context: no node to root on (viewer has no node id)"),!0):(k(`crtr node inspect context --node ${y(e)}`),!0)}i(V,"openContext");function q(o){return process.env.TMUX===void 0?(o.notify("/plugins needs tmux"),!0):(k("crtr pkg browse"),!0)}i(q,"openPlugins");function k(o,e={w:"90%",h:"85%"}){try{p("tmux",["display-popup","-E","-w",e.w,"-h",e.h,...O(),o],()=>{})}catch{}}i(k,"popup");function f(o,e){return o.notify(`Usage: ${e}`),!0}i(f,"usage");function J(o,e,n){return o.notify(`Interactive ${e} picker isn't available over view.sock yet \u2014 use ${n}`),!0}i(J,"pickerHint");function m(o,e,n,t){return o?(o(),!0):J(e,n,t)}i(m,"openOrHint");function Q(o,e){const[n,...t]=o.split(/\s+/),r=t.join(" ").trim();switch(n){case"thinking":return r?N.has(r)?(e.send({type:"set_thinking_level",level:r}),!0):(e.notify(`Invalid thinking level "${r}" \u2014 choose one of: ${[...N].join(", ")}`),!0):f(e,"/settings thinking <level>");case"auto-retry":return e.send({type:"set_auto_retry",enabled:A(r)}),!0;case"auto-compaction":return e.send({type:"set_auto_compaction",enabled:A(r)}),!0;case"":return e.openSettingsPicker?(e.openSettingsPicker(),!0):I(e);default:return I(e)}}i(Q,"handleSettings");function I(o){return o.notify("Settings: /settings thinking <level> | auto-retry on|off | auto-compaction on|off"),!0}i(I,"settingsSummary");function A(o){return/^(on|true|yes|1|enabled?)$/i.test(o.trim())}i(A,"parseBool");function W(o,e){const n=o||`${e}/pi-export-${Date.now()}.html`,t=n.endsWith(".jsonl")?"jsonl":"html";return{path:n,format:t}}i(W,"parseExport");function Y(o){return o?`Session ${o.sessionName?`"${o.sessionName}" `:""}${o.sessionId} \xB7 model ${o.model?.id??"unknown"} \xB7 thinking ${o.thinkingLevel}`:"Session info unavailable"}i(Y,"describeSession");export{g as CANVAS_SLASH_COMMANDS,se as commandAutocompleteItems,re as dispatchSlashCommand,te as isSlashCommand,oe as locallyConsumedCommandNames,ie as slashCommandList};
|
|
1
|
+
var E=Object.defineProperty;var i=(t,e)=>E(t,"name",{value:e,configurable:!0});import{execFile as p}from"node:child_process";import{BUILTIN_SLASH_COMMANDS as h}from"../../../core/runtime/pi-vendored.js";import{surfaceTmuxStyleArgs as O}from"../../../core/runtime/surface-bg.js";import{viewerOperationForSlash as _}from"../input/capabilities.js";import{shellQuote as y}from"../../../shared/shell-quote.js";const g=[{name:"tutorial",description:"The guided crouter tutorial \u2014 your agent teaches it, one lesson at a time"},{name:"clear",description:"Clear the conversation and free context \u2014 a fresh, empty session (like /new)"},{name:"graph",description:"Toggle the canvas GRAPH view (your local subscription tree)"},{name:"inbox",description:"Toggle the human inbox \u2014 pending asks and reviews for you"},{name:"promote",description:"Promote this node to an orchestrator \u2014 /promote, or /promote <kind> to specialize"},{name:"resume",description:"Open the canvas navigator and swap THIS pane onto the chosen node; parked residents wake only on submitted input"},{name:"resume-session",description:"Switch this node onto one of its saved pi sessions \u2014 picker, or /resume-session <session-path>"},{name:"context",description:"Browse this node + its subscriptions\u2019 context dirs in a read-only nvim popup"},{name:"plugins",description:"Open the interactive plugin browser for this machine"},{name:"node-metadata",description:"Inspect this node \u2014 identity, lifecycle, runtime, graph, artifacts, paths, and agent surfaces"},{name:"mcp",description:"MCP servers \u2014 status from the adapter\u2019s config/cache; Enter (or /mcp <server>) connects, reconnects, or re-auths"},{name:"rename",description:"Rename this node everywhere \u2014 node row, viewer window, session chip, tmux pane: /rename <name>"},{name:"color",description:"Color this node \u2014 tmux pane outline + name chip: /color <color> (red, blue, #rrggbb, \u2026) or /color none"},{name:"mouse",description:"Toggle mouse wheel scrolling and drag-select \u2014 off restores tmux drag-select and copy-mode mouse"},{name:"copy-message",description:"Copy the newest message visible in the transcript viewport"},{name:"copy-tool",description:"Copy the newest tool call output"},{name:"copy-transcript",description:"Copy every copyable transcript block"},{name:"fold-tools",description:"View setting \u2014 fold every FINISHED tool call to its single call line (Alt+C \u2192 z \u2192 f)"}],$=new Map([["resume-node","resume"]]),w=new Set([...g.map(t=>t.name),...$.keys()]),U=new Set(["black","red","green","yellow","blue","magenta","cyan","white","brightred","brightgreen","brightyellow","brightblue","brightmagenta","brightcyan","orange","grey","gray","default"]),T=new Set(["trust"]),N=new Set(Object.keys({off:!0,minimal:!0,low:!0,medium:!0,high:!0,xhigh:!0,max:!0})),v=new Set(h.map(t=>t.name));function b(t){return t.capabilities.nodeId}i(b,"nodeIdFor");function H(t){return t.capabilities.profileId}i(H,"profileIdFor");function P(t,e){const n=_(t,v.has(t),w.has(t));return n===void 0?void 0:e.capabilities.refusal(n,t)}i(P,"readOnlyRefusal");function te(){return new Set([...v,...w,...T])}i(te,"locallyConsumedCommandNames");function j(t){const e=/^\/(\S+)\s*([\s\S]*)$/.exec(t.trim());return e===null?void 0:{name:e[1],arg:e[2].trim()}}i(j,"parseSlashCommand");function oe(t){return t.trimStart().startsWith("/")}i(oe,"isSlashCommand");function re(t,e){const n=j(t);if(n===void 0)return!1;const{arg:o}=n,r=$.get(n.name)??n.name;if(T.has(r))return e.notify(`/${r} is not supported in attach (Phase 4)`),!0;if(w.has(r)){const s=P(r,e);return s!==void 0?(e.notify(s),!0):X(r,o,e)}if(!v.has(r))return!1;const u=P(r,e);if(u!==void 0)return e.notify(u),!0;switch(r){case"new":return e.send({type:"new_session"}),!0;case"reload":return e.send({type:"reload"}),!0;case"compact":return e.send({type:"compact",instructions:o||void 0}),!0;case"quit":return e.onQuit?(e.onQuit(),!0):(e.send({type:"bye"}),e.notify("Detaching \u2014 the engine keeps running"),!0);case"name":return o?(e.send({type:"set_session_name",name:o}),!0):m(e,"/name <display name>");case"export":{const{path:s,format:a}=W(o,e.cwd??process.cwd());return e.send({type:"export",path:s,format:a}),e.notify(`Exporting session \u2192 ${s}`),!0}case"import":return o?(e.send({type:"switch_session",path:o}),!0):m(e,"/import <path-to-session.jsonl>");case"model":return o?(e.send({type:"set_model",model:o}),!0):f(e.openModelPicker,e,"model","/model <provider/id>");case"fork":return o?(e.send({type:"fork",entryId:o}),!0):f(e.openForkPicker,e,"fork point","/fork <entryId>");case"tree":return o?(e.send({type:"navigate_tree",targetId:o}),!0):f(e.openTreePicker,e,"tree","/tree <entryId>");case"scoped-models":return e.openScopedModelsPicker?(e.openScopedModelsPicker(),!0):(e.notify("The scoped-models view needs the read-op channel, which this viewer has not wired"),!0);case"settings":return Q(o,e);case"session":return e.notify(Y(e.state)),!0;case"copy":return e.onCopy?(e.onCopy(),!0):(e.notify("/copy is not available in this viewer"),!0);case"login":return e.openLoginPicker?(e.openLoginPicker(),!0):(e.notify("/login is not available in this viewer"),!0);case"logout":return e.openLogoutPicker?(e.openLogoutPicker(),!0):(e.notify("/logout is not available in this viewer"),!0);case"share":return e.send({type:"share"}),e.notify("Sharing session\u2026"),!0;case"clone":return e.send({type:"clone"}),e.notify("Cloning session\u2026"),!0;case"changelog":return e.notify("/changelog: see https://github.com/earendil-works/pi for release notes (no bundled changelog in this build)"),!0;case"hotkeys":return e.onHelp?(e.onHelp(),!0):(e.notify("/hotkeys is not available in this viewer"),!0);default:return e.notify(`/${r} is not available in attach (Phase 4)`),!0}}i(re,"dispatchSlashCommand");function M(t,e){const n=i(a=>{const l=_(a,!1,!0);return l===void 0||e.permits(l)},"permitted"),o=g.filter(a=>n(a.name)),r=new Map(g.map(a=>[a.name,a])),u=t.flatMap(a=>{const l=r.get(a.name);return l===void 0?[a]:n(a.name)?[l]:[]}),s=new Set(t.map(a=>a.name));return[...u,...o.filter(a=>!s.has(a.name))]}i(M,"withCanvasCommands");function ie(t,e){return M(t??h,e).map(n=>({name:n.name,description:n.description}))}i(ie,"slashCommandList");function se(t,e){return M(t??h,e).map(n=>({value:`/${n.name}`,label:`/${n.name}`,description:n.description}))}i(se,"commandAutocompleteItems");function X(t,e,n){switch(t){case"clear":return n.send({type:"new_session"}),!0;case"graph":return n.onGraph?n.onGraph():n.notify("The GRAPH overlay is not wired into the viewer yet"),!0;case"inbox":return n.onInbox?n.onInbox():n.notify("The inbox surface is not wired into the viewer yet"),!0;case"tutorial":return n.send({type:"prompt",text:"The user asked for the crouter tutorial. Run `crtr sys tutorial -h`, pick the track that matches what they want to learn, and teach it one lesson per invocation \u2014 in your own words, letting them try each thing before you move on."}),!0;case"promote":return B(e,n),!0;case"resume":return e?m(n,"/resume \u2014 no argument; use /resume-session <path> to switch pi sessions"):G(n);case"resume-session":return e?(n.send({type:"switch_session",path:e}),!0):f(n.openSessionPicker,n,"session","/resume-session <session-path>");case"mcp":return n.openMcpPicker?n.openMcpPicker(e||void 0):n.notify("The MCP panel is not wired into this viewer"),!0;case"context":return V(n);case"plugins":return q(n);case"node-metadata":return n.onNodeMetadata?n.onNodeMetadata():n.notify("Node metadata is not wired into this viewer"),!0;case"rename":return F(e,n),!0;case"color":return R(e,n);case"fold-tools":return n.onFoldTools?n.onFoldTools():n.notify("The fold-tools view setting is not wired into this viewer"),!0;case"mouse":return n.onToggleMouse?n.onToggleMouse():n.notify("Mouse reporting is not wired into this viewer"),!0;case"copy-message":return n.onCopyTarget?n.onCopyTarget("message"):n.notify("/copy-message is not available in this viewer"),!0;case"copy-tool":return n.onCopyTarget?n.onCopyTarget("tool"):n.notify("/copy-tool is not available in this viewer"),!0;case"copy-transcript":return n.onCopyTarget?n.onCopyTarget("transcript"):n.notify("/copy-transcript is not available in this viewer"),!0;default:return n.notify(`/${t} is not available in attach`),!0}}i(X,"dispatchCanvasCommand");function B(t,e){const n=b(e).trim();if(n===""){e.notify("/promote: no node to promote (viewer has no node id)");return}const o=t.trim().toLowerCase();e.notify(o?`Promoting \u2192 ${o}\u2026`:"Promoting\u2026");const r=["node","promote","--node",n,"--json"];o!==""&&r.push("--kind",o),p("crtr",r,{timeout:15e3,maxBuffer:4*1024*1024},(s,a)=>{let l=null,d=null;try{l=JSON.parse(a)}catch{l=null}if(s&&l===null){const L=typeof s.stdout=="string"?s.stdout:"";try{const C=JSON.parse(L);d=typeof C.message=="string"?C.message:null}catch{d=null}d===null&&(d=s.message)}if(l===null){e.notify(`promote failed: ${d??"unknown error"}`);return}const S=(l.roadmap_path??"").trim();e.notify(`Promoted to ${l.kind??"orchestrator"} orchestrator \u2014 authoring roadmap${S!==""?` (${S})`:""}.`),e.send({type:"prompt",text:"You have just been promoted to an orchestrator. Your new-role guidance is arriving \u2014 read it, author your roadmap, and start delegating."})}).stdin?.end()}i(B,"promoteNode");function F(t,e){const n=b(e).trim();if(n===""){e.notify("/rename: no node to rename (viewer has no node id)");return}if(t===""){e.notify("Usage: /rename <name>");return}e.send({type:"set_session_name",name:t});const o=process.env.TMUX_PANE;process.env.TMUX!==void 0&&o!==void 0&&o!==""&&p("tmux",["select-pane","-t",o,"-T",t],()=>{}),p("crtr",["node","config","--node",n,"--name",t,"--json"],{timeout:15e3,maxBuffer:4*1024*1024},(r,u)=>{if(r){let s=null;try{const a=JSON.parse(u);s=typeof a.message=="string"?a.message:null}catch{s=null}e.notify(`rename failed: ${s??r.message}`);return}e.notify(`Renamed node \u2192 ${t}`)})}i(F,"renameNode");function c(t){return new Promise(e=>{p("tmux",t,{timeout:5e3},(n,o)=>e(n?"":o))})}i(c,"tmuxCall");async function D(t,e,n){if(n){await c(["set-option","-p","-u","-t",t,"@crtr_color"]);return}await c(["set-option","-p","-t",t,"@crtr_color",e]);for(const o of["pane-border-style","pane-active-border-style"]){const r=(await c(["show-options","-wqv","-t",t,o])).trim();if(r.includes("@crtr_color"))continue;let u=r!==""?r:(await c(["show-options","-gqv",o])).trim();u===""&&(u=o==="pane-active-border-style"?"fg=green":"default");const s=`#{?#{!=:#{@crtr_color},},fg=#{@crtr_color},${u.replace(/,/g,"#,")}}`;await c(["set-option","-w","-t",t,o,s])}}i(D,"paintPaneBorder");function R(t,e){const n=t.trim().toLowerCase();if(n==="")return e.notify("Usage: /color <color> \u2014 a name (red, blue, orange\u2026), colourN, or #rrggbb; /color none to clear"),!0;const o=n==="none"||n==="clear";if(!(o||U.has(n)||/^colour\d{1,3}$/.test(n)||/^#[0-9a-f]{6}$/.test(n)))return e.notify(`Invalid color "${n}" \u2014 use a tmux colour name (${["red","green","yellow","blue","magenta","cyan","orange"].join(", ")}, \u2026), colourN, or #rrggbb`),!0;const u=process.env.TMUX_PANE,s=process.env.TMUX!==void 0&&u!==void 0&&u!=="";return s&&D(u,n,o),e.onColor?.(o?null:n),e.notify(o?"Node color cleared":`Node colored ${n}${s?"":" (chip only \u2014 no tmux pane)"}`),!0}i(R,"colorNode");function G(t){const e=process.env.TMUX_PANE;if(process.env.TMUX===void 0||e===void 0||e==="")return t.notify("/resume needs tmux"),!0;const n=y(t.cwd??process.cwd()),o=H(t),r=o===void 0||o===""?"":` --profile-id ${y(o)}`,u=`crtr canvas browse --return-pane ${e} --cwd ${n}${r}`;return k(u,{w:"97%",h:"95%"}),!0}i(G,"resumeNode");function V(t){if(process.env.TMUX===void 0)return t.notify("/context needs tmux"),!0;const e=b(t).trim();return e===""?(t.notify("/context: no node to root on (viewer has no node id)"),!0):(k(`crtr node inspect context --node ${y(e)}`),!0)}i(V,"openContext");function q(t){return process.env.TMUX===void 0?(t.notify("/plugins needs tmux"),!0):(k("crtr pkg browse"),!0)}i(q,"openPlugins");function k(t,e={w:"90%",h:"85%"}){try{p("tmux",["display-popup","-E","-w",e.w,"-h",e.h,...O(),t],()=>{})}catch{}}i(k,"popup");function m(t,e){return t.notify(`Usage: ${e}`),!0}i(m,"usage");function J(t,e,n){return t.notify(`Interactive ${e} picker isn't available over view.sock yet \u2014 use ${n}`),!0}i(J,"pickerHint");function f(t,e,n,o){return t?(t(),!0):J(e,n,o)}i(f,"openOrHint");function Q(t,e){const[n,...o]=t.split(/\s+/),r=o.join(" ").trim();switch(n){case"thinking":return r?N.has(r)?(e.send({type:"set_thinking_level",level:r}),!0):(e.notify(`Invalid thinking level "${r}" \u2014 choose one of: ${[...N].join(", ")}`),!0):m(e,"/settings thinking <level>");case"auto-retry":return e.send({type:"set_auto_retry",enabled:A(r)}),!0;case"auto-compaction":return e.send({type:"set_auto_compaction",enabled:A(r)}),!0;case"":return e.openSettingsPicker?(e.openSettingsPicker(),!0):I(e);default:return I(e)}}i(Q,"handleSettings");function I(t){return t.notify("Settings: /settings thinking <level> | auto-retry on|off | auto-compaction on|off"),!0}i(I,"settingsSummary");function A(t){return/^(on|true|yes|1|enabled?)$/i.test(t.trim())}i(A,"parseBool");function W(t,e){const n=t||`${e}/pi-export-${Date.now()}.html`,o=n.endsWith(".jsonl")?"jsonl":"html";return{path:n,format:o}}i(W,"parseExport");function Y(t){return t?`Session ${t.sessionName?`"${t.sessionName}" `:""}${t.sessionId} \xB7 model ${t.model?.id??"unknown"} \xB7 thinking ${t.thinkingLevel}`:"Session info unavailable"}i(Y,"describeSession");export{g as CANVAS_SLASH_COMMANDS,se as commandAutocompleteItems,re as dispatchSlashCommand,oe as isSlashCommand,te as locallyConsumedCommandNames,ie as slashCommandList};
|