@orcha-ai/runtime-bridge 0.1.0
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/.env.example +17 -0
- package/README.md +154 -0
- package/bin/orcha-runtime-bridge.js +27 -0
- package/package.json +34 -0
package/.env.example
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Orcha Runtime Node deployment config.
|
|
2
|
+
# Copy this file to .env and fill the values for the computer running the runtime.
|
|
3
|
+
|
|
4
|
+
ORCHA_RUNTIME_MODE=node
|
|
5
|
+
ORCHA_RUNTIME_ORCHA_URL=https://orcha.example.com
|
|
6
|
+
ORCHA_RUNTIME_NODE_TOKEN=replace-with-agent-runtime-node-service-token
|
|
7
|
+
ORCHA_RUNTIME_NODE_ID=workstation-a
|
|
8
|
+
ORCHA_RUNTIME_NODE_NAME=Workstation A
|
|
9
|
+
ORCHA_RUNTIME_DEFAULT_TARGET=codex
|
|
10
|
+
ORCHA_RUNTIME_RECONNECT_MS=3000
|
|
11
|
+
ORCHA_RUNTIME_SESSION_PROMPT_TIMEOUT_MS=3600000
|
|
12
|
+
ORCHA_RUNTIME_PERMISSION_TIMEOUT_MS=300000
|
|
13
|
+
ORCHA_RUNTIME_PID_FILE=.orcha-runtime-bridge.pid
|
|
14
|
+
ORCHA_RUNTIME_LOG_FILE=.orcha-runtime-bridge.log
|
|
15
|
+
|
|
16
|
+
# Target examples. Keep this JSON on one line.
|
|
17
|
+
ORCHA_RUNTIME_TARGETS_JSON='{"codex":{"type":"cli","command":"codex","args":["exec","--json"],"promptMode":"stdin","outputMode":"json-lines","captureLastMessage":true},"openclaw":{"type":"openclaw","command":"openclaw"},"claude":{"type":"cli","command":"claude","args":["-p","--output-format","json","--no-session-persistence"],"promptMode":"args","outputMode":"json-lines"}}'
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Orcha Runtime Bridge
|
|
2
|
+
|
|
3
|
+
Runtime Node package that keeps Codex, OpenClaw, Claude Code, and other
|
|
4
|
+
agent runtimes outside the Python API process.
|
|
5
|
+
|
|
6
|
+
The bridge owns Orcha's first-party ACP adapter. It does not depend on a
|
|
7
|
+
third-party ACP package: JSON-RPC framing, request ids, notifications,
|
|
8
|
+
runtime permission requests, and process lifecycle are implemented under
|
|
9
|
+
`src/acp/`.
|
|
10
|
+
|
|
11
|
+
In production the Runtime Node actively connects to Orcha over WebSocket. The
|
|
12
|
+
computer running Codex/OpenClaw/Claude does not need to expose an inbound port.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @orcha-ai/runtime-bridge
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run it with:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
orcha-runtime-bridge
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can also run without a global install:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @orcha-ai/runtime-bridge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Build And Pack
|
|
33
|
+
|
|
34
|
+
The bridge is plain Node.js ESM. The build step bundles and minifies the runtime
|
|
35
|
+
CLI into `bin/orcha-runtime-bridge.js`, then prepares a clean release directory.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run build
|
|
39
|
+
npm run pack:local
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`npm run build` writes `dist/package`. `npm run pack:local` creates the local
|
|
43
|
+
npm tarball under `dist/`. The release package includes the minified `bin/`
|
|
44
|
+
runtime, `README.md`, and `.env.example`; it does not publish `src/`, build
|
|
45
|
+
scripts, test scripts, or the local `.env` file.
|
|
46
|
+
|
|
47
|
+
## Configure
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cp .env.example .env
|
|
51
|
+
# Edit .env and fill ORCHA_RUNTIME_ORCHA_URL / ORCHA_RUNTIME_NODE_TOKEN / targets.
|
|
52
|
+
orcha-runtime-bridge
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Background Process
|
|
56
|
+
|
|
57
|
+
The CLI has a small built-in process manager. It does not require pm2,
|
|
58
|
+
forever, or any third-party package.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Start in the background.
|
|
62
|
+
orcha-runtime-bridge start --env-file /etc/orcha/runtime-node.env
|
|
63
|
+
|
|
64
|
+
# Check status.
|
|
65
|
+
orcha-runtime-bridge status --env-file /etc/orcha/runtime-node.env
|
|
66
|
+
|
|
67
|
+
# Reload after editing the env file or upgrading the package.
|
|
68
|
+
orcha-runtime-bridge reload --env-file /etc/orcha/runtime-node.env
|
|
69
|
+
|
|
70
|
+
# Stop.
|
|
71
|
+
orcha-runtime-bridge stop --env-file /etc/orcha/runtime-node.env
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`start` writes the process id to `ORCHA_RUNTIME_PID_FILE` and redirects stdout
|
|
75
|
+
and stderr to `ORCHA_RUNTIME_LOG_FILE`. Both paths can be set in the env file or
|
|
76
|
+
overridden on the command line:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
orcha-runtime-bridge start \
|
|
80
|
+
--env-file /etc/orcha/runtime-node.env \
|
|
81
|
+
--pid-file /var/run/orcha-runtime-bridge.pid \
|
|
82
|
+
--log-file /var/log/orcha-runtime-bridge.log
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Running `orcha-runtime-bridge` with no command still runs in the foreground,
|
|
86
|
+
which is the right mode for systemd, Docker, launchd, and supervised services.
|
|
87
|
+
|
|
88
|
+
The bridge loads environment variables from `ORCHA_RUNTIME_ENV_FILE` when it is
|
|
89
|
+
set. Otherwise it reads `.env` from the current working directory. Real process
|
|
90
|
+
environment variables always win over values from the file.
|
|
91
|
+
|
|
92
|
+
The node connects to:
|
|
93
|
+
|
|
94
|
+
- `WS /api/agent-runtime-bridge/ws`
|
|
95
|
+
|
|
96
|
+
and receives Orcha RPC frames for:
|
|
97
|
+
|
|
98
|
+
- `agents/create`
|
|
99
|
+
- `agents/dispatch`
|
|
100
|
+
- `sessions/prompt`
|
|
101
|
+
- `sessions/cancel`
|
|
102
|
+
- `permissions/respond`
|
|
103
|
+
|
|
104
|
+
Runtime targets are configured with `ORCHA_RUNTIME_TARGETS_JSON`.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"codex": {
|
|
109
|
+
"type": "cli",
|
|
110
|
+
"command": "codex",
|
|
111
|
+
"args": ["exec", "--json"],
|
|
112
|
+
"promptMode": "stdin",
|
|
113
|
+
"outputMode": "json-lines"
|
|
114
|
+
},
|
|
115
|
+
"openclaw": {
|
|
116
|
+
"type": "openclaw",
|
|
117
|
+
"command": "openclaw"
|
|
118
|
+
},
|
|
119
|
+
"custom-acp": {
|
|
120
|
+
"type": "stdio-acp",
|
|
121
|
+
"command": "node",
|
|
122
|
+
"args": ["/path/to/runtime.js"],
|
|
123
|
+
"framing": "line"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If a target is missing, the bridge uses the built-in `echo` adapter so the
|
|
129
|
+
protocol can be verified without starting a real agent runtime.
|
|
130
|
+
|
|
131
|
+
Target types:
|
|
132
|
+
|
|
133
|
+
- `stdio-acp`: spawn a runtime process and speak Orcha ACP over stdio JSON-RPC.
|
|
134
|
+
- `openclaw`: built-in `openclaw acp --session ...` command adapter.
|
|
135
|
+
- `cli`: run an independent CLI per prompt and wrap stdout as session updates.
|
|
136
|
+
- `echo`: local test adapter.
|
|
137
|
+
|
|
138
|
+
Supported stdio framing:
|
|
139
|
+
|
|
140
|
+
- `line`: one JSON-RPC frame per line.
|
|
141
|
+
- `content-length`: `Content-Length: N` headers, compatible with LSP-style stdio transports.
|
|
142
|
+
|
|
143
|
+
Local HTTP server mode is only for development smoke tests:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
ORCHA_RUNTIME_MODE=server npm start
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For systemd or Docker deployments, point `ORCHA_RUNTIME_ENV_FILE` at the file
|
|
150
|
+
managed by the host:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
ORCHA_RUNTIME_ENV_FILE=/etc/orcha/runtime-node.env npm start
|
|
154
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{spawn as re}from"node:child_process";import{closeSync as ie,existsSync as oe,mkdirSync as ae,openSync as ce,readFileSync as de,rmSync as ue,writeFileSync as le}from"node:fs";import{dirname as he,resolve as gt}from"node:path";import{fileURLToPath as pe}from"node:url";import{randomUUID as $}from"node:crypto";var M="0.1.0";function R(){return new Date().toISOString()}function d(s){return s!==null&&typeof s=="object"&&!Array.isArray(s)}function o(s){return String(s??"").trim()}function g(s,t){let e=Number.parseInt(String(s??""),10);return Number.isFinite(e)?e:t}function k(s){return Array.isArray(s)?s.map(String):[]}function h(s,t,e,n={}){let r={ts:R(),level:s,module:t,msg:e,...d(n)?n:{}};console.error(JSON.stringify(r))}function P(s){return Array.isArray(s)?s.map(t=>d(t)?t.type==="text"?o(t.text):t.type==="file"?o(t.uri):t.type==="image"?o(t.uri):"":"").filter(Boolean).join(`
|
|
3
|
+
`):""}var C=class{constructor(t){this.agent=t,this.sessionCounter=0}async sessionNew(){return this.sessionCounter+=1,{sessionId:`echo-${this.agent.agentId}-${this.sessionCounter}`}}async sessionPrompt(t,e){let n=P(t.prompt);return e({sessionId:t.sessionId,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:n?`Echo runtime received: ${n}`:"Echo runtime received an empty prompt."}}}),{stopReason:"end_turn"}}async sessionCancel(){return{ok:!0}}};import{spawn as yt}from"node:child_process";var N="2.0",Tt=3e4;function St(s){return{code:Number.isInteger(s?.code)?s.code:-32e3,message:o(s?.message)||String(s),data:d(s?.data)?s.data:void 0}}function Rt(s){return d(s)&&"id"in s&&!("method"in s)}function Ot(s){return d(s)&&typeof s.method=="string"&&"id"in s}function xt(s){return d(s)&&typeof s.method=="string"&&!("id"in s)}var L=class{constructor({command:t,args:e=[],cwd:n,env:r,label:i,framing:a="line"}){this.command=t,this.args=Array.isArray(e)?e.map(String):[],this.cwd=n||process.cwd(),this.env={...process.env,...d(r)?r:{}},this.label=i||t,this.framing=a==="content-length"?"content-length":"line",this.child=null,this.lineBuffer="",this.headerBuffer=Buffer.alloc(0),this.seq=0,this.pending=new Map,this.notificationHandlers=new Map,this.requestHandlers=new Map,this.exitHandlers=new Set}async start({initialize:t=!0,initializeParams:e={}}={}){this.child&&!this.child.killed||(this.child=yt(this.command,this.args,{cwd:this.cwd,env:this.env,stdio:["pipe","pipe","pipe"]}),this.child.stdout.on("data",n=>this.onStdout(n)),this.child.stderr.setEncoding("utf8"),this.child.stderr.on("data",n=>{let r=n.trimEnd();r&&h("debug","runtime.stderr","runtime stderr",{target:this.label,text:r})}),this.child.on("exit",(n,r)=>this.handleExit(n,r)),this.child.on("error",n=>this.rejectAll(n)),t&&await this.call("initialize",{protocolVersion:1,clientCapabilities:{},...e}).catch(n=>{throw Object.assign(new Error(`runtime initialize failed: ${n.message}`),{code:"RUNTIME_INITIALIZE_FAILED"})}))}onStdout(t){if(this.framing==="content-length"){this.onContentLengthData(Buffer.isBuffer(t)?t:Buffer.from(t));return}this.onLineData(t.toString("utf8"))}onLineData(t){this.lineBuffer+=t;let e=this.lineBuffer.split(/\r?\n/);this.lineBuffer=e.pop()||"";for(let n of e){let r=n.trim();r&&this.dispatchRaw(r)}}onContentLengthData(t){for(this.headerBuffer=Buffer.concat([this.headerBuffer,t]);this.headerBuffer.length>0;){let e=this.headerBuffer.indexOf(`\r
|
|
4
|
+
\r
|
|
5
|
+
`);if(e<0)return;let r=this.headerBuffer.subarray(0,e).toString("utf8").match(/content-length:\s*(\d+)/i);if(!r){h("warn","runtime.protocol","missing Content-Length header",{target:this.label}),this.headerBuffer=Buffer.alloc(0);return}let i=Number.parseInt(r[1],10),a=e+4,c=a+i;if(this.headerBuffer.length<c)return;let u=this.headerBuffer.subarray(a,c).toString("utf8");this.headerBuffer=this.headerBuffer.subarray(c),this.dispatchRaw(u)}}dispatchRaw(t){let e;try{e=JSON.parse(t)}catch(n){h("warn","runtime.protocol","invalid JSON frame",{target:this.label,error:String(n)});return}this.dispatch(e)}dispatch(t){if(Rt(t)){this.resolveResponse(t);return}if(Ot(t)){this.handleRequest(t);return}xt(t)&&this.handleNotification(t)}resolveResponse(t){let e=String(t.id),n=this.pending.get(e);n&&(clearTimeout(n.timer),this.pending.delete(e),t.error?n.reject(new Error(d(t.error)?String(t.error.message||"runtime error"):String(t.error))):n.resolve(t.result))}async handleRequest(t){let e=this.requestHandlers.get(t.method);if(!e){this.writeFrame({jsonrpc:N,id:t.id,error:{code:-32601,message:`method not found: ${t.method}`}});return}try{let n=await e(d(t.params)?t.params:{},t);this.writeFrame({jsonrpc:N,id:t.id,result:n??{}})}catch(n){this.writeFrame({jsonrpc:N,id:t.id,error:St(n)})}}handleNotification(t){let e=this.notificationHandlers.get(t.method);if(e)for(let n of e)n(d(t.params)?t.params:{},t)}call(t,e={},n=Tt){if(!this.child||!this.child.stdin||this.child.killed)return Promise.reject(new Error("runtime process is not running"));let r=`${t}_${++this.seq}`,i={jsonrpc:N,id:r,method:t,params:e};return new Promise((a,c)=>{let u=setTimeout(()=>{this.pending.delete(r),c(new Error(`${t} timed out after ${n}ms`))},n);this.pending.set(r,{resolve:a,reject:c,timer:u}),this.writeFrame(i,l=>{l&&(clearTimeout(u),this.pending.delete(r),c(l))})})}notify(t,e={}){this.writeFrame({jsonrpc:N,method:t,params:e})}writeFrame(t,e){if(!this.child?.stdin||this.child.killed){e?.(new Error("runtime process is not running"));return}let n=JSON.stringify(t),r=this.framing==="content-length"?`Content-Length: ${Buffer.byteLength(n,"utf8")}\r
|
|
6
|
+
\r
|
|
7
|
+
${n}`:`${n}
|
|
8
|
+
`;this.child.stdin.write(r,e)}onNotification(t,e){let n=this.notificationHandlers.get(t)||new Set;return n.add(e),this.notificationHandlers.set(t,n),()=>{n.delete(e),n.size===0&&this.notificationHandlers.delete(t)}}onRequest(t,e){return this.requestHandlers.set(t,e),()=>this.requestHandlers.delete(t)}onExit(t){return this.exitHandlers.add(t),()=>this.exitHandlers.delete(t)}rejectAll(t){for(let e of this.pending.values())clearTimeout(e.timer),e.reject(t);this.pending.clear()}handleExit(t,e){this.rejectAll(new Error(`runtime exited before response (${t}/${e})`));for(let n of this.exitHandlers)n(t,e)}stop(t="stop"){!this.child||this.child.killed||(h("info","runtime","stopping runtime",{target:this.label,reason:t}),this.child.kill("SIGTERM"),setTimeout(()=>{this.child&&!this.child.killed&&this.child.kill("SIGKILL")},5e3).unref())}};var O=class{constructor(t,e,n={},r={}){this.agent=t,this.target=e,this.callbacks=n,this.options=r,this.proc=null,this.permissionHandlers=new Map}async ensureStarted(){if(this.proc)return;let t=o(this.target.command);if(!t)throw new Error(`runtime target ${this.agent.targetCode} is missing command`);this.proc=new L({command:t,args:k(this.target.args),cwd:o(this.target.cwd)||this.agent.workspace||process.cwd(),env:d(this.target.env)?this.target.env:{},label:`${this.agent.agentId}:${this.agent.targetCode}`,framing:o(this.target.framing||"line")}),this.proc.onRequest("session/request_permission",e=>this.handlePermissionRequest(e)),this.proc.onRequest("session/requestPermission",e=>this.handlePermissionRequest(e)),await this.proc.start({initialize:this.target.initialize!==!1,initializeParams:{clientInfo:{name:"orcha-runtime-bridge",version:"0.1.0"},...d(this.target.initializeParams)?this.target.initializeParams:{}}})}async sessionNew(t={}){return await this.ensureStarted(),this.proc.call("session/new",t)}async sessionPrompt(t,e){await this.ensureStarted();let n=o(t.sessionId),r=this.proc.onNotification("session/update",a=>{let c=o(a.sessionId);(!c||c===n)&&e(a)}),i=async a=>this.callbacks.onPermissionRequest?.({agent:this.agent,runtimeSessionId:n,request:a,emitUpdate:e})||{outcome:"denied",reason:"permission handler is not configured"};n&&this.permissionHandlers.set(n,i),this.permissionHandlers.set("*",i);try{return await this.proc.call("session/prompt",t,this.options.sessionPromptTimeoutMs)}finally{r(),n&&this.permissionHandlers.delete(n),this.permissionHandlers.delete("*")}}async sessionCancel(t){return await this.ensureStarted(),this.proc.call("session/cancel",t,5e3).catch(()=>({ok:!0}))}async handlePermissionRequest(t){let e=o(t.sessionId),n=this.permissionHandlers.get(e)||this.permissionHandlers.get("*");return n?n(t):{outcome:"denied",reason:"no active Orcha permission handler"}}stop(t){this.proc?.stop(t)}};var v=class extends O{constructor(t,e,n,r={},i={}){let a=o(n).replace(/[^A-Za-z0-9._-]+/g,"-")||"default",c=o(e.command)||"openclaw",u=Array.isArray(e.args)?e.args.map(String):[],l=u.length>0?u:["acp","--session",`agent:${t.agentId}:${a}`];super(t,{...e,command:c,args:l},r,i)}};import{spawn as Nt}from"node:child_process";import{randomUUID as F}from"node:crypto";import{existsSync as Ut,readFileSync as bt,unlinkSync as At}from"node:fs";import{tmpdir as Mt}from"node:os";import{basename as nt,extname as kt,join as Pt}from"node:path";var Ct=36e5,Lt=5e3;function H(s,t){try{s.kill(t)}catch{}}function w(s){if(typeof s=="string")return s;if(Array.isArray(s))return s.map(t=>w(t)).join("");if(!d(s))return"";for(let t of["text","delta","output_text","content","message","output"]){let e=w(s[t]);if(e)return e}return""}function vt(s){if(!d(s))return"";let t=d(s.payload)?s.payload:{};if(o(s.type)==="event_msg"){if(o(t.type)==="agent_message")return o(t.message);if(o(t.type)==="task_complete")return o(t.last_agent_message)}if(o(s.type)==="response_item"&&o(t.type)==="message"&&o(t.role)==="assistant")return w(t.content)||w(t.message);let e=w(s.delta)||w(s.text)||w(s.content);if(e)return e;let n=d(s.item)?s.item:{};return o(n.role)==="assistant"||o(n.type)==="message"?w(n.content)||w(n.text)||w(n.message):""}function Ht(s){let t=kt(o(s)).toLowerCase();return t===".jpg"||t===".jpeg"?"image/jpeg":t===".webp"?"image/webp":t===".gif"?"image/gif":"image/png"}function Ft(s,t){let e=typeof s=="string"?s.trim():"";return e?e.startsWith("data:")?e:`data:${t};base64,${e.replace(/\s+/g,"")}`:""}function Dt(s){if(!d(s))return null;let t=d(s.payload)?s.payload:{},e=o(s.type),n=o(t.type);if(!(e==="event_msg"&&n==="image_generation_end"||e==="response_item"&&n==="image_generation_call"))return null;let r=o(t.call_id||t.id),i=o(t.saved_path||t.savedPath),a=o(t.revised_prompt||t.revisedPrompt),c=Ht(i),l=Ft(t.result,c)||i;if(!l)return null;let f=i?nt(i):`${r||F()}.png`;return{artifactType:"image",type:"image",title:f||"Generated image",summary:a||"Generated image",content:{prompt:a||void 0,imageUrl:l,previewUrl:l,fileUrl:l,mimeType:c},previewUrl:l,fileUrl:l,metadata:{source:"codex_cli_image_generation",eventType:n,callId:r||void 0,savedPath:i||void 0,filename:f,mimeType:c,revisedPrompt:a||void 0}}}function $t(s,t){if(!t)return;let e=d(t.metadata)?t.metadata:{},n=o(e.callId||e.savedPath||t.previewUrl||t.fileUrl);if(!n)return;let r=s.artifacts.get(n);if(!r){s.artifacts.set(n,t);return}r.content={...d(r.content)?r.content:{},...d(t.content)?t.content:{}},r.metadata={...d(r.metadata)?r.metadata:{},...e},r.previewUrl=t.previewUrl||r.previewUrl,r.fileUrl=t.fileUrl||r.fileUrl,r.summary=t.summary||r.summary,r.title=t.title||r.title}function Bt(s){if(!d(s)||!d(s.payload))return d(s)?s:{value:s};let t=o(s.payload.type);if(t!=="image_generation_end"&&t!=="image_generation_call")return s;let e={...s.payload};return typeof e.result=="string"&&e.result&&(e.result="<omitted>",e.resultLength=s.payload.result.length),{...s,payload:e}}function jt(s,t,e){return e.captureLastMessage===!1||t.includes("--output-last-message")||t.includes("-o")?!1:e.captureLastMessage===!0?!0:nt(s)==="codex"}function qt(s){if(!s||!Ut(s))return"";try{return bt(s,"utf8").trim()}catch{return""}finally{try{At(s)}catch{}}}var D=class{constructor(t,e,n={}){this.agent=t,this.target=e,this.options=n,this.active=new Map}async sessionNew(){return{sessionId:`cli-${this.agent.agentId}-${F()}`}}async sessionPrompt(t,e){let n=o(this.target.command);if(!n)throw new Error(`runtime target ${this.agent.targetCode} is missing command`);let r=o(t.sessionId)||F(),i=P(t.prompt),a=o(this.target.promptMode||"stdin"),c=this.buildArgs(a,i),u=jt(n,c,this.target)?Pt(Mt(),`orcha-runtime-last-message-${F()}.txt`):"";u&&c.push("--output-last-message",u);let l=g(this.target.timeoutMs||this.target.sessionPromptTimeoutMs,g(this.options.sessionPromptTimeoutMs,Ct)),f={...process.env,...d(this.target.env)?this.target.env:{},...a==="env"?{ORCHA_RUNTIME_PROMPT:i}:{},ORCHA_AGENT_ID:this.agent.agentId,ORCHA_SESSION_ID:r},p=Nt(n,c,{cwd:o(this.target.cwd)||this.agent.workspace||process.cwd(),env:f,stdio:["pipe","pipe","pipe"]});this.active.set(r,p),h("info","runtime.cli","runtime prompt started",{target:this.agent.targetCode,sessionId:r,pid:p.pid,timeoutMs:l});let I="",_=o(this.target.outputMode||"text"),T={artifacts:new Map,emittedTexts:new Set,value:""};return p.stdout.setEncoding("utf8"),p.stdout.on("data",S=>this.handleStdout(S,_,T,r,e)),p.stderr.setEncoding("utf8"),p.stderr.on("data",S=>{I+=S,this.target.emitStderr===!0&&e({sessionId:r,update:{sessionUpdate:"runtime_stderr_chunk",content:{type:"text",text:S}}})}),a==="stdin"?p.stdin.end(i):p.stdin.end(),await new Promise((S,_t)=>{let U=!1,Y=!1,E=null,X=setTimeout(()=>{Y=!0,h("warn","runtime.cli","runtime prompt timed out; terminating child",{target:this.agent.targetCode,sessionId:r,pid:p.pid,timeoutMs:l}),H(p,"SIGTERM"),E=setTimeout(()=>{h("warn","runtime.cli","runtime prompt force killing child",{target:this.agent.targetCode,sessionId:r,pid:p.pid}),H(p,"SIGKILL")},Lt),E.unref?.(),et(Object.assign(new Error(`runtime CLI timed out after ${l}ms`),{code:"RUNTIME_CLI_TIMEOUT"}))},l);X.unref?.();let tt=()=>{clearTimeout(X),E&&!Y&&clearTimeout(E),this.active.delete(r)},Et=m=>{U||(U=!0,tt(),S(m))},et=m=>{U||(U=!0,tt(),_t(m))};p.on("error",m=>{h("warn","runtime.cli","runtime child error",{target:this.agent.targetCode,sessionId:r,pid:p.pid,error:String(m?.message||m)}),et(m)}),p.on("exit",(m,st)=>{E&&(clearTimeout(E),E=null),_==="json-lines"&&T.value.trim()&&this.handleStdout(`
|
|
9
|
+
`,_,T,r,e);let b=qt(u);b&&!T.emittedTexts.has(b)&&(T.emittedTexts.add(b),e({sessionId:r,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:b}}}));let A=I.trim();A&&h("debug","runtime.cli","runtime stderr",{target:this.agent.targetCode,sessionId:r,stderr:A}),h("info","runtime.cli","runtime prompt finished",{target:this.agent.targetCode,sessionId:r,pid:p.pid,exitCode:m,signal:st}),Et({stopReason:m===0?"end_turn":"error",exitCode:m,signal:st,artifacts:[...T.artifacts.values()],...A?{stderr:A}:{}})})})}buildArgs(t,e){let n=k(this.target.args);return t==="args"?[...n,e]:n}handleStdout(t,e,n,r,i){if(e!=="json-lines"){i({sessionId:r,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:t}}});return}n.value+=t;let a=n.value.split(/\r?\n/);n.value=a.pop()||"";for(let c of a){let u=c.trim();if(u)try{let l=JSON.parse(u);$t(n,Dt(l)),i({sessionId:r,update:{sessionUpdate:"runtime_event",event:Bt(l)}});let f=vt(l);f&&!n.emittedTexts.has(f)&&(n.emittedTexts.add(f),i({sessionId:r,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:f}}}))}catch{i({sessionId:r,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`${c}
|
|
10
|
+
`}}})}}}async sessionCancel(t){let e=o(t.sessionId),n=this.active.get(e);return n?(h("info","runtime.cli","cancelling runtime child",{target:this.agent.targetCode,sessionId:e,pid:n.pid}),H(n,"SIGTERM"),this.active.delete(e),{ok:!0,cancelled:!0}):{ok:!0,cancelled:!1}}stop(t="stop"){for(let[e,n]of this.active.entries())h("info","runtime.cli","stopping runtime child",{target:this.agent.targetCode,sessionId:e,pid:n.pid,reason:t}),H(n,"SIGTERM");this.active.clear()}};function Gt(s,t,e){let n=d(s?._meta)?s._meta:{};return{...d(s)?s:{},sessionId:t,update:d(s?.update)?s.update:{},_meta:{...n,orchaAgentId:e.agentId,runtimeSessionId:e.runtimeSessionId,bridgeUpdateId:$()}}}var B=class{constructor(t){this.config=t,this.targets=t.targets||{},this.agents=new Map,this.sessions=new Map,this.runtimeSessionIndex=new Map,this.pendingPermissions=new Map,this.startedAt=R()}targetFor(t){let e=o(t)||this.config.defaultTargetCode,n=this.targets[e];return{code:e,target:d(n)?n:{type:"echo"}}}async createAgent(t){let e=o(t.agentId);if(!e)throw Object.assign(new Error("agentId is required"),{status:400,code:"BAD_REQUEST"});let{code:n,target:r}=this.targetFor(t.targetCode||t.framework),i={agentId:e,targetCode:n,framework:o(t.framework||n),workspace:o(t.workspace),metadata:d(t.metadata)?t.metadata:{},createdAt:R(),runtimeKind:o(r.type||"echo")};return this.agents.set(e,i),{agent:i}}runtimeFor(t,e){let{target:n}=this.targetFor(t.targetCode),r=o(n.type||"echo"),i={onPermissionRequest:c=>this.requestPermission(c)},a={sessionPromptTimeoutMs:this.config.sessionPromptTimeoutMs};return r==="stdio-acp"||r==="acp"?new O(t,n,i,a):r==="openclaw"?new v(t,n,e,i,a):r==="cli"?new D(t,n,a):new C(t)}async prompt(t,e={}){let n=o(t.agentId),r=o(t.sessionId)||$();if(!n)throw Object.assign(new Error("agentId is required"),{status:400,code:"BAD_REQUEST"});let i=this.agents.get(n)||(await this.createAgent({agentId:n,targetCode:t.targetCode,framework:t.framework,workspace:t.workspace,metadata:t.metadata})).agent,a=o(t.workspace);a&&a!==i.workspace&&(i.workspace=a);let c=`${n}:${r}`,u=this.sessions.get(c);if(!u){let I=this.runtimeFor(i,r),_=await I.sessionNew({cwd:i.workspace||process.cwd(),metadata:d(t.metadata)?t.metadata:{}});u={agentId:n,sessionId:r,runtime:I,runtimeSessionId:o(d(_)?_.sessionId:"")||r,createdAt:R()},this.sessions.set(c,u),this.runtimeSessionIndex.set(`${n}:${u.runtimeSessionId}`,u)}let l=[],f=I=>{let _=Gt(I,r,u);l.push(_),e.onUpdate?.(_)},p=await u.runtime.sessionPrompt({sessionId:u.runtimeSessionId,prompt:Array.isArray(t.prompt)?t.prompt:[],metadata:d(t.metadata)?t.metadata:{}},f);return{sessionId:r,runtimeSessionId:u.runtimeSessionId,result:d(p)?p:{},updates:l}}async cancel(t){let e=o(t.agentId),n=o(t.sessionId),r=this.sessions.get(`${e}:${n}`);return r?(await r.runtime.sessionCancel({sessionId:r.runtimeSessionId}),{cancelled:!0}):{cancelled:!1}}async dispatch(t,e={}){let n=o(t.agentId),r=o(t.taskId),i=o(t.executionId);if(!n||!r)throw Object.assign(new Error("agentId and taskId are required"),{status:400,code:"BAD_REQUEST"});let a=o(t.sessionId)||`dispatch-${i||r}`,c=o(t.instructions),u=[{type:"text",text:["Task dispatch received.",`taskId: ${r}`,i?`executionId: ${i}`:"",c?`instructions: ${c}`:""].filter(Boolean).join(`
|
|
11
|
+
`)}];return this.prompt({...t,agentId:n,sessionId:a,prompt:u},e)}async requestPermission({agent:t,runtimeSessionId:e,request:n,emitUpdate:r}){let i=$(),c=this.runtimeSessionIndex.get(`${t.agentId}:${e}`)?.sessionId||e||$(),u={permissionId:i,agentId:t.agentId,sessionId:c,runtimeSessionId:e,request:d(n)?n:{},status:"pending",createdAt:R()};return r?.({sessionId:e,update:{sessionUpdate:"permission_request",permission:u}}),await new Promise(l=>{let f=g(n?.timeoutMs,this.config.permissionRequestTimeoutMs),p=setTimeout(()=>{this.pendingPermissions.delete(i),l({permissionId:i,outcome:"denied",response:{reason:"permission request timed out"}})},f);this.pendingPermissions.set(i,{timer:p,resolve:l,permission:u})})}async respondPermission(t){let e=o(t.permissionId),n=this.pendingPermissions.get(e),r=o(t.outcome||"approved")||"approved";return n?(clearTimeout(n.timer),this.pendingPermissions.delete(e),n.resolve({permissionId:e,outcome:r,response:d(t.response)?t.response:{},userId:o(t.userId),metadata:d(t.metadata)?t.metadata:{}}),{accepted:!0,permissionId:e,outcome:r}):{accepted:!1,permissionId:e,outcome:r}}health(){return{status:"ok",bridgeVersion:M,startedAt:this.startedAt,agents:[...this.agents.values()].map(t=>({agentId:t.agentId,targetCode:t.targetCode,framework:t.framework,runtimeKind:t.runtimeKind})),sessions:this.sessions.size,pendingPermissions:this.pendingPermissions.size,targets:Object.keys(this.targets)}}};import Jt from"node:http";import zt from"node:https";import{randomBytes as rt}from"node:crypto";var j=1,z=3,q=class{constructor(t){this.url=t,this.readyState=0,this.socket=null,this.buffer=Buffer.alloc(0),this.listeners=new Map,this.connect()}addEventListener(t,e){let n=this.listeners.get(t)||new Set;n.add(e),this.listeners.set(t,n)}emit(t,e={}){let n=this.listeners.get(t);if(n)for(let r of n)r(e)}connect(){let t=new URL(this.url),e=t.protocol==="wss:"?zt:Jt,n=rt(16).toString("base64"),r=e.request({protocol:t.protocol==="wss:"?"https:":"http:",hostname:t.hostname,port:t.port||(t.protocol==="wss:"?443:80),path:`${t.pathname}${t.search}`,method:"GET",headers:{Connection:"Upgrade",Upgrade:"websocket","Sec-WebSocket-Key":n,"Sec-WebSocket-Version":"13"}});r.on("upgrade",(i,a,c)=>{this.socket=a,this.readyState=j,a.on("data",u=>this.onData(u)),a.on("close",()=>this.onClose()),a.on("error",u=>{this.emit("error",{error:u,message:u.message,type:"error"}),this.onClose()}),c?.length&&this.onData(c),this.emit("open")}),r.on("response",i=>{this.emit("error",{message:`websocket upgrade failed: HTTP ${i.statusCode}`,type:"error"}),i.resume(),this.onClose()}),r.on("error",i=>{this.emit("error",{error:i,message:i.message,type:"error"}),this.onClose()}),r.end()}send(t){!this.socket||this.readyState!==j||this.socket.write(K(1,Buffer.from(String(t),"utf8")))}close(){!this.socket||this.readyState===z||(this.socket.write(K(8,Buffer.alloc(0))),this.socket.end(),this.onClose())}onClose(){this.readyState!==z&&(this.readyState=z,this.emit("close"))}onData(t){for(this.buffer=Buffer.concat([this.buffer,t]);this.buffer.length>=2;){let e=this.buffer[0],n=this.buffer[1],r=e&15,i=(n&128)!==0,a=n&127,c=2;if(a===126){if(this.buffer.length<c+2)return;a=this.buffer.readUInt16BE(c),c+=2}else if(a===127){if(this.buffer.length<c+8)return;let f=this.buffer.readUInt32BE(c),p=this.buffer.readUInt32BE(c+4);a=f*2**32+p,c+=8}let u=c;if(i&&(c+=4),this.buffer.length<c+a)return;let l=this.buffer.subarray(c,c+a);if(i){let f=this.buffer.subarray(u,u+4);l=Buffer.from(l.map((p,I)=>p^f[I%4]))}this.buffer=this.buffer.subarray(c+a),r===1?this.emit("message",{data:l.toString("utf8")}):r===8?this.close():r===9&&this.socket?.write(K(10,l))}}};function K(s,t){let e=t.length,n=2;e>=126&&e<=65535?n+=2:e>65535&&(n+=8);let r=rt(4),i=Buffer.alloc(n+4+e);i[0]=128|s;let a=2;e<126?i[1]=128|e:e<=65535?(i[1]=254,i.writeUInt16BE(e,a),a+=2):(i[1]=255,i.writeUInt32BE(Math.floor(e/2**32),a),i.writeUInt32BE(e>>>0,a+4),a+=8),r.copy(i,a),a+=4;for(let c=0;c<e;c+=1)i[a+c]=t[c]^r[c%4];return i}function Kt(s){let t=new URL(s);if(t.protocol==="http:")t.protocol="ws:";else if(t.protocol==="https:")t.protocol="wss:";else if(t.protocol!=="ws:"&&t.protocol!=="wss:")throw new Error(`unsupported ORCHA_RUNTIME_ORCHA_URL protocol: ${t.protocol}`);return t.pathname=`${t.pathname.replace(/\/+$/,"")}/api/agent-runtime-bridge/ws`,t.search="",t.toString()}function Wt(s){return Object.entries(s||{}).map(([t,e])=>({targetCode:t,targetName:o(e?.name||e?.targetName||t),targetType:o(e?.type||"echo"),status:"available",metadata:d(e)?e:{}}))}var G=class{constructor(t,e){this.config=t,this.bridge=e,this.ws=null,this.stopped=!1,this.connected=!1,this.reconnectTimer=null}start(){this.connect()}stop(){this.stopped=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null,this.ws?.close()}connect(){let t=Kt(this.config.orchaUrl),e=new q(t);this.ws=e,e.addEventListener("open",()=>this.authenticate()),e.addEventListener("message",n=>this.handleMessage(n.data)),e.addEventListener("close",()=>this.scheduleReconnect("close")),e.addEventListener("error",n=>{h("warn","orcha-client","runtime node websocket error",{error:String(n?.message||n?.type||n)})})}authenticate(){this.send({type:"runtime_node.authenticate",token:this.config.nodeToken,nodeId:this.config.nodeId,nodeName:this.config.nodeName,version:M,protocolVersion:"orcha-runtime-node.v1",defaultTargetCode:this.config.defaultTargetCode,targets:Wt(this.config.targets)})}scheduleReconnect(t){this.connected=!1,!this.stopped&&(this.reconnectTimer||(h("info","orcha-client","runtime node disconnected; reconnect scheduled",{reason:t,reconnectMs:this.config.reconnectMs}),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},this.config.reconnectMs)))}async handleMessage(t){let e;try{e=JSON.parse(String(t))}catch(n){h("warn","orcha-client","invalid Orcha websocket frame",{error:String(n)});return}if(d(e)){if(e.type==="runtime_node.authenticated"){this.connected=!0,h("info","orcha-client","runtime node authenticated",{nodeId:this.config.nodeId,connectionId:e.connectionId});return}if(e.type==="orcha.runtime.request"){await this.handleRequest(e);return}e.type==="runtime_node.ping"&&this.send({type:"runtime_node.pong",ts:new Date().toISOString()})}}async handleRequest(t){let e=o(t.id),n=o(t.method),r=d(t.params)?t.params:{};try{let i=await this.invoke(n,r,e);this.send({type:"orcha.runtime.response",id:e,ok:!0,result:i})}catch(i){this.send({type:"orcha.runtime.response",id:e,ok:!1,error:{code:i.code||"RUNTIME_NODE_ERROR",message:i.message||String(i)}})}}async invoke(t,e,n){if(t==="health")return this.bridge.health();if(t==="agents/create")return this.bridge.createAgent(e);if(t==="agents/dispatch")return this.bridge.dispatch(e,{onUpdate:r=>this.sendUpdate(n,r)});if(t==="sessions/prompt")return this.bridge.prompt(e,{onUpdate:r=>this.sendUpdate(n,r)});if(t==="sessions/cancel")return this.bridge.cancel(e);if(t==="permissions/respond")return this.bridge.respondPermission(e);throw Object.assign(new Error(`unsupported Orcha runtime method: ${t}`),{code:"METHOD_NOT_FOUND"})}sendUpdate(t,e){this.send({type:"orcha.runtime.event",requestId:t,eventType:"runtime.update",payload:e})}send(t){return!this.ws||this.ws.readyState!==j?!1:(this.ws.send(JSON.stringify(t)),!0)}};import{existsSync as Vt,readFileSync as Qt}from"node:fs";import{resolve as it}from"node:path";var Zt=/^[A-Za-z_][A-Za-z0-9_]*$/;function ot(s=process.env,t=process.cwd()){let e=o(s.ORCHA_RUNTIME_ENV_FILE),n=e?it(t,e):it(t,".env");if(!Vt(n))return e&&h("warn","config","runtime env file not found",{path:n}),{loaded:!1,path:n,keys:[]};let r=[],i=Qt(n,"utf8");for(let a of i.split(/\r?\n/)){let c=Xt(a);if(!c)continue;let[u,l]=c;Object.prototype.hasOwnProperty.call(s,u)||(s[u]=l,r.push(u))}return h("info","config","runtime env file loaded",{path:n,keyCount:r.length}),{loaded:!0,path:n,keys:r}}function at(s=process.env){return{mode:o(s.ORCHA_RUNTIME_MODE||(s.ORCHA_RUNTIME_ORCHA_URL?"node":"server")),host:s.ORCHA_RUNTIME_BRIDGE_HOST||"127.0.0.1",port:g(s.ORCHA_RUNTIME_BRIDGE_PORT,5588),token:s.ORCHA_RUNTIME_BRIDGE_TOKEN||"",orchaUrl:o(s.ORCHA_RUNTIME_ORCHA_URL),nodeToken:s.ORCHA_RUNTIME_NODE_TOKEN||s.ORCHA_RUNTIME_BRIDGE_TOKEN||"",nodeId:o(s.ORCHA_RUNTIME_NODE_ID||s.HOSTNAME||"local-runtime-node"),nodeName:o(s.ORCHA_RUNTIME_NODE_NAME||s.ORCHA_RUNTIME_NODE_ID||s.HOSTNAME||"Local Runtime Node"),reconnectMs:g(s.ORCHA_RUNTIME_RECONNECT_MS,3e3),defaultTargetCode:s.ORCHA_RUNTIME_DEFAULT_TARGET||"echo",requestLimitBytes:g(s.ORCHA_RUNTIME_REQUEST_LIMIT_BYTES,1048576),sessionPromptTimeoutMs:g(s.ORCHA_RUNTIME_SESSION_PROMPT_TIMEOUT_MS,36e5),permissionRequestTimeoutMs:g(s.ORCHA_RUNTIME_PERMISSION_TIMEOUT_MS,3e5),targets:Yt(s.ORCHA_RUNTIME_TARGETS_JSON||"")}}function Yt(s){if(!o(s))return{};try{let t=JSON.parse(s);if(d(t))return t}catch(t){h("warn","config","invalid ORCHA_RUNTIME_TARGETS_JSON",{error:String(t)})}return{}}function Xt(s){let t=s.trim();if(!t||t.startsWith("#"))return null;let e=t.startsWith("export ")?t.slice(7).trimStart():t,n=e.indexOf("=");if(n<=0)return null;let r=e.slice(0,n).trim();if(!Zt.test(r))return null;let i=te(e.slice(n+1).trim());return[r,i]}function te(s){if(!s)return"";let t=s[0];if((t==='"'||t==="'")&&s.endsWith(t)){let e=s.slice(1,-1);return t==='"'?ee(e):e}return se(s).trim()}function ee(s){return s.replaceAll("\\n",`
|
|
12
|
+
`).replaceAll("\\r","\r").replaceAll("\\t"," ").replaceAll('\\"','"').replaceAll("\\\\","\\")}function se(s){let t=!1,e=!1;for(let n=0;n<s.length;n+=1){let r=s[n],i=s[n-1];if(r==="'"&&!e)t=!t;else if(r==='"'&&!t&&i!=="\\")e=!e;else if(r==="#"&&!t&&!e&&/\s/.test(i||""))return s.slice(0,n)}return s}import{createServer as ne}from"node:http";function J(s,t,e){let n=JSON.stringify(e);s.writeHead(t,{"content-type":"application/json; charset=utf-8","content-length":Buffer.byteLength(n)}),s.end(n)}function y(s={}){return{ok:!0,...s}}function W(s,t,e){return{ok:!1,error:{code:s,message:t,...e===void 0?{}:{detail:e}}}}async function ct(s,t){let e=[],n=0;for await(let i of s){if(n+=i.length,n>t)throw Object.assign(new Error("request body too large"),{status:413,code:"REQUEST_TOO_LARGE"});e.push(i)}if(e.length===0)return{};let r=Buffer.concat(e).toString("utf8");if(!r.trim())return{};try{let i=JSON.parse(r);if(!d(i))throw Object.assign(new Error("JSON body must be an object"),{status:400,code:"BAD_JSON"});return i}catch(i){throw i.status?i:Object.assign(new Error(`invalid JSON body: ${i.message}`),{status:400,code:"BAD_JSON"})}}function dt(s,t){if(!t)return;if((s.headers.authorization||"")!==`Bearer ${t}`)throw Object.assign(new Error("runtime bridge token is invalid"),{status:401,code:"UNAUTHORIZED"})}function ut(s,t){let e={"GET /health":async()=>y(t.health()),"POST /agents/create":async n=>y(await t.createAgent(n)),"POST /agents/dispatch":async n=>y(await t.dispatch(n)),"POST /sessions/prompt":async n=>y(await t.prompt(n)),"POST /sessions/cancel":async n=>y(await t.cancel(n)),"POST /permissions/respond":async n=>y(await t.respondPermission(n))};return ne(async(n,r)=>{let i=new URL(n.url||"/",`http://${n.headers.host||`${s.host}:${s.port}`}`),a=`${n.method||"GET"} ${i.pathname}`,c=e[a];if(!c){J(r,404,W("NOT_FOUND",`No route for ${a}`));return}try{dt(n,s.token);let u=n.method==="GET"?{}:await ct(n,s.requestLimitBytes);J(r,200,await c(u))}catch(u){let l=u.status||500,f=u.code||"BRIDGE_INTERNAL";J(r,l,W(f,u.message||String(u)))}})}var fe=pe(import.meta.url),me=".orcha-runtime-bridge.pid",ge=".orcha-runtime-bridge.log",{command:we,options:wt}=ye(process.argv.slice(2));Te(wt);ot();var Ie=at();await _e(we,Ie,wt);async function _e(s,t,e){switch(s){case"run":case"foreground":lt(t);return;case"start":if(e.foreground){lt(t);return}ht();return;case"reload":case"restart":await pt({quiet:!0}),ht();return;case"stop":await pt();return;case"status":Ee();return;case"help":case"--help":case"-h":Se();return;default:throw new Error(`Unknown command: ${s}`)}}function lt(s){let t=new B(s);if(s.mode==="server"){ut(s,t).listen(s.port,s.host,()=>{h("info","runtime-bridge","Orcha runtime bridge HTTP server started",{host:s.host,port:s.port,targets:Object.keys(t.targets)})});return}if(!s.orchaUrl)throw new Error("ORCHA_RUNTIME_ORCHA_URL is required in runtime node mode");new G(s,t).start(),h("info","runtime-bridge","Orcha runtime node started",{nodeId:s.nodeId,nodeName:s.nodeName,orchaUrl:s.orchaUrl,targets:Object.keys(t.targets)})}function ht(){let s=Q(),t=It(),e=Z(s);if(e&&x(e)){console.log(JSON.stringify({status:"already_running",pid:e,pidFile:s,logFile:t}));return}e&&V(s),mt(s),mt(t);let n=ce(t,"a"),r=re(process.execPath,[fe,"run"],{cwd:process.cwd(),detached:!0,env:process.env,stdio:["ignore",n,n]});r.unref(),ie(n),le(s,`${r.pid}
|
|
13
|
+
`),console.log(JSON.stringify({status:"started",pid:r.pid,pidFile:s,logFile:t}))}async function pt({quiet:s=!1}={}){let t=Q(),e=Z(t);if(!e){s||console.log(JSON.stringify({status:"not_running",pidFile:t}));return}if(!x(e)){V(t),s||console.log(JSON.stringify({status:"not_running",pid:e,pidFile:t}));return}process.kill(e,"SIGTERM"),!await ft(e,1e4)&&x(e)&&(process.kill(e,"SIGKILL"),await ft(e,3e3)),V(t),s||console.log(JSON.stringify({status:"stopped",pid:e,pidFile:t}))}function Ee(){let s=Q(),t=It(),e=Z(s),n=!!(e&&x(e));console.log(JSON.stringify({status:n?"running":"not_running",running:n,pid:n?e:null,pidFile:s,logFile:t}))}function ye(s){let t=[],e={};for(let r=0;r<s.length;r+=1){let i=s[r];i==="--daemon"?e.daemon=!0:i==="--foreground"?e.foreground=!0:i==="--env-file"?e.envFile=s[++r]:i==="--pid-file"?e.pidFile=s[++r]:i==="--log-file"?e.logFile=s[++r]:t.push(i)}return{command:t[0]||"run",options:e}}function Te(s){s.envFile&&(process.env.ORCHA_RUNTIME_ENV_FILE=s.envFile),s.pidFile&&(process.env.ORCHA_RUNTIME_PID_FILE=s.pidFile),s.logFile&&(process.env.ORCHA_RUNTIME_LOG_FILE=s.logFile)}function Q(){return gt(process.cwd(),process.env.ORCHA_RUNTIME_PID_FILE||me)}function It(){return gt(process.cwd(),process.env.ORCHA_RUNTIME_LOG_FILE||ge)}function Z(s){if(!oe(s))return null;let t=Number.parseInt(de(s,"utf8").trim(),10);return Number.isInteger(t)&&t>0?t:null}function x(s){try{return process.kill(s,0),!0}catch(t){return t?.code==="EPERM"}}async function ft(s,t){let e=Date.now();for(;Date.now()-e<t;){if(!x(s))return!0;await new Promise(n=>setTimeout(n,100))}return!x(s)}function mt(s){ae(he(s),{recursive:!0})}function V(s){try{ue(s,{force:!0})}catch{}}function Se(){console.log(`Usage: orcha-runtime-bridge [command] [options]
|
|
14
|
+
|
|
15
|
+
Commands:
|
|
16
|
+
run, foreground Run in the foreground. This is the default.
|
|
17
|
+
start Start in the background and write a pid file.
|
|
18
|
+
reload, restart Stop the background process and start it again.
|
|
19
|
+
stop Stop the background process.
|
|
20
|
+
status Print background process status as JSON.
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--env-file <path> Load runtime environment from this file.
|
|
24
|
+
--pid-file <path> Override ORCHA_RUNTIME_PID_FILE.
|
|
25
|
+
--log-file <path> Override ORCHA_RUNTIME_LOG_FILE.
|
|
26
|
+
--foreground Run "start" in the foreground.
|
|
27
|
+
`)}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orcha-ai/runtime-bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Local Orcha runtime bridge for ACP-compatible agent runtimes.",
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"homepage": "https://orcha.tansuo.cloud",
|
|
9
|
+
"bin": {
|
|
10
|
+
"orcha-runtime-bridge": "./bin/orcha-runtime-bridge.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"README.md",
|
|
15
|
+
".env.example"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node bin/orcha-runtime-bridge.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"orcha",
|
|
22
|
+
"runtime",
|
|
23
|
+
"agent",
|
|
24
|
+
"acp",
|
|
25
|
+
"codex",
|
|
26
|
+
"openclaw"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
}
|
|
34
|
+
}
|