@north-light/crouter 0.3.326 → 0.3.328

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 CHANGED
@@ -1,46 +1,30 @@
1
1
  # crouter
2
2
 
3
+ crouter is an agent runtime. Agents work as nodes on a canvas; the `crtrd` daemon owns the canvas state and node lifecycle, and the `crtr` CLI creates, observes, and manages nodes.
4
+
3
5
  ## Install
4
6
 
5
7
  ```bash
6
8
  npm install -g @north-light/crouter
9
+ crtr sys setup
7
10
  ```
8
11
 
9
- ## Building from source
10
-
11
- `npm run build` requires a C compiler and a local filesystem with atomic directory exchange support: APFS on macOS, or a Linux filesystem that supports `renameat2(..., RENAME_EXCHANGE)` (such as ext4, XFS, or Btrfs). The build verifies both prerequisites before compiling output because publication swaps the complete staged `dist` tree into place atomically; that is build/packaging behavior, not the runtime safety boundary.
12
-
13
- To activate source changes, run `npm run build && npm run install-runtime`, then restart the daemon. `install-runtime` stages and seals a complete immutable local generation before atomically switching `selected`; a failed install preserves the prior selection, and live brokers continue using their own generation until restarted.
14
-
15
- ## Runtime generations
16
-
17
- The packaged `runtime.lock.json` is copied to the staged generation's `package-lock.json` for its production dependency install. After a successful install, generation GC keeps `selected` plus the three newest generations, but conservatively skips pruning entirely while any crtr shim process may still be using a generation; old generations are reclaimed on a later install when no such process is live.
18
-
19
- `crtr sys version` reports the selected and current-process generations and flags drift. `crtr sys doctor` validates their manifests, sealing and containment, and hashes their complete inventories on demand.
20
-
21
- ## Usage
12
+ `crtr sys setup` is the first-run installer. It signs in to a model provider and lets you install companion packages and system dependencies. Then explore the CLI:
22
13
 
23
14
  ```bash
24
15
  crtr --help
16
+ crtr node new "Inspect this repository and report the failing tests."
25
17
  ```
26
18
 
27
- ## Official marketplace
28
-
29
- `crtr` ships with the [crouter official marketplace](https://github.com/crouton-labs/crouter-official-marketplace) pre-installed. On first run it is cloned into your user scope and registered automatically — no plugins are enabled by default.
30
-
31
- Browse and install plugins from it:
32
-
33
- ```bash
34
- crtr pkg market browse crouter-official-marketplace
35
- crtr pkg plugin install crouter-official-marketplace/<plugin>
36
- ```
19
+ ## Use crouter from an application
37
20
 
38
- To opt out of the bootstrap (e.g. in CI), set `CRTR_NO_BOOTSTRAP=1`.
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://github.com/vallum-security/crouter/tree/main/docs/sdk) has working examples and the full API reference.
39
22
 
40
- ## Running crouter on pi
23
+ ## Extend crouter
41
24
 
42
- The node runtime hosts the [pi coding agent](https://pi.dev) in each broker. To set your pi install up the way crouter's author runs it, add these to the `packages` array in `~/.pi/agent/settings.json` (neither ships with pi — you add them yourself):
25
+ - Build an HTTP plugin with [`@north-light/crouter-plugin`](https://www.npmjs.com/package/@north-light/crouter-plugin). Its [authoring guide](https://github.com/vallum-security/crouter/tree/main/docs/plugin) covers commands, parameters, output, errors, deployment, and plugin memory documents.
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.
43
27
 
44
- - **[pi-claude-oauth-adapter](https://github.com/minzique/dotfiles-agents/tree/main/packages/pi-claude-oauth-adapter)** — third-party npm package; Anthropic OAuth / Claude Code compatibility adapter for pi. Install with `"npm:pi-claude-oauth-adapter"`.
45
- - **pi-crtr-extensions** — crtr's engine-side pi extensions (crtr help-gate, slash-command surfacing, provider rotation, frontmatter rules). Bundled in this repo at `src/builtin-pi-packages/pi-crtr-extensions` and wired into `~/.pi/agent/settings.json` automatically by `crtr sys setup` — nothing to install separately.
28
+ ## Source and contributing
46
29
 
30
+ Source, issue tracking, and development instructions are in the [crouter repository](https://github.com/vallum-security/crouter). Build and runtime-generation details are for contributors and are kept there rather than on this package page.
@@ -19,10 +19,18 @@ export declare class NodeEventRing {
19
19
  };
20
20
  get empty(): boolean;
21
21
  }
22
- /** Translate only the application-safe subset of broker frames. The map retains
23
- * tool argument summaries between start/end; raw arguments and tool output never
24
- * cross this boundary. */
25
- export declare function translateBrokerFrame(nodeId: string, frame: BrokerToClient, toolSummaries?: Map<string, string>): StreamEventPayload[];
22
+ /** Per-node state retained while broker frames become public stream events. */
23
+ export declare class NodeEventTranslationState {
24
+ readonly toolSummaries: Map<string, string>;
25
+ private emittedAssistantText;
26
+ resetAssistantText(): void;
27
+ emitAssistantTextDelta(message: unknown): string;
28
+ seedAssistantText(message: unknown): string;
29
+ }
30
+ /** Translate only the application-safe subset of broker frames. Tool argument
31
+ * summaries and emitted assistant text stay per node hub; raw arguments and
32
+ * tool output never cross this boundary. */
33
+ export declare function translateBrokerFrame(nodeId: string, frame: BrokerToClient, state?: NodeEventTranslationState): StreamEventPayload[];
26
34
  declare class NodeEventStreams {
27
35
  private readonly hubs;
28
36
  subscribe(nodeId: string, after: number | undefined, subscriber: Subscriber): {
@@ -1,6 +1,6 @@
1
- var q=Object.defineProperty;var o=(n,e)=>q(n,"name",{value:e,configurable:!0});import{randomUUID as T}from"node:crypto";import{ApiError as E}from"../../../api/errors.js";import{BrokerClient as C}from"../../../core/broker-client/client.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{isPidAlive as A}from"../../../core/canvas/pid.js";import{BROKER_VIEW_SOCKET_WAIT_MS as M}from"../../../core/runtime/view-socket.js";import{toNodeOutcomeDTO as B}from"../map.js";import{reportsForNode as k}from"./reports.js";const L=15e3,$=6e4,w=250,P=512,R=256*1024,j=1e3,D=32*1024*1024;class G{static{o(this,"NodeEventRing")}events=[];bytes=0;nextSequence=1;earliestAvailable=1;append(e){const t={...e,sequence_number:this.nextSequence++},s=Buffer.byteLength(JSON.stringify(t),"utf8");if(s>R)return this.events=[],this.bytes=0,this.earliestAvailable=this.nextSequence,t;for(this.events.push(t),this.bytes+=s;this.events.length>P||this.bytes>R;){const r=this.events.shift();if(r===void 0)break;this.bytes-=Buffer.byteLength(JSON.stringify(r),"utf8"),this.earliestAvailable=r.sequence_number+1}return t}replay(e){if(e===void 0)return{gap:null,events:this.events};const t=e<this.earliestAvailable-1||e>=this.nextSequence?N(this.earliestAvailable):null;return{gap:t,events:t===null?this.events.filter(s=>s.sequence_number>e):this.events}}get empty(){return this.events.length===0}}function N(n){return{type:"error",error:{code:"stream_gap",message:"the requested event cursor is older than this daemon can resume",details:{earliest_sequence:n}}}}o(N,"streamGap");function V(n){return n===null?"arguments: null":Array.isArray(n)?`arguments: array (${n.length} items)`:typeof n=="object"?`arguments: object (${Object.keys(n).length} fields)`:`arguments: ${typeof n}`}o(V,"toolSummary");function I(n){if(typeof n!="object"||n===null)return"";const e=n.content;return typeof e=="string"?e:Array.isArray(e)?e.filter(t=>typeof t=="object"&&t!==null).filter(t=>t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(""):""}o(I,"assistantText");function H(n,e,t=new Map){switch(e.type){case"message_update":return e.message.role==="assistant"&&e.assistantMessageEvent.type==="text_delta"?[{type:"node.output_text.delta",node_id:n,delta:e.assistantMessageEvent.delta}]:[];case"message_end":return e.message.role==="assistant"?[{type:"node.output_text.done",node_id:n,text:I(e.message)}]:[];case"tool_execution_start":{const s=V(e.args);return t.set(e.toolCallId,s),[{type:"node.tool_call.started",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,summary:s}]}case"tool_execution_end":{const s=t.get(e.toolCallId)??"[arguments unavailable]";return t.delete(e.toolCallId),[{type:"node.tool_call.completed",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,status:e.isError?"error":"ok",summary:s}]}case"turn_start":return[{type:"node.turn.started",node_id:n}];case"turn_end":return[{type:"node.turn.completed",node_id:n}];default:return[]}}o(H,"translateBrokerFrame");class z{static{o(this,"NodeEventHub")}nodeId;remove;ring=new G;subscribers=new Set;observer;observerErrorAt;poll;reconnect;idleClose;status;settledRevision;settledOutcome;settledSequence;snapshotSeeded=!1;reportsSeeded=!1;toolSummaries=new Map;fresh=!0;constructor(e,t){this.nodeId=e,this.remove=t}subscribe(e,t){const s=this.fresh&&e!==void 0;this.fresh=!1,this.start();const r=this.settledOutcome!==void 0,d=s?{gap:N(1),events:this.ring.replay(void 0).events}:this.ring.replay(e);r||(this.subscribers.add(t),this.status!=="dormant"&&this.openObserver());let h=!1;return queueMicrotask(()=>{if(!h){d.gap!==null&&t(d.gap);for(const l of d.events)t(l)}}),r&&this.scheduleIdleClose(),{unsubscribe:o(()=>{h=!0,r||this.unsubscribe(t)},"unsubscribe"),terminal:r}}report(e){this.settledRevision===void 0&&this.publish({type:"node.report.pushed",node_id:this.nodeId,report:e})}start(){this.idleClose!==void 0&&(clearTimeout(this.idleClose),this.idleClose=void 0),this.poll===void 0&&(this.check(),this.settledRevision===void 0&&(this.seedReports(),this.poll=setInterval(()=>this.check(),w)))}seedReports(){if(!this.reportsSeeded){this.reportsSeeded=!0;for(const e of k(this.nodeId).reverse())this.report(e)}}check(){const e=b(this.nodeId);if(e===null){this.fail("stream_error",`node ${this.nodeId} no longer exists`);return}const t=B(e);if(t!==null){this.settledRevision!==t.revision&&(this.settledRevision=t.revision,this.settledOutcome=t,this.settledSequence=this.publish({type:"node.settled",node_id:this.nodeId,outcome:t}).sequence_number,this.stopObserver(),this.stopPoll());return}const s=e.pi_pid===null?"dormant":e.status;if(s==="dormant"){this.status!=="dormant"&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s}));return}this.status===void 0?this.status=s:this.status!=="dormant"&&this.status!==s&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s})),this.openObserver()}openObserver(){if(this.observer!==void 0||this.subscribers.size===0)return;const e=new C(this.nodeId);this.observer=e,e.on("connect",()=>{this.observerErrorAt=void 0;const t=b(this.nodeId);this.status==="dormant"&&t!==null&&t.pi_pid!==null&&(this.status=t.status,this.publish({type:"node.status.changed",node_id:this.nodeId,status:t.status})),e.send({type:"hello",role:"observer",client_id:T(),attends:!1})}),e.on("frame",t=>{try{t.type==="welcome"&&this.seedSnapshot(t.snapshot);for(const s of H(this.nodeId,t,this.toolSummaries))this.publish(s)}catch(s){this.fail("stream_error",s instanceof Error?s.message:String(s))}}),e.on("error",()=>{const t=b(this.nodeId),s=Date.now();t!==null&&t.pi_pid!==null&&A(t.pi_pid)&&(this.observerErrorAt??=s,s-this.observerErrorAt<M)||this.fail("stream_error","observer connection failed")}),e.on("close",()=>{this.observer===e&&(this.observer=void 0,!(this.subscribers.size===0||this.settledRevision!==void 0)&&(this.status!=="dormant"&&(this.publish({type:"node.status.changed",node_id:this.nodeId,status:"dormant"}),this.status="dormant"),this.scheduleReconnect()))}),e.connect()}seedSnapshot(e){if(this.snapshotSeeded)return;this.snapshotSeeded=!0;const t=e.messages.filter(r=>r.role==="assistant"),s=e.state.isStreaming?t.pop():void 0;for(const r of t)this.publish({type:"node.output_text.done",node_id:this.nodeId,text:I(r)});s!==void 0&&this.publish({type:"node.output_text.delta",node_id:this.nodeId,delta:I(s)})}scheduleReconnect(){this.reconnect===void 0&&(this.reconnect=setTimeout(()=>{this.reconnect=void 0,this.check()},w))}publish(e){const t=this.ring.append(e);for(const s of[...this.subscribers])s(t);return t}fail(e,t){const s={type:"error",error:{code:e,message:t}};for(const r of[...this.subscribers])r(s);this.stopObserver(),this.stopPoll()}unsubscribe(e){this.subscribers.delete(e),this.subscribers.size===0&&this.scheduleIdleClose()}scheduleIdleClose(){this.idleClose===void 0&&(this.idleClose=setTimeout(()=>{this.stopObserver(),this.stopPoll(),this.remove()},$))}stopObserver(){this.reconnect!==void 0&&(clearTimeout(this.reconnect),this.reconnect=void 0);const e=this.observer;this.observer=void 0,e?.close()}stopPoll(){this.poll!==void 0&&(clearInterval(this.poll),this.poll=void 0)}}class J{static{o(this,"NodeEventStreams")}hubs=new Map;subscribe(e,t,s){let r=this.hubs.get(e);return r===void 0&&(r=new z(e,()=>this.hubs.delete(e)),this.hubs.set(e,r)),r.subscribe(t,s)}report(e,t){this.hubs.get(e)?.report(t)}}const F=new J;function K(n){const e=n.get("after");if(e===null||e==="")return{};if(!/^(?:0|[1-9]\d*)$/.test(e))throw new E(400,"invalid_request","`after` must be a non-negative integer");const t=Number(e);if(!Number.isSafeInteger(t))throw new E(400,"invalid_request","`after` must be a safe integer");return{after:t}}o(K,"parseQuery");function S(n){const{type:e,...t}=n;return`event: ${e}
1
+ var O=Object.defineProperty;var o=(n,e)=>O(n,"name",{value:e,configurable:!0});import{randomUUID as q}from"node:crypto";import{ApiError as E}from"../../../api/errors.js";import{BrokerClient as C}from"../../../core/broker-client/client.js";import{getNode as b}from"../../../core/canvas/canvas.js";import{isPidAlive as B}from"../../../core/canvas/pid.js";import{BROKER_VIEW_SOCKET_WAIT_MS as k}from"../../../core/runtime/view-socket.js";import{toNodeOutcomeDTO as M}from"../map.js";import{reportsForNode as L}from"./reports.js";const D=15e3,$=6e4,x=250,P=512,T=256*1024,j=1e3,G=32*1024*1024;class V{static{o(this,"NodeEventRing")}events=[];bytes=0;nextSequence=1;earliestAvailable=1;append(e){const t={...e,sequence_number:this.nextSequence++},s=Buffer.byteLength(JSON.stringify(t),"utf8");if(s>T)return this.events=[],this.bytes=0,this.earliestAvailable=this.nextSequence,t;for(this.events.push(t),this.bytes+=s;this.events.length>P||this.bytes>T;){const r=this.events.shift();if(r===void 0)break;this.bytes-=Buffer.byteLength(JSON.stringify(r),"utf8"),this.earliestAvailable=r.sequence_number+1}return t}replay(e){if(e===void 0)return{gap:null,events:this.events};const t=e<this.earliestAvailable-1||e>=this.nextSequence?A(this.earliestAvailable):null;return{gap:t,events:t===null?this.events.filter(s=>s.sequence_number>e):this.events}}get empty(){return this.events.length===0}}function A(n){return{type:"error",error:{code:"stream_gap",message:"the requested event cursor is older than this daemon can resume",details:{earliest_sequence:n}}}}o(A,"streamGap");function H(n){return n===null?"arguments: null":Array.isArray(n)?`arguments: array (${n.length} items)`:typeof n=="object"?`arguments: object (${Object.keys(n).length} fields)`:`arguments: ${typeof n}`}o(H,"toolSummary");function m(n){if(typeof n!="object"||n===null)return"";const e=n.content;return typeof e=="string"?e:Array.isArray(e)?e.filter(t=>typeof t=="object"&&t!==null).filter(t=>t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(""):""}o(m,"assistantText");class w{static{o(this,"NodeEventTranslationState")}toolSummaries=new Map;emittedAssistantText="";resetAssistantText(){this.emittedAssistantText=""}emitAssistantTextDelta(e){const t=m(e),s=t.startsWith(this.emittedAssistantText)?t.slice(this.emittedAssistantText.length):t;return this.emittedAssistantText=t,s}seedAssistantText(e){const t=m(e);return this.emittedAssistantText=t,t}}function z(n,e,t=new w){switch(e.type){case"message_start":return e.message.role==="assistant"&&t.resetAssistantText(),[];case"message_update":{if(e.message.role!=="assistant")return[];const s=t.emitAssistantTextDelta(e.message);return s===""?[]:[{type:"node.output_text.delta",node_id:n,delta:s}]}case"message_end":{if(e.message.role!=="assistant")return[];const s=m(e.message);return t.resetAssistantText(),[{type:"node.output_text.done",node_id:n,text:s}]}case"tool_execution_start":{const s=H(e.args);return t.toolSummaries.set(e.toolCallId,s),[{type:"node.tool_call.started",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,summary:s}]}case"tool_execution_end":{const s=t.toolSummaries.get(e.toolCallId)??"[arguments unavailable]";return t.toolSummaries.delete(e.toolCallId),[{type:"node.tool_call.completed",node_id:n,tool_call_id:e.toolCallId,tool:e.toolName,status:e.isError?"error":"ok",summary:s}]}case"turn_start":return[{type:"node.turn.started",node_id:n}];case"turn_end":return[{type:"node.turn.completed",node_id:n}];default:return[]}}o(z,"translateBrokerFrame");class J{static{o(this,"NodeEventHub")}nodeId;remove;ring=new V;subscribers=new Set;observer;observerErrorAt;poll;reconnect;idleClose;status;settledRevision;settledOutcome;settledSequence;snapshotSeeded=!1;reportsSeeded=!1;translationState=new w;fresh=!0;constructor(e,t){this.nodeId=e,this.remove=t}subscribe(e,t){const s=this.fresh&&e!==void 0;this.fresh=!1,this.start();const r=this.settledOutcome!==void 0,d=s?{gap:A(1),events:this.ring.replay(void 0).events}:this.ring.replay(e);r||(this.subscribers.add(t),this.status!=="dormant"&&this.openObserver());let h=!1;return queueMicrotask(()=>{if(!h){d.gap!==null&&t(d.gap);for(const l of d.events)t(l)}}),r&&this.scheduleIdleClose(),{unsubscribe:o(()=>{h=!0,r||this.unsubscribe(t)},"unsubscribe"),terminal:r}}report(e){this.settledRevision===void 0&&this.publish({type:"node.report.pushed",node_id:this.nodeId,report:e})}start(){this.idleClose!==void 0&&(clearTimeout(this.idleClose),this.idleClose=void 0),this.poll===void 0&&(this.check(),this.settledRevision===void 0&&(this.seedReports(),this.poll=setInterval(()=>this.check(),x)))}seedReports(){if(!this.reportsSeeded){this.reportsSeeded=!0;for(const e of L(this.nodeId).reverse())this.report(e)}}check(){const e=b(this.nodeId);if(e===null){this.fail("stream_error",`node ${this.nodeId} no longer exists`);return}const t=M(e);if(t!==null){this.settledRevision!==t.revision&&(this.settledRevision=t.revision,this.settledOutcome=t,this.settledSequence=this.publish({type:"node.settled",node_id:this.nodeId,outcome:t}).sequence_number,this.stopObserver(),this.stopPoll());return}const s=e.pi_pid===null?"dormant":e.status;if(s==="dormant"){this.status!=="dormant"&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s}));return}this.status===void 0?this.status=s:this.status!=="dormant"&&this.status!==s&&(this.status=s,this.publish({type:"node.status.changed",node_id:this.nodeId,status:s})),this.openObserver()}openObserver(){if(this.observer!==void 0||this.subscribers.size===0)return;const e=new C(this.nodeId);this.observer=e,e.on("connect",()=>{this.observerErrorAt=void 0;const t=b(this.nodeId);this.status==="dormant"&&t!==null&&t.pi_pid!==null&&(this.status=t.status,this.publish({type:"node.status.changed",node_id:this.nodeId,status:t.status})),e.send({type:"hello",role:"observer",client_id:q(),attends:!1})}),e.on("frame",t=>{try{t.type==="welcome"&&this.seedSnapshot(t.snapshot);for(const s of z(this.nodeId,t,this.translationState))this.publish(s)}catch(s){this.fail("stream_error",s instanceof Error?s.message:String(s))}}),e.on("error",()=>{const t=b(this.nodeId),s=Date.now();t!==null&&t.pi_pid!==null&&B(t.pi_pid)&&(this.observerErrorAt??=s,s-this.observerErrorAt<k)||this.fail("stream_error","observer connection failed")}),e.on("close",()=>{this.observer===e&&(this.observer=void 0,!(this.subscribers.size===0||this.settledRevision!==void 0)&&(this.status!=="dormant"&&(this.publish({type:"node.status.changed",node_id:this.nodeId,status:"dormant"}),this.status="dormant"),this.scheduleReconnect()))}),e.connect()}seedSnapshot(e){if(this.snapshotSeeded)return;this.snapshotSeeded=!0;const t=e.messages.filter(r=>r.role==="assistant"),s=e.state.isStreaming?t.pop():void 0;for(const r of t)this.publish({type:"node.output_text.done",node_id:this.nodeId,text:m(r)});s!==void 0&&this.publish({type:"node.output_text.delta",node_id:this.nodeId,delta:this.translationState.seedAssistantText(s)})}scheduleReconnect(){this.reconnect===void 0&&(this.reconnect=setTimeout(()=>{this.reconnect=void 0,this.check()},x))}publish(e){const t=this.ring.append(e);for(const s of[...this.subscribers])s(t);return t}fail(e,t){const s={type:"error",error:{code:e,message:t}};for(const r of[...this.subscribers])r(s);this.stopObserver(),this.stopPoll()}unsubscribe(e){this.subscribers.delete(e),this.subscribers.size===0&&this.scheduleIdleClose()}scheduleIdleClose(){this.idleClose===void 0&&(this.idleClose=setTimeout(()=>{this.stopObserver(),this.stopPoll(),this.remove()},$))}stopObserver(){this.reconnect!==void 0&&(clearTimeout(this.reconnect),this.reconnect=void 0);const e=this.observer;this.observer=void 0,e?.close()}stopPoll(){this.poll!==void 0&&(clearInterval(this.poll),this.poll=void 0)}}class W{static{o(this,"NodeEventStreams")}hubs=new Map;subscribe(e,t,s){let r=this.hubs.get(e);return r===void 0&&(r=new J(e,()=>this.hubs.delete(e)),this.hubs.set(e,r)),r.subscribe(t,s)}report(e,t){this.hubs.get(e)?.report(t)}}const F=new W;function K(n){const e=n.get("after");if(e===null||e==="")return{};if(!/^(?:0|[1-9]\d*)$/.test(e))throw new E(400,"invalid_request","`after` must be a non-negative integer");const t=Number(e);if(!Number.isSafeInteger(t))throw new E(400,"invalid_request","`after` must be a safe integer");return{after:t}}o(K,"parseQuery");function I(n){const{type:e,...t}=n;return`event: ${e}
2
2
  data: ${JSON.stringify(t)}
3
3
 
4
- `}o(S,"sseRecord");async function U(n){const e=n.params.id??"";if(b(e)===null)throw new E(404,"node_not_found",`no node ${e}`);const{after:t}=K(n.query),{req:s,res:r}=n;r.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"});let d=!1,h=!1,l=!1,c=0;const u=[];let m,f;const v=o(()=>{r.writableEnded||r.end()},"finish"),p=o(()=>{if(!d){if(d=!0,f!==void 0&&clearInterval(f),m?.(),s.destroyed||r.destroyed){u.length=0,c=0,v();return}if(l||u.length>0){h=!0;return}v()}},"close"),_=o(()=>{for(l=!1;u.length>0;){const i=u.shift();if(c-=Buffer.byteLength(i,"utf8"),!r.write(i)){l=!0,r.once("drain",_);return}}h&&v()},"flush"),O=o(()=>{if(d)return;u.length=0,c=0;const i=S({type:"error",error:{code:"stream_dropped",message:"stream reader is too slow"}});u.push(i),c=Buffer.byteLength(i,"utf8"),d=!0,h=!0,f!==void 0&&clearInterval(f),m?.(),l||_()},"dropSlowReader"),y=o(i=>{if(d)return!1;if(l||u.length>0){const a=Buffer.byteLength(i,"utf8");return u.length>=j||c+a>D?(O(),!1):(u.push(i),c+=a,!0)}return r.write(i)||(l=!0,r.once("drain",_)),!0},"write"),g=o((i,a)=>{d||y(S({type:"error",error:{code:i,message:a}})),p()},"fail"),x=o(i=>{try{if(!y(S(i)))return;(i.type==="node.settled"||i.type==="error"&&i.error.code!=="stream_gap")&&p()}catch(a){g("stream_error",a instanceof Error?a.message:String(a))}},"onEvent");f=setInterval(()=>{try{d||y(`: keepalive
4
+ `}o(I,"sseRecord");async function U(n){const e=n.params.id??"";if(b(e)===null)throw new E(404,"node_not_found",`no node ${e}`);const{after:t}=K(n.query),{req:s,res:r}=n;r.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"});let d=!1,h=!1,l=!1,c=0;const u=[];let v,f;const _=o(()=>{r.writableEnded||r.end()},"finish"),p=o(()=>{if(!d){if(d=!0,f!==void 0&&clearInterval(f),v?.(),s.destroyed||r.destroyed){u.length=0,c=0,_();return}if(l||u.length>0){h=!0;return}_()}},"close"),g=o(()=>{for(l=!1;u.length>0;){const i=u.shift();if(c-=Buffer.byteLength(i,"utf8"),!r.write(i)){l=!0,r.once("drain",g);return}}h&&_()},"flush"),N=o(()=>{if(d)return;u.length=0,c=0;const i=I({type:"error",error:{code:"stream_dropped",message:"stream reader is too slow"}});u.push(i),c=Buffer.byteLength(i,"utf8"),d=!0,h=!0,f!==void 0&&clearInterval(f),v?.(),l||g()},"dropSlowReader"),y=o(i=>{if(d)return!1;if(l||u.length>0){const a=Buffer.byteLength(i,"utf8");return u.length>=j||c+a>G?(N(),!1):(u.push(i),c+=a,!0)}return r.write(i)||(l=!0,r.once("drain",g)),!0},"write"),S=o((i,a)=>{d||y(I({type:"error",error:{code:i,message:a}})),p()},"fail"),R=o(i=>{try{if(!y(I(i)))return;(i.type==="node.settled"||i.type==="error"&&i.error.code!=="stream_gap")&&p()}catch(a){S("stream_error",a instanceof Error?a.message:String(a))}},"onEvent");f=setInterval(()=>{try{d||y(`: keepalive
5
5
 
6
- `)}catch(i){g("stream_error",i instanceof Error?i.message:String(i))}},L),s.once("close",p);try{const i=F.subscribe(e,t,x);m=i.unsubscribe,i.terminal&&queueMicrotask(p)}catch(i){g("stream_error",i instanceof Error?i.message:String(i))}return{status:200}}o(U,"handleEvents");const re=[{method:"GET",pattern:"/v1/nodes/:id/events",handler:U}];export{G as NodeEventRing,re as nodeEventRoutes,F as nodeEventStreams,H as translateBrokerFrame};
6
+ `)}catch(i){S("stream_error",i instanceof Error?i.message:String(i))}},D),s.once("close",p);try{const i=F.subscribe(e,t,R);v=i.unsubscribe,i.terminal&&queueMicrotask(p)}catch(i){S("stream_error",i instanceof Error?i.message:String(i))}return{status:200}}o(U,"handleEvents");const ie=[{method:"GET",pattern:"/v1/nodes/:id/events",handler:U}];export{V as NodeEventRing,w as NodeEventTranslationState,ie as nodeEventRoutes,F as nodeEventStreams,z as translateBrokerFrame};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.326",
3
+ "version": "0.3.328",
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.326",
3
+ "version": "0.3.328",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.326",
9
+ "version": "0.3.328",
10
10
  "hasInstallScript": true,
11
11
  "license": "GPL-3.0-only",
12
12
  "workspaces": [
@@ -4795,25 +4795,25 @@
4795
4795
  },
4796
4796
  "packages/crouter-api": {
4797
4797
  "name": "@north-light/crouter-api",
4798
- "version": "0.3.326",
4798
+ "version": "0.3.328",
4799
4799
  "license": "GPL-3.0-only"
4800
4800
  },
4801
4801
  "packages/crouter-env-docker": {
4802
4802
  "name": "@north-light/crouter-env-docker",
4803
- "version": "0.3.326",
4803
+ "version": "0.3.328",
4804
4804
  "license": "GPL-3.0-only"
4805
4805
  },
4806
4806
  "packages/crouter-plugin": {
4807
4807
  "name": "@north-light/crouter-plugin",
4808
- "version": "0.3.326",
4808
+ "version": "0.3.328",
4809
4809
  "license": "GPL-3.0-only",
4810
4810
  "dependencies": {
4811
- "@north-light/crouter-api": "^0.3.326"
4811
+ "@north-light/crouter-api": "^0.3.328"
4812
4812
  }
4813
4813
  },
4814
4814
  "packages/crouter-sdk": {
4815
4815
  "name": "@north-light/crouter-sdk",
4816
- "version": "0.3.326",
4816
+ "version": "0.3.328",
4817
4817
  "license": "GPL-3.0-only",
4818
4818
  "dependencies": {
4819
4819
  "@north-light/crouter-api": "^0.3.322"