stable-harness 0.0.101 → 0.0.103

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.
Files changed (35) hide show
  1. package/README.md +4 -0
  2. package/docs/guides/index.md +1 -0
  3. package/docs/guides/integration-guide.md +27 -5
  4. package/docs/protocols/agent-protocols.md +73 -0
  5. package/docs/protocols/http-runtime.md +3 -3
  6. package/node_modules/@stable-harness/adapter-deepagents/package.json +2 -2
  7. package/node_modules/@stable-harness/adapter-langgraph/package.json +2 -2
  8. package/node_modules/@stable-harness/core/package.json +3 -3
  9. package/node_modules/@stable-harness/governance/package.json +1 -1
  10. package/node_modules/@stable-harness/memory/package.json +1 -1
  11. package/node_modules/@stable-harness/protocols/dist/src/agent-protocols.d.ts +7 -0
  12. package/node_modules/@stable-harness/protocols/dist/src/agent-protocols.js +1 -0
  13. package/node_modules/@stable-harness/protocols/dist/src/index.d.ts +2 -0
  14. package/node_modules/@stable-harness/protocols/dist/src/index.js +1 -1
  15. package/node_modules/@stable-harness/protocols/package.json +2 -2
  16. package/node_modules/@stable-harness/tool-gateway/package.json +1 -1
  17. package/node_modules/@stable-harness/workspace-yaml/package.json +2 -2
  18. package/package.json +9 -9
  19. package/packages/adapter-deepagents/package.json +2 -2
  20. package/packages/adapter-langgraph/package.json +2 -2
  21. package/packages/cli/dist/src/cli.js +1 -1
  22. package/packages/cli/dist/src/console/session.js +1 -1
  23. package/packages/cli/dist/src/server.js +1 -1
  24. package/packages/cli/package.json +8 -8
  25. package/packages/core/package.json +3 -3
  26. package/packages/evaluation/package.json +2 -2
  27. package/packages/governance/package.json +1 -1
  28. package/packages/memory/package.json +1 -1
  29. package/packages/protocols/dist/src/agent-protocols.d.ts +7 -0
  30. package/packages/protocols/dist/src/agent-protocols.js +1 -0
  31. package/packages/protocols/dist/src/index.d.ts +2 -0
  32. package/packages/protocols/dist/src/index.js +1 -1
  33. package/packages/protocols/package.json +2 -2
  34. package/packages/tool-gateway/package.json +1 -1
  35. package/packages/workspace-yaml/package.json +2 -2
package/README.md CHANGED
@@ -97,6 +97,9 @@ http://127.0.0.1:8641
97
97
  http://127.0.0.1:8642/v1
98
98
  ```
99
99
 
100
+ ACP, A2A, and AG-UI facades are available when enabled in runtime YAML through
101
+ the combined `agentProtocols` server.
102
+
100
103
  For request commands, the CLI is daemon-first. It prints whether it connected to
101
104
  the workspace daemon or fell back to an in-process local runtime.
102
105
  Console mode uses the same daemon-first behavior and keeps slash commands such
@@ -236,6 +239,7 @@ This is constrained repair, not silent magic:
236
239
  - Stable Runtime HTTP + SSE: [docs/protocols/http-runtime.md](docs/protocols/http-runtime.md)
237
240
  - OpenAI-compatible facade: [docs/protocols/openai-compatible.md](docs/protocols/openai-compatible.md)
238
241
  - LangGraph-compatible facade: [docs/protocols/langgraph-compatible.md](docs/protocols/langgraph-compatible.md)
242
+ - ACP, A2A, and AG-UI facades: [docs/protocols/agent-protocols.md](docs/protocols/agent-protocols.md)
239
243
 
240
244
  ## Documentation
241
245
 
@@ -40,6 +40,7 @@ embed it, operate it, or explain why it exists.
40
40
  - [OpenAI-compatible protocol](../protocols/openai-compatible.md)
41
41
  - [LangGraph-compatible protocol](../protocols/langgraph-compatible.md)
42
42
  - [HTTP runtime protocol](../protocols/http-runtime.md)
43
+ - [ACP, A2A, and AG-UI protocol facades](../protocols/agent-protocols.md)
43
44
 
44
45
  The short version: keep execution semantics upstream-native, define product
45
46
  inventory in YAML, and use Stable Harness for lifecycle, governance,
@@ -105,8 +105,9 @@ stable-harness start -w ./workspace --host 127.0.0.1 --port 8642
105
105
  ```
106
106
 
107
107
  `stable-harness start` exposes the first-party Stable Runtime control plane,
108
- the OpenAI-compatible facade, and the LangGraph-compatible server when enabled
109
- by runtime config. Point first-party clients at:
108
+ the OpenAI-compatible facade, the LangGraph-compatible server, and optional
109
+ ACP/A2A/AG-UI facades when enabled by runtime config. Point first-party clients
110
+ at:
110
111
 
111
112
  ```text
112
113
  http://127.0.0.1:8641
@@ -130,12 +131,33 @@ artifacts, memory lifecycle, runtime inspection, and real-time control-plane
130
131
  updates.
131
132
 
132
133
  This is the first-party protocol for Studio and product shells. OpenAI
133
- compatible `/v1` and LangGraph-compatible servers are ecosystem facades; future
134
- ACP, A2A, and AG-UI support should map to this protocol instead of adding new
135
- runtime semantics.
134
+ compatible `/v1`, LangGraph-compatible, ACP, A2A, and AG-UI servers are
135
+ ecosystem facades that map to this protocol instead of adding new runtime
136
+ semantics.
136
137
 
137
138
  See [Stable Runtime HTTP + SSE](../protocols/http-runtime.md).
138
139
 
140
+ ## ACP, A2A, and AG-UI Facades
141
+
142
+ Enable the combined agent protocol server when an external client already
143
+ speaks one of these protocols:
144
+
145
+ ```yaml
146
+ apiVersion: stable-harness.dev/v1
147
+ kind: Runtime
148
+ spec:
149
+ protocols:
150
+ agentProtocols:
151
+ enabled: true
152
+ host: 127.0.0.1
153
+ port: 8650
154
+ protocols: [acp, a2a, agui]
155
+ ```
156
+
157
+ The server exposes A2A over HTTP+JSON/SSE, AG-UI over HTTP+SSE, and ACP over a
158
+ documented HTTP JSON-RPC custom transport. See
159
+ [ACP, A2A, and AG-UI Protocol Facades](../protocols/agent-protocols.md).
160
+
139
161
  ## Backend Adapters
140
162
 
141
163
  Adapters translate Stable Harness runtime requests into upstream backend calls.
@@ -0,0 +1,73 @@
1
+ # ACP, A2A, and AG-UI Protocol Facades
2
+
3
+ Stable Harness exposes ACP, A2A, and AG-UI as protocol adapters over the native
4
+ Stable Runtime request, event, and inspection surfaces. These adapters must not
5
+ add routing heuristics, backend execution semantics, or workspace-specific
6
+ behavior.
7
+
8
+ Enable the combined protocol server from runtime YAML:
9
+
10
+ ```yaml
11
+ apiVersion: stable-harness.dev/v1
12
+ kind: Runtime
13
+ spec:
14
+ protocols:
15
+ agentProtocols:
16
+ enabled: true
17
+ host: 127.0.0.1
18
+ port: 8650
19
+ protocols: [acp, a2a, agui]
20
+ ```
21
+
22
+ You can also enable a single protocol by configuring one of `acp`, `a2a`, or
23
+ `agui` with `enabled: true`. The CLI starts one combined HTTP server for the
24
+ enabled adapter set.
25
+
26
+ ## A2A
27
+
28
+ A2A is exposed as an HTTP+JSON and SSE facade:
29
+
30
+ - `GET /.well-known/agent-card.json`
31
+ - `GET /a2a/agent-card.json`
32
+ - `POST /a2a/message:send`
33
+ - `POST /a2a/message:stream`
34
+ - `POST /a2a/rpc`
35
+ - `GET /a2a/tasks`
36
+ - `GET /a2a/tasks/:id`
37
+ - `POST /a2a/tasks/:id:cancel`
38
+
39
+ `message:send` and JSON-RPC `SendMessage` map to a native `RuntimeRequest`.
40
+ Streaming responses subscribe to native runtime events and project them as A2A
41
+ status updates.
42
+
43
+ ## AG-UI
44
+
45
+ AG-UI is exposed as an HTTP+SSE event stream:
46
+
47
+ - `GET /ag-ui/capabilities`
48
+ - `POST /ag-ui/runs`
49
+
50
+ `POST /ag-ui/runs` emits `RunStarted`, protocol `Custom` events for Stable
51
+ Runtime events, `TextMessageChunk` for final assistant output, and `RunFinished`
52
+ or `RunError`.
53
+
54
+ ## ACP
55
+
56
+ ACP's standard transport is JSON-RPC over stdio. Stable Harness currently
57
+ exposes ACP over an HTTP JSON-RPC endpoint as a documented custom transport:
58
+
59
+ - `GET /acp/capabilities`
60
+ - `POST /acp`
61
+
62
+ Supported methods:
63
+
64
+ - `initialize`
65
+ - `session/new`
66
+ - `session/load`
67
+ - `session/list`
68
+ - `session/prompt`
69
+ - `session/cancel`
70
+
71
+ `session/prompt` maps to a native `RuntimeRequest` and returns the ACP stop
72
+ reason plus Stable Harness request metadata. A future stdio entrypoint can reuse
73
+ the same JSON-RPC method handler without changing runtime semantics.
@@ -4,9 +4,9 @@ The native Stable Runtime protocol is the product control-plane contract. It is
4
4
  the source of truth for first-party clients such as Studio, operator dashboards,
5
5
  and embedded product shells.
6
6
 
7
- OpenAI-compatible `/v1` and LangGraph-compatible servers are ecosystem
8
- facades. ACP, A2A, and AG-UI should be added later as adapters over this
9
- contract, not as new runtime semantics.
7
+ OpenAI-compatible `/v1`, LangGraph-compatible, ACP, A2A, and AG-UI servers are
8
+ ecosystem facades. They adapt external protocols to this contract rather than
9
+ adding new runtime semantics.
10
10
 
11
11
  ## HTTP Commands And Queries
12
12
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -15,7 +15,7 @@
15
15
  "@langchain/node-vfs": "^0.1.4",
16
16
  "@langchain/ollama": "^1.2.7",
17
17
  "@langchain/openai": "^1.4.5",
18
- "@stable-harness/core": "0.0.101",
18
+ "@stable-harness/core": "0.0.103",
19
19
  "deepagents": "^1.10.1",
20
20
  "langchain": "^1.4.0"
21
21
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-langgraph",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -11,6 +11,6 @@
11
11
  "types": "dist/src/index.d.ts",
12
12
  "peerDependencies": {
13
13
  "@langchain/langgraph": "^1.3.0",
14
- "@stable-harness/core": "0.0.101"
14
+ "@stable-harness/core": "0.0.103"
15
15
  }
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -11,7 +11,7 @@
11
11
  ".": "./dist/index.js"
12
12
  },
13
13
  "peerDependencies": {
14
- "@stable-harness/governance": "0.0.101",
15
- "@stable-harness/memory": "0.0.101"
14
+ "@stable-harness/governance": "0.0.103",
15
+ "@stable-harness/memory": "0.0.103"
16
16
  }
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/memory",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -0,0 +1,7 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { StableHarnessRuntime } from "@stable-harness/core";
3
+ export type AgentProtocolServerOptions = {
4
+ baseUrl?: string;
5
+ enabledProtocols?: Array<"acp" | "a2a" | "agui">;
6
+ };
7
+ export declare function createAgentProtocolHttpServer(runtime: StableHarnessRuntime, options?: AgentProtocolServerOptions): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
@@ -0,0 +1 @@
1
+ import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,s={}){const a=new Set(s.enabledProtocols??["acp","a2a","agui"]);return e(async(e,n)=>{try{if("GET"===e.method&&"/health"===e.url)return void sendJson(n,200,{ok:!0,protocols:[...a]});if(a.has("a2a")&&await async function handleA2a(e,t,s,a){if("GET"===t.method&&("/.well-known/agent-card.json"===t.url||"/a2a/agent-card.json"===t.url))return sendJson(s,200,function createAgentCard(e,t){const s=e.inspect();return{protocolVersion:"1.0",name:s.workspaceRoot.split(/[\\/]/u).filter(Boolean).at(-1)??"stable-harness",description:"Stable Harness runtime agent endpoint",version:"0.0.0",url:t.baseUrl?`${t.baseUrl.replace(/\/$/u,"")}/a2a`:"/a2a",preferredTransport:"HTTP+JSON",capabilities:{streaming:!0},defaultInputModes:["text/plain"],defaultOutputModes:["text/plain"],skills:s.agents.map(e=>({id:e,name:e,description:e}))}}(e,a)),!0;const n=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&n){const t=e.inspectRequest(n);return sendJson(s,t?200:404,t?{task:toA2aTask(t)}:{error:"task_not_found"}),!0}if("GET"===t.method&&"/a2a/tasks"===t.url)return sendJson(s,200,{tasks:e.listRequests().map(toA2aTaskSummary)}),!0;const r=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);return"POST"===t.method&&r?(e.cancel(r,"a2a_cancel"),sendJson(s,200,{task:e.inspectRequest(r)}),!0):"POST"===t.method&&"/a2a/message:send"===t.url?(sendJson(s,200,{task:toA2aTask(await e.request(toA2aRuntimeRequest(await readJson(t))))}),!0):"POST"===t.method&&"/a2a/message:stream"===t.url?(await streamA2a(e,await readJson(t),s),!0):"POST"===t.method&&"/a2a/rpc"===t.url&&(await async function handleA2aRpc(e,t,s){if("SendMessage"===t.method){const a=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(s,200,jsonRpcResult(t.id,{task:toA2aTask(a)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const a=readString(readRecord(t.params)?.id),n=a?e.inspectRequest(a):void 0;return void sendJson(s,n?200:404,n?jsonRpcResult(t.id,toA2aTask(n)):jsonRpcError(t.id,-32004,"task_not_found"))}"ListTasks"!==t.method?sendJson(s,404,jsonRpcError(t.id,-32601,"method_not_found")):sendJson(s,200,jsonRpcResult(t.id,{tasks:e.listRequests().map(toA2aTaskSummary)}))}else await streamA2a(e,readRecord(t.params)??{},s,t.id)}(e,await readJson(t),s),!0)}(t,e,n,s))return;if(a.has("agui")&&await async function handleAgui(e,t,s){return"GET"===t.method&&"/ag-ui/capabilities"===t.url?(sendJson(s,200,{protocol:"ag-ui",transports:["http+sse"],events:["RunStarted","TextMessageChunk","Custom","RunFinished","RunError"]}),!0):"POST"===t.method&&"/ag-ui/runs"===t.url&&(await async function streamAgui(e,t,s){const a=readString(t.runId)??crypto.randomUUID(),n=readString(t.threadId)??readString(t.sessionId)??`thread-${a}`,r=readPromptText(t);writeSseHeaders(s),writeSse(s,{type:"RunStarted",threadId:n,runId:a,input:{messages:t.messages,input:r}});const o=e.subscribe(e=>{e.requestId===a&&writeSse(s,function toAguiEvent(e){return"runtime.progress.narration"===e.type?{type:"Custom",name:"stable-harness.progress",value:e}:"runtime.tool.direct.started"===e.type?{type:"ToolCallStart",toolCallId:`${e.requestId}:${e.toolId}`,toolCallName:e.toolId}:"runtime.tool.direct.completed"===e.type?{type:"ToolCallResult",messageId:`${e.requestId}-message`,toolCallId:`${e.requestId}:${e.toolId}`,content:e.output,role:"tool"}:{type:"Custom",name:`stable-harness.${e.type}`,value:e}}(e))});try{const o=await e.request({input:r,requestId:a,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),i=`${a}-message`;o.output&&writeSse(s,{type:"TextMessageChunk",messageId:i,role:"assistant",delta:o.output}),writeSse(s,{type:"RunFinished",threadId:n,runId:a,outcome:{type:"completed"===o.state?"success":"interrupt"},result:o.output})}catch(e){writeSse(s,{type:"RunError",threadId:n,runId:a,message:errorMessage(e)})}finally{o(),s.end()}}(e,await readJson(t),s),!0)}(t,e,n))return;if(a.has("acp")&&await async function handleAcp(e,t,s){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(s,200,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"}}),!0;if("POST"===t.method&&"/acp"===t.url){const a=await readJson(t),n=await async function handleAcpMessage(e,t){if(!t.id&&"session/cancel"===t.method){const s=readString(readRecord(t.params)?.sessionId);return void(s&&e.listRequests({sessionId:s,state:"running"}).forEach(t=>e.cancel(t.requestId,"acp_cancel")))}if("initialize"===t.method)return jsonRpcResult(t.id,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"}});if("session/new"===t.method)return jsonRpcResult(t.id,{sessionId:`acp-${crypto.randomUUID()}`});if("session/load"===t.method)return jsonRpcResult(t.id,{sessionId:readString(readRecord(t.params)?.sessionId)});if("session/list"===t.method)return jsonRpcResult(t.id,{sessions:e.listSessions()});if("session/prompt"===t.method){const s=readRecord(t.params)??{},a=await e.request({input:readPromptText(s),sessionId:readString(s.sessionId),agentId:readString(s.agentId),metadata:{protocol:"acp",transport:"http-jsonrpc"}});return jsonRpcResult(t.id,{stopReason:"completed"===a.state?"end_turn":"refusal",_meta:{requestId:a.requestId,output:a.output}})}return jsonRpcError(t.id,-32601,"method_not_found")}(e,a);return n?sendJson(s,200,n):s.writeHead(204).end(),!0}return!1}(t,e,n))return;sendJson(n,404,{error:"not_found"})}catch(e){sendJson(n,400,{error:errorMessage(e)})}})}async function streamA2a(e,t,s,a){const n=toA2aRuntimeRequest(t);writeSseHeaders(s);const r=e.subscribe(e=>{e.requestId===n.requestId&&writeSse(s,{jsonrpc:"2.0",id:a,result:{event:toA2aEvent(e)}})});try{const t=await e.request(n);writeSse(s,{jsonrpc:"2.0",id:a,result:{task:toA2aTask(t),final:!0}})}finally{r(),s.end()}}function toA2aRuntimeRequest(e){const t=readRecord(e.message)??e;return{input:readPromptText(t),requestId:readString(e.requestId)??readString(t.messageId),sessionId:readString(e.taskId)??readString(t.taskId)??readString(t.contextId),agentId:readString(e.agentId)??readString(e.metadata&&readRecord(e.metadata)?.agentId),metadata:{protocol:"a2a",configuration:e.configuration,metadata:e.metadata}}}function toA2aTask(e){const t="state"in e?e.state:e.summary.state,s="output"in e?e.output:e.output??"",a="requestId"in e?e.requestId:e.summary.requestId;return{id:a,contextId:"sessionId"in e?e.sessionId:e.summary.sessionId,status:{state:"completed"===t?"completed":t,message:s?{role:"agent",messageId:`${a}-response`,parts:[{kind:"text",text:s}]}:void 0}}}function toA2aTaskSummary(e){return{id:e.requestId,contextId:e.sessionId,status:{state:e.state},metadata:{agentId:e.agentId}}}function toA2aEvent(e){return{kind:"status-update",taskId:e.requestId,contextId:e.sessionId,status:{state:e.type},metadata:e}}function readPromptText(e){return"string"==typeof e.input?e.input:"string"==typeof e.text?e.text:"string"==typeof e.prompt?e.prompt:Array.isArray(e.parts)?e.parts.map(readPartText).filter(Boolean).join("\n"):Array.isArray(e.messages)?readPromptText(readRecord(e.messages.at(-1))??{}):Array.isArray(e.content)?e.content.map(readPartText).filter(Boolean).join("\n"):"string"==typeof e.content?e.content:""}function readPartText(e){const t=readRecord(e);return t?readString(t.text)??readString(t.content)??"":"string"==typeof e?e:""}function matchPath(e,t){const s=(e??"").match(t);return s?.[1]?decodeURIComponent(s[1]):void 0}function readRecord(e){return"object"!=typeof e||null===e||Array.isArray(e)?void 0:e}function readString(e){return"string"==typeof e&&e.trim()?e:void 0}function jsonRpcResult(e,t){return{jsonrpc:"2.0",id:e,result:t}}function jsonRpcError(e,t,s){return{jsonrpc:"2.0",id:e,error:{code:t,message:s}}}function sendJson(e,t,s){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(s))}function writeSseHeaders(e){e.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive"})}function writeSse(e,t){e.write(`data: ${JSON.stringify(t)}\n\n`)}async function readJson(e){const t=[];for await(const s of e)t.push(Buffer.isBuffer(s)?s:Buffer.from(s));return t.length>0?JSON.parse(Buffer.concat(t).toString("utf8")):{}}function errorMessage(e){return e instanceof Error?e.message:String(e)}
@@ -1,4 +1,6 @@
1
1
  export { createInProcessClient } from "./in-process-client.js";
2
+ export { createAgentProtocolHttpServer } from "./agent-protocols.js";
3
+ export type { AgentProtocolServerOptions } from "./agent-protocols.js";
2
4
  export { createHttpServer } from "./http-server.js";
3
5
  export { createOpenAiCompatibleHttpServer } from "./openai-compatible.js";
4
6
  export type { OpenAiCompatibleServerOptions } from "./openai-compatible.js";
@@ -1 +1 @@
1
- export{createInProcessClient}from"./in-process-client.js";export{createHttpServer}from"./http-server.js";export{createOpenAiCompatibleHttpServer}from"./openai-compatible.js";
1
+ export{createInProcessClient}from"./in-process-client.js";export{createAgentProtocolHttpServer}from"./agent-protocols.js";export{createHttpServer}from"./http-server.js";export{createOpenAiCompatibleHttpServer}from"./openai-compatible.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/protocols",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -10,6 +10,6 @@
10
10
  "main": "dist/src/index.js",
11
11
  "types": "dist/src/index.d.ts",
12
12
  "peerDependencies": {
13
- "@stable-harness/core": "0.0.101"
13
+ "@stable-harness/core": "0.0.103"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/workspace-yaml",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -11,6 +11,6 @@
11
11
  ".": "./dist/index.js"
12
12
  },
13
13
  "peerDependencies": {
14
- "@stable-harness/core": "0.0.101"
14
+ "@stable-harness/core": "0.0.103"
15
15
  }
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stable-harness",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "description": "Stable application runtime and operator control plane for agent workspaces.",
6
6
  "license": "Apache-2.0",
@@ -82,14 +82,14 @@
82
82
  "@langchain/node-vfs": "^0.1.4",
83
83
  "@langchain/ollama": "^1.2.7",
84
84
  "@langchain/openai": "^1.4.5",
85
- "@stable-harness/adapter-deepagents": "0.0.101",
86
- "@stable-harness/adapter-langgraph": "0.0.101",
87
- "@stable-harness/core": "0.0.101",
88
- "@stable-harness/governance": "0.0.101",
89
- "@stable-harness/memory": "0.0.101",
90
- "@stable-harness/protocols": "0.0.101",
91
- "@stable-harness/tool-gateway": "0.0.101",
92
- "@stable-harness/workspace-yaml": "0.0.101",
85
+ "@stable-harness/adapter-deepagents": "0.0.103",
86
+ "@stable-harness/adapter-langgraph": "0.0.103",
87
+ "@stable-harness/core": "0.0.103",
88
+ "@stable-harness/governance": "0.0.103",
89
+ "@stable-harness/memory": "0.0.103",
90
+ "@stable-harness/protocols": "0.0.103",
91
+ "@stable-harness/tool-gateway": "0.0.103",
92
+ "@stable-harness/workspace-yaml": "0.0.103",
93
93
  "deepagents": "^1.10.1",
94
94
  "langchain": "^1.4.0",
95
95
  "yaml": "^2.8.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -15,7 +15,7 @@
15
15
  "@langchain/node-vfs": "^0.1.4",
16
16
  "@langchain/ollama": "^1.2.7",
17
17
  "@langchain/openai": "^1.4.5",
18
- "@stable-harness/core": "0.0.101",
18
+ "@stable-harness/core": "0.0.103",
19
19
  "deepagents": "^1.10.1",
20
20
  "langchain": "^1.4.0"
21
21
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-langgraph",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -11,6 +11,6 @@
11
11
  "types": "dist/src/index.d.ts",
12
12
  "peerDependencies": {
13
13
  "@langchain/langgraph": "^1.3.0",
14
- "@stable-harness/core": "0.0.101"
14
+ "@stable-harness/core": "0.0.103"
15
15
  }
16
16
  }
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{realpathSync as e}from"node:fs";import{fileURLToPath as t}from"node:url";import{createBackendModel as r,createDeepAgentsAdapter as o}from"@stable-harness/adapter-deepagents";import{createLangGraphWorkflowAdapter as s}from"@stable-harness/adapter-langgraph";import{createStableHarnessRuntime as i}from"@stable-harness/core";import{projectEvent as a,projectRuntimeTrace as n}from"@stable-harness/core";import{createInMemoryApprovalQueue as u}from"@stable-harness/governance";import{createModuleToolGateway as l}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as d}from"@stable-harness/workspace-yaml";import{helpText as c,parseArgs as p}from"./args.js";import{buildWorkspaceArtifact as m}from"./build.js";import{runConsole as f}from"./console/session.js";import{formatCliRuntimeEvent as w,readCliEventViewConfig as v,shouldEnableCliProgressNarration as g}from"./event-view.js";import{initWorkspace as y}from"./init.js";import{runRequestThroughDaemon as I}from"./daemon/client.js";import{ensureCliMemoryServices as R}from"./memory/lifecycle.js";import{createCliMemoryProviders as k}from"./memory/providers.js";import{formatDetail as h,inspectWorkflow as b,renderAgent as q,renderWorkflow as A,workspaceStatus as C}from"./output.js";import{serveProtocol as j,stopProtocol as M}from"./server.js";export async function runCli(e=process.argv.slice(2)){const t=p(e);if(t.help)return void process.stdout.write(c());const r=setTimeout(()=>{process.stderr.write(`stable-harness request timed out after ${t.timeoutMs}ms\n`),process.exit(124)},t.timeoutMs),s=t.workspaceRoot;try{if("init"===t.command)return void process.stdout.write(await y(t.prompt||s));const e=await d(s);if(t.workflowRenderId)return void process.stdout.write(A(e,t.workflowRenderId));if(t.workflowInspectId)return void process.stdout.write(b(e,t.workflowInspectId));if(t.agentRenderId)return void process.stdout.write(q(e,t.agentRenderId));if("build"===t.command)return void process.stdout.write(await m({workspace:e,workspaceRoot:s,outputDir:t.buildOutput,target:t.buildTarget}));if("stop"===t.command)return clearTimeout(r),void await M(e,t);const c=v(e.runtime),createRuntime=()=>async function createCliRuntime(e,t,r){const s=await l({tools:e.tools.values(),options:{betterCall:{mode:"repair"}}}),a=u(),n=await async function createCliMemoryProvidersForCommand(e,t){return t.serveOpenAi||t.shouldRunRequest&&!t.toolId?(await R(e),k(e)):[]}(e,t);let d;return d=i({workspace:e,toolGateway:s,approvals:a,memoryProviders:n,adapters:[o()],workflowAdapters:[createCliWorkflowAdapter(s,()=>d)],progressNarration:g(r,e.runtime)?{enabled:!0,style:"cli"}:void 0,qualityReviewModel:createQualityReviewModel(e)}),d}(e,t,c);if("console"===t.command)return clearTimeout(r),void await f({args:t,runtimePolicy:e.runtime,eventView:c,createRuntime:createRuntime});if(t.shouldRunRequest&&!t.serveOpenAi&&await I({args:t,runtimePolicy:e.runtime,eventView:c}))return;t.shouldRunRequest&&!t.serveOpenAi&&process.stderr.write("stable-harness runtime: no matching daemon found; running in-process\n");const p=await createRuntime();if(t.serveOpenAi)return clearTimeout(r),void await j(p,t);if(!t.shouldRunRequest)return void process.stdout.write(C(e,s));await async function runInProcessRequest(e,t,r){t.trace&&e.subscribe(e=>{const t=a(e);t&&process.stdout.write(`trace:${t.agentId}:${t.label}${h(t.detail)}\n`)}),e.subscribe(e=>{const t=w(e,r);t&&process.stdout.write(`${t}\n`)});const o=await e.request({input:t.prompt,agentId:t.agentId,sessionId:t.sessionId,toolCall:t.toolId?{toolId:t.toolId,args:t.toolArgs}:void 0,workflow:t.workflowRunId?{workflowId:t.workflowRunId,input:t.prompt}:void 0});if(t.trace||t.traceJson){const r=e.getRun(o.requestId),s=r?n(r):[];t.traceJson&&process.stdout.write(`${JSON.stringify({trace:s})}\n`)}process.stdout.write(`${o.output}\n`)}(p,t,c)}finally{clearTimeout(r)}}function createQualityReviewModel(e){const t=function readQualityModelRef(e){const t=isRecord(e)?e:{},r=isRecord(t.reviewer)?t.reviewer:t;return"string"==typeof r.modelRef&&r.modelRef.trim()?r.modelRef.trim():void 0}(e.runtime.quality),o=t?e.models.get(t):void 0,s=o?r(o):void 0;return function isQualityReviewModel(e){return isRecord(e)&&"function"==typeof e.invoke}(s)?s:void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function createCliWorkflowAdapter(e,t){return s({nodeResolvers:{tools:async({id:t,node:r,request:o,requestId:s,sessionId:i,state:a,workspace:n})=>{return(await e.invoke({toolId:t,args:(u=r.config,l=o.input,d=a.outputs,!0===u?.inputFromState?{...u,requestInput:l,outputs:d}:u&&"requiredInput"in u?u.requiredInput:u&&("args"in u||"cwd"in u||"timeoutMs"in u)?u:"object"==typeof l&&null!==l?l:{}),context:{workspaceRoot:n.root,requestId:s,sessionId:i,agentId:`workflow:${r.id}`,approvalIds:readApprovalIds(o.metadata)}})).output;var u,l,d},agents:async({id:e,node:r,request:o,sessionId:s,state:i})=>{var a,n,u,l;return(await t().request({input:(a=e,n=o.input,u=i.outputs,l=r.config,[`Workflow node agents.${a}: synthesize the workflow evidence into the requested final output.`,`Original request: ${"string"==typeof n?n:JSON.stringify(n)}`,"Requirements:","- Produce the final answer now; do not ask follow-up questions.","- Match the original request language unless workflow config explicitly says otherwise.","- Use only the workflow outputs as evidence; call out uncertainty directly.",...l?[`Workflow node config: ${JSON.stringify(l)}`]:[],"Prior workflow outputs:",JSON.stringify(u)].join("\n")),agentId:e,sessionId:s,metadata:o.metadata})).output}}})}function readApprovalIds(e){const t=e?.approvalIds??e?.approvalId;return"string"==typeof t&&t.trim()?[t.trim()]:Array.isArray(t)?t.filter(e=>"string"==typeof e&&e.trim().length>0):void 0}(function isCliEntrypoint(){const r=process.argv[1];if(!r)return!1;try{return e(t(import.meta.url))===e(r)}catch{return!1}})()&&runCli().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1});
2
+ import{realpathSync as e}from"node:fs";import{fileURLToPath as t}from"node:url";import{createBackendModel as r,createDeepAgentsAdapter as o}from"@stable-harness/adapter-deepagents";import{createLangGraphWorkflowAdapter as s}from"@stable-harness/adapter-langgraph";import{createStableHarnessRuntime as i}from"@stable-harness/core";import{projectEvent as a,projectRuntimeTrace as n}from"@stable-harness/core";import{createInMemoryApprovalQueue as u}from"@stable-harness/governance";import{createModuleToolGateway as l}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as d}from"@stable-harness/workspace-yaml";import{helpText as c,parseArgs as p}from"./args.js";import{buildWorkspaceArtifact as m}from"./build.js";import{runConsole as f}from"./console/session.js";import{formatCliRuntimeEvent as w,readCliEventViewConfig as v,shouldEnableCliProgressNarration as g}from"./event-view.js";import{initWorkspace as y}from"./init.js";import{runRequestThroughDaemon as I}from"./daemon/client.js";import{ensureCliMemoryServices as R}from"./memory/lifecycle.js";import{createCliMemoryProviders as k}from"./memory/providers.js";import{formatDetail as h,inspectWorkflow as b,renderAgent as q,renderWorkflow as A,workspaceStatus as C}from"./output.js";import{serveProtocol as j,stopProtocol as M}from"./server.js";export async function runCli(e=process.argv.slice(2)){const t=p(e);if(t.help)return void process.stdout.write(c());const r=setTimeout(()=>{process.stderr.write(`stable-harness request timed out after ${t.timeoutMs}ms\n`),process.exit(124)},t.timeoutMs),s=t.workspaceRoot;try{if("init"===t.command)return void process.stdout.write(await y(t.prompt||s));const e=await d(s);if(t.workflowRenderId)return void process.stdout.write(A(e,t.workflowRenderId));if(t.workflowInspectId)return void process.stdout.write(b(e,t.workflowInspectId));if(t.agentRenderId)return void process.stdout.write(q(e,t.agentRenderId));if("build"===t.command)return void process.stdout.write(await m({workspace:e,workspaceRoot:s,outputDir:t.buildOutput,target:t.buildTarget}));if("stop"===t.command)return clearTimeout(r),void await M(e,t);const c=v(e.runtime),createRuntime=()=>async function createCliRuntime(e,t,r){const s=await l({tools:e.tools.values(),options:{betterCall:{mode:"repair"}}}),a=u(),n=await async function createCliMemoryProvidersForCommand(e,t){return"console"===t.command||t.serveOpenAi||t.shouldRunRequest&&!t.toolId?(await R(e),k(e)):[]}(e,t);let d;return d=i({workspace:e,toolGateway:s,approvals:a,memoryProviders:n,adapters:[o()],workflowAdapters:[createCliWorkflowAdapter(s,()=>d)],progressNarration:g(r,e.runtime)?{enabled:!0,style:"cli"}:void 0,qualityReviewModel:createQualityReviewModel(e)}),d}(e,t,c);if("console"===t.command)return clearTimeout(r),void await f({args:t,runtimePolicy:e.runtime,eventView:c,createRuntime:createRuntime});if(t.shouldRunRequest&&!t.serveOpenAi&&await I({args:t,runtimePolicy:e.runtime,eventView:c}))return;t.shouldRunRequest&&!t.serveOpenAi&&process.stderr.write("stable-harness runtime: no matching daemon found; running in-process\n");const p=await createRuntime();if(t.serveOpenAi)return clearTimeout(r),void await j(p,t);if(!t.shouldRunRequest)return void process.stdout.write(C(e,s));await async function runInProcessRequest(e,t,r){t.trace&&e.subscribe(e=>{const t=a(e);t&&process.stdout.write(`trace:${t.agentId}:${t.label}${h(t.detail)}\n`)}),e.subscribe(e=>{const t=w(e,r);t&&process.stdout.write(`${t}\n`)});const o=await e.request({input:t.prompt,agentId:t.agentId,sessionId:t.sessionId,toolCall:t.toolId?{toolId:t.toolId,args:t.toolArgs}:void 0,workflow:t.workflowRunId?{workflowId:t.workflowRunId,input:t.prompt}:void 0});if(t.trace||t.traceJson){const r=e.getRun(o.requestId),s=r?n(r):[];t.traceJson&&process.stdout.write(`${JSON.stringify({trace:s})}\n`)}process.stdout.write(`${o.output}\n`)}(p,t,c)}finally{clearTimeout(r)}}function createQualityReviewModel(e){const t=function readQualityModelRef(e){const t=isRecord(e)?e:{},r=isRecord(t.reviewer)?t.reviewer:t;return"string"==typeof r.modelRef&&r.modelRef.trim()?r.modelRef.trim():void 0}(e.runtime.quality),o=t?e.models.get(t):void 0,s=o?r(o):void 0;return function isQualityReviewModel(e){return isRecord(e)&&"function"==typeof e.invoke}(s)?s:void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function createCliWorkflowAdapter(e,t){return s({nodeResolvers:{tools:async({id:t,node:r,request:o,requestId:s,sessionId:i,state:a,workspace:n})=>{return(await e.invoke({toolId:t,args:(u=r.config,l=o.input,d=a.outputs,!0===u?.inputFromState?{...u,requestInput:l,outputs:d}:u&&"requiredInput"in u?u.requiredInput:u&&("args"in u||"cwd"in u||"timeoutMs"in u)?u:"object"==typeof l&&null!==l?l:{}),context:{workspaceRoot:n.root,requestId:s,sessionId:i,agentId:`workflow:${r.id}`,approvalIds:readApprovalIds(o.metadata)}})).output;var u,l,d},agents:async({id:e,node:r,request:o,sessionId:s,state:i})=>{var a,n,u,l;return(await t().request({input:(a=e,n=o.input,u=i.outputs,l=r.config,[`Workflow node agents.${a}: synthesize the workflow evidence into the requested final output.`,`Original request: ${"string"==typeof n?n:JSON.stringify(n)}`,"Requirements:","- Produce the final answer now; do not ask follow-up questions.","- Match the original request language unless workflow config explicitly says otherwise.","- Use only the workflow outputs as evidence; call out uncertainty directly.",...l?[`Workflow node config: ${JSON.stringify(l)}`]:[],"Prior workflow outputs:",JSON.stringify(u)].join("\n")),agentId:e,sessionId:s,metadata:o.metadata})).output}}})}function readApprovalIds(e){const t=e?.approvalIds??e?.approvalId;return"string"==typeof t&&t.trim()?[t.trim()]:Array.isArray(t)?t.filter(e=>"string"==typeof e&&e.trim().length>0):void 0}(function isCliEntrypoint(){const r=process.argv[1];if(!r)return!1;try{return e(t(import.meta.url))===e(r)}catch{return!1}})()&&runCli().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1});
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{createInterface as s}from"node:readline/promises";import{stdin as n,stdout as t}from"node:process";import{daemonBaseUrl as o,isDaemonAvailable as i}from"../daemon/client.js";import{formatCliRuntimeEvent as a}from"../event-view.js";export async function runConsole(r){const c=await async function createConsoleClient(e){const s=o(e.runtimePolicy);return await i(s,e.args.workspaceRoot)?function createDaemonConsoleClient(e,s){return{mode:"daemon",baseUrl:e,request:s=>async function postJson(e,s){const n=await fetch(e,{method:"POST",body:JSON.stringify(s)});return await n.json()}(`${e}/requests`,s),health:async()=>(await getJson(`${e}/health`)).ok?"ok":"unhealthy",sessions:()=>getJson(`${e}/sessions`),requests:s=>getJson(`${e}/requests${s?`?sessionId=${encodeURIComponent(s)}`:""}`),memories:s=>getJson(`${e}/memories${s?`?namespace=${encodeURIComponent(s)}`:""}`),clearSession:async s=>(await async function deleteJson(e){const s=await fetch(e,{method:"DELETE"});return await s.json()}(`${e}/sessions/${encodeURIComponent(s)}`)).deletedCount,debug:()=>getJson(`${e}/inspect`),stream:(n,t)=>function streamDaemonEvents(e,s,n,t){const o=new AbortController;return fetch(`${e}/events?requestId=${encodeURIComponent(s)}`,{signal:o.signal}).then(async e=>{const s=e.body?.getReader();if(!s)return;let o="";for(;;){const{value:e,done:i}=await s.read();if(i)return;o+=Buffer.from(e).toString("utf8");const r=o.split("\n\n");o=r.pop()??"";for(const e of r){const s=e.split("\n").find(e=>e.startsWith("data: "))?.slice(6);if(!s)continue;const o=a(JSON.parse(s),n);o&&t(o)}}}).catch(e=>{o.signal.aborted||process.stderr.write(`stable-harness console event stream failed: ${String(e)}\n`)}),{close:()=>o.abort()}}(e,n,s,t)}}(s,e.eventView):function createLocalConsoleClient(e,s){return{mode:"local",request:s=>e.request(s),health:async()=>"ok",sessions:async()=>e.listSessions(),requests:async s=>e.listRequests(s?{sessionId:s}:void 0),memories:async s=>e.listMemories(s?{namespace:s}:{}),clearSession:async s=>e.deleteSession(s).deletedCount,debug:async()=>e.inspect(),stream:(n,t)=>({close:e.subscribe(e=>{if(e.requestId===n){const n=a(e,s);n&&t(n)}})})}}(await e.createRuntime(),e.eventView)}(r);let d=r.args.sessionId??e(),l=r.args.agentId;process.stderr.write(`stable-harness console: ${"daemon"===c.mode?`connected to daemon at ${c.baseUrl}`:"no matching daemon found; running in-process"}\n`),process.stdout.write(`session ${d}\n`);const u=s({input:n,output:t,terminal:process.stdout.isTTY});try{const e=process.stdout.isTTY?await async function runInteractiveLoop(e){let{sessionId:s,agentId:n}=e;for(;;){const t=await e.rl.question("stable> "),o=await handleConsoleLine({client:e.client,line:t.trim(),sessionId:s,agentId:n,eventView:e.eventView});if(o.done)return o;s=o.sessionId,n=o.agentId}}({rl:u,client:c,sessionId:d,agentId:l,eventView:r.eventView}):await async function runPipedLoop(e){let{sessionId:s,agentId:n}=e;for await(const t of e.rl){const o=await handleConsoleLine({client:e.client,line:t.trim(),sessionId:s,agentId:n,eventView:e.eventView});if(o.done)return o;s=o.sessionId,n=o.agentId}return{done:!0,sessionId:s,agentId:n}}({rl:u,client:c,sessionId:d,agentId:l,eventView:r.eventView});d=e.sessionId,l=e.agentId}catch(e){if(!function isEndOfInput(e){return e instanceof Error&&e.message.includes("closed")}(e))throw e}finally{u.close()}}async function handleConsoleLine(s){const n=function parseConsoleCommand(e){if(!e.startsWith("/"))return;const[s="",...n]=e.slice(1).trim().split(/\s+/u);return{name:s,args:n.join(" ").trim()}}(s.line);return s.line?n?await async function runConsoleCommand(s){const{client:n,command:t}=s;if("exit"===t.name||"quit"===t.name)return{...s,done:!0};if("help"===t.name)!function printHelp(){process.stdout.write(["/help Show console commands.","/session [id] Show or switch session.","/new Create and switch to a new session.","/sessions List known sessions.","/agent [id] Show or switch selected agent.","/tool <id> [json] Invoke a tool in the current session.","/requests [all] List current-session or all requests.","/memory [namespace] List memory records.","/clear Delete current session requests.","/health Check runtime health.","/debug Print runtime inspection JSON.","/exit Leave console.",""].join("\n"))}();else if("health"===t.name)process.stdout.write(`${await n.health()}\n`);else if("debug"===t.name)process.stdout.write(`${JSON.stringify(await n.debug())}\n`);else{if("session"===t.name)return switchSession(s,t.args);if("new"===t.name)return switchSession(s,e());if("agent"===t.name)return function switchAgent(e,s){const n=s.trim()||void 0;return process.stdout.write(`agent ${n??"default"}\n`),{done:!1,sessionId:e.sessionId,agentId:n}}(s,t.args);"sessions"===t.name?printJsonLines(await n.sessions(),"sessionId"):"requests"===t.name?printJsonLines(await n.requests("all"===t.args?void 0:s.sessionId),"requestId"):"memory"===t.name||"memories"===t.name?printJsonLines(await n.memories(t.args||void 0),"id"):"clear"===t.name?process.stdout.write(`cleared ${await n.clearSession(s.sessionId)} requests from ${s.sessionId}\n`):"tool"===t.name?await async function runConsoleTool(e){const s=e.command.args.match(/^(\S+)(?:\s+([\s\S]+))?$/u);s?.[1]?await runConsoleRequest(e.client,{input:"",sessionId:e.sessionId,...e.agentId?{agentId:e.agentId}:{},toolCall:{toolId:s[1],args:s[2]?JSON.parse(s[2]):{}}}):process.stdout.write("usage: /tool <tool-id> [json-args]\n")}(s):process.stdout.write(`unknown command: /${t.name}\n`)}return{done:!1,sessionId:s.sessionId,agentId:s.agentId}}({...s,command:n}):(await runConsoleRequest(s.client,{input:s.line,sessionId:s.sessionId,...s.agentId?{agentId:s.agentId}:{}}),{done:!1,sessionId:s.sessionId,agentId:s.agentId}):{done:!1,sessionId:s.sessionId,agentId:s.agentId}}async function runConsoleRequest(s,n){const t=n.requestId??e(),o=s.stream(t,e=>process.stdout.write(`${e}\n`));try{const e=await s.request({...n,requestId:t});process.stdout.write(`${e.output}\n`)}finally{o.close()}}function switchSession(e,s){const n=s.trim()||e.sessionId;return process.stdout.write(`session ${n}\n`),{done:!1,sessionId:n,agentId:e.agentId}}function printJsonLines(e,s){if(0!==e.length)for(const n of e){const e="object"==typeof n&&n?n:{};process.stdout.write(`${String(e[s]??"")} ${JSON.stringify(n)}\n`)}else process.stdout.write("none\n")}async function getJson(e){const s=await fetch(e);return await s.json()}
1
+ import{randomUUID as e}from"node:crypto";import{createInterface as s}from"node:readline/promises";import{stdin as n,stdout as t}from"node:process";import{daemonBaseUrl as o,isDaemonAvailable as i}from"../daemon/client.js";import{formatCliRuntimeEvent as r}from"../event-view.js";export async function runConsole(a){const c=await async function createConsoleClient(e){const s=o(e.runtimePolicy);return await i(s,e.args.workspaceRoot)?function createDaemonConsoleClient(e,s){return{mode:"daemon",baseUrl:e,request:s=>async function postJson(e,s){const n=await fetch(e,{method:"POST",body:JSON.stringify(s)});if(!n.ok)throw new Error(`HTTP ${n.status}`);return await n.json()}(`${e}/requests`,s),health:async()=>(await getJson(`${e}/health`)).ok?"ok":"unhealthy",sessions:()=>getJson(`${e}/sessions`),requests:s=>getJson(`${e}/requests${s?`?sessionId=${encodeURIComponent(s)}`:""}`),inspectRequest:async s=>{const n=await fetch(`${e}/requests/${encodeURIComponent(s)}`);return n.ok?await n.json():void 0},memories:s=>getJson(`${e}/memories${s?`?namespace=${encodeURIComponent(s)}`:""}`),clearSession:async s=>(await async function deleteJson(e){const s=await fetch(e,{method:"DELETE"});if(!s.ok)throw new Error(`HTTP ${s.status}`);return await s.json()}(`${e}/sessions/${encodeURIComponent(s)}`)).deletedCount,debug:()=>getJson(`${e}/inspect`),stream:(n,t)=>function streamDaemonEvents(e,s,n,t){const o=new AbortController;return fetch(`${e}/events?requestId=${encodeURIComponent(s)}`,{signal:o.signal}).then(async e=>{const s=e.body?.getReader();if(!s)return;let o="";for(;;){const{value:e,done:i}=await s.read();if(i)return;o+=Buffer.from(e).toString("utf8");const a=o.split("\n\n");o=a.pop()??"";for(const e of a){const s=e.split("\n").find(e=>e.startsWith("data: "))?.slice(6);if(!s)continue;const o=r(JSON.parse(s),n);o&&t(o)}}}).catch(e=>{o.signal.aborted||process.stderr.write(`stable-harness console event stream failed: ${String(e)}\n`)}),{close:()=>o.abort()}}(e,n,s,t)}}(s,e.eventView):function createLocalConsoleClient(e,s){return{mode:"local",request:s=>e.request(s),health:async()=>"ok",sessions:async()=>e.listSessions(),requests:async s=>e.listRequests(s?{sessionId:s}:void 0),inspectRequest:async s=>e.inspectRequest(s),memories:async s=>e.listMemories(s?{namespace:s}:{}),clearSession:async s=>e.deleteSession(s).deletedCount,debug:async()=>e.inspect(),stream:(n,t)=>({close:e.subscribe(e=>{if(e.requestId===n){const n=r(e,s);n&&t(n)}})})}}(await e.createRuntime(),e.eventView)}(a);let d=a.args.sessionId??e(),u=a.args.agentId;process.stderr.write(`stable-harness console: ${"daemon"===c.mode?`connected to daemon at ${c.baseUrl}`:"no matching daemon found; running in-process"}\n`),process.stdout.write(`session ${d}\n`);const l=s({input:n,output:t,terminal:process.stdout.isTTY});try{const e=process.stdout.isTTY?await async function runInteractiveLoop(e){let{sessionId:s,agentId:n}=e;for(;;){const t=await e.rl.question("stable> "),o=await handleConsoleLineSafely({client:e.client,line:t.trim(),sessionId:s,agentId:n,eventView:e.eventView});if(o.done)return o;s=o.sessionId,n=o.agentId}}({rl:l,client:c,sessionId:d,agentId:u,eventView:a.eventView}):await async function runPipedLoop(e){let{sessionId:s,agentId:n}=e;for await(const t of e.rl){const o=await handleConsoleLineSafely({client:e.client,line:t.trim(),sessionId:s,agentId:n,eventView:e.eventView});if(o.done)return o;s=o.sessionId,n=o.agentId}return{done:!0,sessionId:s,agentId:n}}({rl:l,client:c,sessionId:d,agentId:u,eventView:a.eventView});d=e.sessionId,u=e.agentId}catch(e){if(!function isEndOfInput(e){return e instanceof Error&&e.message.includes("closed")}(e))throw e}finally{l.close()}}async function handleConsoleLineSafely(s){try{return await async function handleConsoleLine(s){const n=function parseConsoleCommand(e){if(!e.startsWith("/"))return;const[s="",...n]=e.slice(1).trim().split(/\s+/u);return{name:s,args:n.join(" ").trim()}}(s.line);return s.line?n?await async function runConsoleCommand(s){const{client:n,command:t}=s;if("exit"===t.name||"quit"===t.name)return{...s,done:!0};if("help"===t.name)!function printHelp(){process.stdout.write(["/help Show console commands.","/session [id] Show or switch session.","/new Create and switch to a new session.","/sessions List known sessions.","/agent [id] Show or switch selected agent.","/tool <id> [json] Invoke a tool in the current session.","/requests [all] List current-session or all requests.","/memory [namespace] List memory records.","/clear Delete current session requests.","/health Check runtime health.","/debug Print runtime inspection JSON.","/exit Leave console.",""].join("\n"))}();else if("health"===t.name)process.stdout.write(`${await n.health()}\n`);else if("debug"===t.name)process.stdout.write(`${JSON.stringify(await n.debug())}\n`);else{if("session"===t.name)return switchSession(s,t.args);if("new"===t.name)return switchSession(s,e());if("agent"===t.name)return function switchAgent(e,s){const n=s.trim()||void 0;return process.stdout.write(`agent ${n??"default"}\n`),{done:!1,sessionId:e.sessionId,agentId:n}}(s,t.args);"sessions"===t.name?printJsonLines(await n.sessions(),"sessionId"):"requests"===t.name?printJsonLines(await n.requests("all"===t.args?void 0:s.sessionId),"requestId"):"memory"===t.name||"memories"===t.name?printJsonLines(await n.memories(t.args||void 0),"id"):"clear"===t.name?process.stdout.write(`cleared ${await n.clearSession(s.sessionId)} requests from ${s.sessionId}\n`):"tool"===t.name?await async function runConsoleTool(e){const s=e.command.args.match(/^(\S+)(?:\s+([\s\S]+))?$/u);s?.[1]?await runConsoleRequest(e.client,{input:"",sessionId:e.sessionId,...e.agentId?{agentId:e.agentId}:{},toolCall:{toolId:s[1],args:s[2]?JSON.parse(s[2]):{}}}):process.stdout.write("usage: /tool <tool-id> [json-args]\n")}(s):process.stdout.write(`unknown command: /${t.name}\n`)}return{done:!1,sessionId:s.sessionId,agentId:s.agentId}}({...s,command:n}):(await runConsoleRequest(s.client,await async function withSessionHistory(e,s){if(!s.sessionId||s.toolCall||s.workflow)return s;const n=await async function buildSessionHistory(e,s){const n=(await e.requests(s)).filter(e=>"completed"===e.state).slice(-6),t=[];for(const s of n){const n=await e.inspectRequest(s.requestId),o=readHistoryInput(n),i=n?.output?.trim();o&&i&&t.push({role:"user",content:o},{role:"assistant",content:i})}return t.slice(-12)}(e,s.sessionId);return{...s,metadata:{...s.metadata,consoleSession:!0,openaiMessages:[...n,{role:"user",content:s.input}],openaiSessionHistory:n.length>0}}}(s.client,{input:s.line,sessionId:s.sessionId,...s.agentId?{agentId:s.agentId}:{}})),{done:!1,sessionId:s.sessionId,agentId:s.agentId}):{done:!1,sessionId:s.sessionId,agentId:s.agentId}}(s)}catch(e){return process.stdout.write(`error: ${function errorMessage(e){return e instanceof Error?e.message:String(e)}(e)}\n`),{done:!1,sessionId:s.sessionId,agentId:s.agentId}}}async function runConsoleRequest(s,n){const t=n.requestId??e(),o=s.stream(t,e=>process.stdout.write(`${e}\n`));try{const e=await s.request({...n,requestId:t});process.stdout.write(`${e.output}\n`)}finally{o.close()}}function readHistoryInput(e){const s=Array.isArray(e?.metadata?.openaiMessages)?e.metadata.openaiMessages:[],n=s.map(e=>"object"==typeof e&&e?e:{}).filter(e=>"user"===e.role&&"string"==typeof e.content&&e.content.trim()).at(-1)?.content;return"string"==typeof n?n:e?.input.trim()||void 0}function switchSession(e,s){const n=s.trim()||e.sessionId;return process.stdout.write(`session ${n}\n`),{done:!1,sessionId:n,agentId:e.agentId}}function printJsonLines(e,s){if(0!==e.length)for(const n of e){const e="object"==typeof n&&n?n:{};process.stdout.write(`${String(e[s]??"")} ${JSON.stringify(n)}\n`)}else process.stdout.write("none\n")}async function getJson(e){const s=await fetch(e);if(!s.ok)throw new Error(`HTTP ${s.status}`);return await s.json()}
@@ -1 +1 @@
1
- import{execFile as t}from"node:child_process";import{promisify as r}from"node:util";import{createHttpServer as e,createOpenAiCompatibleHttpServer as n}from"@stable-harness/protocols";import{startOfficialLangGraphServer as o}from"./langgraph-official.js";const s="127.0.0.1",i=r(t);export async function serveProtocol(t,r){const e=createConfiguredServers(t,r),n=[];let o=0;for(const r of e)if("http"===r.kind){if(!await listen(r)){process.stdout.write(`stable-harness ${r.protocol} API already running on ${serverUrl(r)}\n`);continue}n.push(()=>closeHttpServer(r.server)),o+=1;const t=r.server.address(),e="object"==typeof t&&t?t.port:r.port;process.stdout.write(`stable-harness ${r.protocol} API listening on ${serverUrl({...r,port:e})}\n`)}else{const e=await startLangGraphServer(t,r);if(!e){process.stdout.write(`stable-harness ${r.protocol} API already running on http://${r.config.host}:${r.config.port}\n`);continue}n.push(e.cleanup),o+=1,process.stdout.write(`stable-harness ${r.protocol} API listening on ${e.url}\n`)}0!==o&&await async function waitForShutdown(t){const r=setInterval(()=>{},864e5);await new Promise(e=>{const shutdown=()=>{clearInterval(r),Promise.allSettled(t.map(t=>t())).finally(()=>process.exit(0))};process.once("SIGINT",shutdown),process.once("SIGTERM",shutdown)})}(n)}export async function stopProtocol(t,r){const e=createConfiguredServers({getRuntimePolicy:()=>t.runtime},r).map(t=>"http"===t.kind?{protocol:t.protocol,host:t.host,port:t.port}:{protocol:t.protocol,host:t.config.host,port:t.config.port}),n=await Promise.all(e.map(async t=>({target:t,pids:await stableHarnessListenerPids(t.port)}))),o=[...new Set(n.flatMap(t=>t.pids))];for(const t of o)process.kill(t,"SIGTERM");for(const{target:t,pids:r}of n)0!==r.length?process.stdout.write(`stable-harness ${t.protocol} API stopped on ${t.host}:${t.port} pid=${r.join(",")}\n`):process.stdout.write(`stable-harness ${t.protocol} API not running on ${t.host}:${t.port}\n`)}function createConfiguredServers(t,r){const e=readRecord(t.getRuntimePolicy().protocols)??{},n=protocolConfig(e,"stableRuntime","stable-runtime","http")??{},o=protocolConfig(e,"openaiCompatible","openai-compatible","openai")??{},s=protocolConfig(e,"langgraph")??{};return[...enabled(n)?[stableRuntimeServer(t,n,r)]:[],...enabled(o)?[openAiServer(t,o,r)]:[],...enabled(s)?[langGraphServer(s)]:[]]}function stableRuntimeServer(t,r,n){return{kind:"http",protocol:"stable-runtime",server:e(t),host:n.host??configString(r.host)??s,port:configNumber(r.port)??configNumber(process.env.STABLE_HARNESS_RUNTIME_PORT)??8641}}function openAiServer(t,r,e){const o=configString(r.host)??s,i=e.port??configNumber(r.port)??8642,a=e.host??o,c=configString(r.bearerToken)??configString(r.apiKey)??e.apiKey;return{kind:"http",protocol:"openai-compatible",server:n(t,{bearerToken:c}),host:a,port:i,...c?{bearerToken:c}:{}}}function langGraphServer(t){const r=configString(t.host)??s,e=configNumber(t.port)??2024,n=function configStringArray(t){if(Array.isArray(t)&&t.every(t=>"string"==typeof t))return t.filter(t=>t.trim()).map(t=>t.trim())}(t.exposeAgents);return{kind:"langgraph",protocol:"langgraph-compatible",config:{host:r,port:e,nWorkers:configNumber(t.nWorkers)??10,...n?{exposeAgents:n}:{},...void 0!==t.env?{env:t.env}:{},...void 0!==t.envFile?{envFile:t.envFile}:{}}}}function protocolConfig(t,...r){for(const e of r){const r=readRecord(t[e]);if(r)return r}}function enabled(t){return!1!==t.enabled}function configString(t){if("string"!=typeof t||!t.trim())return;const r=t.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return r?process.env[r[1]]??r[2]:t}function configNumber(t){return"number"==typeof t&&Number.isFinite(t)?t:"string"==typeof t&&t.trim()?Number(t):void 0}function readRecord(t){return"object"!=typeof t||null===t||Array.isArray(t)?void 0:t}async function listen(t){try{return await new Promise((r,e)=>{t.server.once("error",e),t.server.listen(t.port,t.host,()=>{t.server.off("error",e),r()})}),!0}catch(r){if(isAddressInUse(r)&&await async function isHttpServerAlreadyRunning(t){return"stable-runtime"===t.protocol?await async function isStableRuntimeServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/inspect`);return Array.isArray(r?.agents)&&Array.isArray(r?.runs)}(t):await async function isOpenAiServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/v1/capabilities`,{...t.bearerToken?{authorization:`Bearer ${t.bearerToken}`}:{}});return"stable_harness.capabilities"===r?.object}(t)}(t))return!1;throw portConflictError(r,t.protocol,t.host,t.port)}}async function startLangGraphServer(t,r){if(!await isLangGraphServerAlreadyRunning(r))try{return await o(t,r.config)}catch(t){if(isAddressInUse(t)&&await isLangGraphServerAlreadyRunning(r))return;throw portConflictError(t,r.protocol,r.config.host,r.config.port)}}function serverUrl(t){const r=`http://${t.host}:${t.port}`;return"openai-compatible"===t.protocol?`${r}/v1`:r}async function isLangGraphServerAlreadyRunning(t){const r=await fetchJson(`http://${t.config.host}:${t.config.port}/ok`);return!0===r?.ok}async function fetchJson(t,r={}){try{const e=await fetch(t,{headers:r});if(!e.ok)return;return await e.json()}catch{return}}function isAddressInUse(t){return"EADDRINUSE"===function readErrorCode(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}(t)||String(t).includes("EADDRINUSE")}function portConflictError(t,r,e,n){return isAddressInUse(t)?new Error([`stable-harness ${r} port is already in use: ${e}:${n}.`,`Use --port <port>, update config/runtime/workspace.yaml, or stop the process currently listening on ${e}:${n}.`].join("\n")):t}async function stableHarnessListenerPids(t){const r=await async function listenerPids(t){try{const{stdout:r}=await i("lsof",[`-tiTCP:${t}`,"-sTCP:LISTEN"]);return r.split(/\s+/u).map(t=>Number(t)).filter(t=>Number.isInteger(t)&&t>0)}catch{return[]}}(t);return(await Promise.all(r.map(async t=>{const r=await async function processCommand(t){try{const{stdout:r}=await i("ps",["-p",String(t),"-o","command="]);return r.trim()}catch{return""}}(t);return isStableHarnessStartCommand(r)?t:void 0}))).filter(t=>"number"==typeof t)}export function isStableHarnessStartCommand(t){if(function hasUnsafeCommandCharacters(t){return/[\u0000-\u001F\u007F;|`&<>]/u.test(t)}(t))return!1;const r=function splitCommandLine(t){const r=[];let e,n="";for(const o of t)'"'!==o&&"'"!==o||void 0!==e?o!==e?/\s/u.test(o)&&void 0===e?n&&(r.push(n),n=""):n+=o:e=void 0:e=o;return n&&r.push(n),r}(t),e=function stableHarnessCommandIndex(t){return isStableHarnessExecutableToken(t[0]??"")?0:function isNodeExecutableToken(t){const r=t.split(/[\\/]/u).at(-1);return"node"===r||"nodejs"===r}(t[0]??"")&&(isStableHarnessExecutableToken(t[1]??"")||function isStableHarnessScriptToken(t){if(hasTraversalSegment(t))return!1;const r=t.replaceAll("\\","/");return r.includes("/stable-harness/")&&r.endsWith("/packages/cli/dist/src/cli.js")}(t[1]??""))?1:-1}(r);return e>=0&&r.slice(e+1).includes("start")}function isStableHarnessExecutableToken(t){if(hasTraversalSegment(t))return!1;const r=t.split(/[\\/]/u).at(-1);return"stable-harness"===r||"botbotgo"===r}function hasTraversalSegment(t){return t.split(/[\\/]/u).some(t=>"."===t||".."===t)}async function closeHttpServer(t){await new Promise((r,e)=>{t.close(t=>{t?e(t):r()})})}
1
+ import{execFile as t}from"node:child_process";import{promisify as r}from"node:util";import{createAgentProtocolHttpServer as o,createHttpServer as e,createOpenAiCompatibleHttpServer as n}from"@stable-harness/protocols";import{startOfficialLangGraphServer as s}from"./langgraph-official.js";const i="127.0.0.1",a=r(t);export async function serveProtocol(t,r){const o=createConfiguredServers(t,r),e=[];let n=0;for(const r of o)if("http"===r.kind){if(!await listen(r)){process.stdout.write(`stable-harness ${r.protocol} API already running on ${serverUrl(r)}\n`);continue}e.push(()=>closeHttpServer(r.server)),n+=1;const t=r.server.address(),o="object"==typeof t&&t?t.port:r.port;process.stdout.write(`stable-harness ${r.protocol} API listening on ${serverUrl({...r,port:o})}\n`)}else{const o=await startLangGraphServer(t,r);if(!o){process.stdout.write(`stable-harness ${r.protocol} API already running on http://${r.config.host}:${r.config.port}\n`);continue}e.push(o.cleanup),n+=1,process.stdout.write(`stable-harness ${r.protocol} API listening on ${o.url}\n`)}0!==n&&await async function waitForShutdown(t){const r=setInterval(()=>{},864e5);await new Promise(o=>{const shutdown=()=>{clearInterval(r),Promise.allSettled(t.map(t=>t())).finally(()=>process.exit(0))};process.once("SIGINT",shutdown),process.once("SIGTERM",shutdown)})}(e)}export async function stopProtocol(t,r){const o=createConfiguredServers({getRuntimePolicy:()=>t.runtime},r).map(t=>"http"===t.kind?{protocol:t.protocol,host:t.host,port:t.port}:{protocol:t.protocol,host:t.config.host,port:t.config.port}),e=await Promise.all(o.map(async t=>({target:t,pids:await stableHarnessListenerPids(t.port)}))),n=[...new Set(e.flatMap(t=>t.pids))];for(const t of n)process.kill(t,"SIGTERM");for(const{target:t,pids:r}of e)0!==r.length?process.stdout.write(`stable-harness ${t.protocol} API stopped on ${t.host}:${t.port} pid=${r.join(",")}\n`):process.stdout.write(`stable-harness ${t.protocol} API not running on ${t.host}:${t.port}\n`)}function createConfiguredServers(t,r){const o=readRecord(t.getRuntimePolicy().protocols)??{},e=protocolConfig(o,"stableRuntime","stable-runtime","http")??{},n=protocolConfig(o,"openaiCompatible","openai-compatible","openai")??{},s=function agentProtocolConfig(t){const r=protocolConfig(t,"agentProtocols","agent-protocols");if(r)return enabled(r)?r:void 0;const o=["acp","a2a","agui"].filter(r=>enabled(protocolConfig(t,r)??{enabled:!1}));return o.length>0?{protocols:o}:void 0}(o),i=protocolConfig(o,"langgraph")??{};return[...enabled(e)?[stableRuntimeServer(t,e,r)]:[],...enabled(n)?[openAiServer(t,n,r)]:[],...s?[agentProtocolServer(t,s,r)]:[],...enabled(i)?[langGraphServer(i)]:[]]}function stableRuntimeServer(t,r,o){return{kind:"http",protocol:"stable-runtime",server:e(t),host:o.host??configString(r.host)??i,port:configNumber(r.port)??configNumber(process.env.STABLE_HARNESS_RUNTIME_PORT)??8641}}function openAiServer(t,r,o){const e=configString(r.host)??i,s=o.port??configNumber(r.port)??8642,a=o.host??e,c=configString(r.bearerToken)??configString(r.apiKey)??o.apiKey;return{kind:"http",protocol:"openai-compatible",server:n(t,{bearerToken:c}),host:a,port:s,...c?{bearerToken:c}:{}}}function agentProtocolServer(t,r,e){const n=configString(r.host)??i,s=configNumber(r.port)??8650,a=e.host??n;return{kind:"http",protocol:"agent-protocols",server:o(t,{baseUrl:`http://${a}:${s}`,enabledProtocols:configProtocolIds(r.protocols)}),host:a,port:s}}function langGraphServer(t){const r=configString(t.host)??i,o=configNumber(t.port)??2024,e=configStringArray(t.exposeAgents);return{kind:"langgraph",protocol:"langgraph-compatible",config:{host:r,port:o,nWorkers:configNumber(t.nWorkers)??10,...e?{exposeAgents:e}:{},...void 0!==t.env?{env:t.env}:{},...void 0!==t.envFile?{envFile:t.envFile}:{}}}}function protocolConfig(t,...r){for(const o of r){const r=readRecord(t[o]);if(r)return r}}function enabled(t){return!1!==t.enabled}function configString(t){if("string"!=typeof t||!t.trim())return;const r=t.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return r?process.env[r[1]]??r[2]:t}function configNumber(t){return"number"==typeof t&&Number.isFinite(t)?t:"string"==typeof t&&t.trim()?Number(t):void 0}function configStringArray(t){if(Array.isArray(t)&&t.every(t=>"string"==typeof t))return t.filter(t=>t.trim()).map(t=>t.trim())}function configProtocolIds(t){const r=configStringArray(t)?.filter(t=>"acp"===t||"a2a"===t||"agui"===t);return r&&r.length>0?r:void 0}function readRecord(t){return"object"!=typeof t||null===t||Array.isArray(t)?void 0:t}async function listen(t){try{return await new Promise((r,o)=>{t.server.once("error",o),t.server.listen(t.port,t.host,()=>{t.server.off("error",o),r()})}),!0}catch(r){if(isAddressInUse(r)&&await async function isHttpServerAlreadyRunning(t){return"stable-runtime"===t.protocol?await async function isStableRuntimeServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/inspect`);return Array.isArray(r?.agents)&&Array.isArray(r?.runs)}(t):"openai-compatible"===t.protocol?await async function isOpenAiServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/v1/capabilities`,{...t.bearerToken?{authorization:`Bearer ${t.bearerToken}`}:{}});return"stable_harness.capabilities"===r?.object}(t):await async function isAgentProtocolServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/health`);return!0===r?.ok&&Array.isArray(r.protocols)}(t)}(t))return!1;throw portConflictError(r,t.protocol,t.host,t.port)}}async function startLangGraphServer(t,r){if(!await isLangGraphServerAlreadyRunning(r))try{return await s(t,r.config)}catch(t){if(isAddressInUse(t)&&await isLangGraphServerAlreadyRunning(r))return;throw portConflictError(t,r.protocol,r.config.host,r.config.port)}}function serverUrl(t){const r=`http://${t.host}:${t.port}`;return"openai-compatible"===t.protocol?`${r}/v1`:"agent-protocols"===t.protocol?`${r}/acp / ${r}/a2a / ${r}/ag-ui`:r}async function isLangGraphServerAlreadyRunning(t){const r=await fetchJson(`http://${t.config.host}:${t.config.port}/ok`);return!0===r?.ok}async function fetchJson(t,r={}){try{const o=await fetch(t,{headers:r});if(!o.ok)return;return await o.json()}catch{return}}function isAddressInUse(t){return"EADDRINUSE"===function readErrorCode(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}(t)||String(t).includes("EADDRINUSE")}function portConflictError(t,r,o,e){return isAddressInUse(t)?new Error([`stable-harness ${r} port is already in use: ${o}:${e}.`,`Use --port <port>, update config/runtime/workspace.yaml, or stop the process currently listening on ${o}:${e}.`].join("\n")):t}async function stableHarnessListenerPids(t){const r=await async function listenerPids(t){try{const{stdout:r}=await a("lsof",[`-tiTCP:${t}`,"-sTCP:LISTEN"]);return r.split(/\s+/u).map(t=>Number(t)).filter(t=>Number.isInteger(t)&&t>0)}catch{return[]}}(t);return(await Promise.all(r.map(async t=>{const r=await async function processCommand(t){try{const{stdout:r}=await a("ps",["-p",String(t),"-o","command="]);return r.trim()}catch{return""}}(t);return isStableHarnessStartCommand(r)?t:void 0}))).filter(t=>"number"==typeof t)}export function isStableHarnessStartCommand(t){if(function hasUnsafeCommandCharacters(t){return/[\u0000-\u001F\u007F;|`&<>]/u.test(t)}(t))return!1;const r=function splitCommandLine(t){const r=[];let o,e="";for(const n of t)'"'!==n&&"'"!==n||void 0!==o?n!==o?/\s/u.test(n)&&void 0===o?e&&(r.push(e),e=""):e+=n:o=void 0:o=n;return e&&r.push(e),r}(t),o=function stableHarnessCommandIndex(t){return isStableHarnessExecutableToken(t[0]??"")?0:function isNodeExecutableToken(t){const r=t.split(/[\\/]/u).at(-1);return"node"===r||"nodejs"===r}(t[0]??"")&&(isStableHarnessExecutableToken(t[1]??"")||function isStableHarnessScriptToken(t){if(hasTraversalSegment(t))return!1;const r=t.replaceAll("\\","/");return r.includes("/stable-harness/")&&r.endsWith("/packages/cli/dist/src/cli.js")}(t[1]??""))?1:-1}(r);return o>=0&&r.slice(o+1).includes("start")}function isStableHarnessExecutableToken(t){if(hasTraversalSegment(t))return!1;const r=t.split(/[\\/]/u).at(-1);return"stable-harness"===r||"botbotgo"===r}function hasTraversalSegment(t){return t.split(/[\\/]/u).some(t=>"."===t||".."===t)}async function closeHttpServer(t){await new Promise((r,o)=>{t.close(t=>{t?o(t):r()})})}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/cli",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -14,12 +14,12 @@
14
14
  "types": "dist/src/index.d.ts",
15
15
  "peerDependencies": {
16
16
  "@langchain/langgraph-api": "^1.2.1",
17
- "@stable-harness/adapter-deepagents": "0.0.101",
18
- "@stable-harness/adapter-langgraph": "0.0.101",
19
- "@stable-harness/core": "0.0.101",
20
- "@stable-harness/memory": "0.0.101",
21
- "@stable-harness/protocols": "0.0.101",
22
- "@stable-harness/tool-gateway": "0.0.101",
23
- "@stable-harness/workspace-yaml": "0.0.101"
17
+ "@stable-harness/adapter-deepagents": "0.0.103",
18
+ "@stable-harness/adapter-langgraph": "0.0.103",
19
+ "@stable-harness/core": "0.0.103",
20
+ "@stable-harness/memory": "0.0.103",
21
+ "@stable-harness/protocols": "0.0.103",
22
+ "@stable-harness/tool-gateway": "0.0.103",
23
+ "@stable-harness/workspace-yaml": "0.0.103"
24
24
  }
25
25
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -11,7 +11,7 @@
11
11
  ".": "./dist/index.js"
12
12
  },
13
13
  "peerDependencies": {
14
- "@stable-harness/governance": "0.0.101",
15
- "@stable-harness/memory": "0.0.101"
14
+ "@stable-harness/governance": "0.0.103",
15
+ "@stable-harness/memory": "0.0.103"
16
16
  }
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/evaluation",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -10,6 +10,6 @@
10
10
  "main": "dist/src/index.js",
11
11
  "types": "dist/src/index.d.ts",
12
12
  "peerDependencies": {
13
- "@stable-harness/core": "0.0.101"
13
+ "@stable-harness/core": "0.0.103"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/memory",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -0,0 +1,7 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { StableHarnessRuntime } from "@stable-harness/core";
3
+ export type AgentProtocolServerOptions = {
4
+ baseUrl?: string;
5
+ enabledProtocols?: Array<"acp" | "a2a" | "agui">;
6
+ };
7
+ export declare function createAgentProtocolHttpServer(runtime: StableHarnessRuntime, options?: AgentProtocolServerOptions): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
@@ -0,0 +1 @@
1
+ import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,s={}){const a=new Set(s.enabledProtocols??["acp","a2a","agui"]);return e(async(e,n)=>{try{if("GET"===e.method&&"/health"===e.url)return void sendJson(n,200,{ok:!0,protocols:[...a]});if(a.has("a2a")&&await async function handleA2a(e,t,s,a){if("GET"===t.method&&("/.well-known/agent-card.json"===t.url||"/a2a/agent-card.json"===t.url))return sendJson(s,200,function createAgentCard(e,t){const s=e.inspect();return{protocolVersion:"1.0",name:s.workspaceRoot.split(/[\\/]/u).filter(Boolean).at(-1)??"stable-harness",description:"Stable Harness runtime agent endpoint",version:"0.0.0",url:t.baseUrl?`${t.baseUrl.replace(/\/$/u,"")}/a2a`:"/a2a",preferredTransport:"HTTP+JSON",capabilities:{streaming:!0},defaultInputModes:["text/plain"],defaultOutputModes:["text/plain"],skills:s.agents.map(e=>({id:e,name:e,description:e}))}}(e,a)),!0;const n=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&n){const t=e.inspectRequest(n);return sendJson(s,t?200:404,t?{task:toA2aTask(t)}:{error:"task_not_found"}),!0}if("GET"===t.method&&"/a2a/tasks"===t.url)return sendJson(s,200,{tasks:e.listRequests().map(toA2aTaskSummary)}),!0;const r=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);return"POST"===t.method&&r?(e.cancel(r,"a2a_cancel"),sendJson(s,200,{task:e.inspectRequest(r)}),!0):"POST"===t.method&&"/a2a/message:send"===t.url?(sendJson(s,200,{task:toA2aTask(await e.request(toA2aRuntimeRequest(await readJson(t))))}),!0):"POST"===t.method&&"/a2a/message:stream"===t.url?(await streamA2a(e,await readJson(t),s),!0):"POST"===t.method&&"/a2a/rpc"===t.url&&(await async function handleA2aRpc(e,t,s){if("SendMessage"===t.method){const a=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(s,200,jsonRpcResult(t.id,{task:toA2aTask(a)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const a=readString(readRecord(t.params)?.id),n=a?e.inspectRequest(a):void 0;return void sendJson(s,n?200:404,n?jsonRpcResult(t.id,toA2aTask(n)):jsonRpcError(t.id,-32004,"task_not_found"))}"ListTasks"!==t.method?sendJson(s,404,jsonRpcError(t.id,-32601,"method_not_found")):sendJson(s,200,jsonRpcResult(t.id,{tasks:e.listRequests().map(toA2aTaskSummary)}))}else await streamA2a(e,readRecord(t.params)??{},s,t.id)}(e,await readJson(t),s),!0)}(t,e,n,s))return;if(a.has("agui")&&await async function handleAgui(e,t,s){return"GET"===t.method&&"/ag-ui/capabilities"===t.url?(sendJson(s,200,{protocol:"ag-ui",transports:["http+sse"],events:["RunStarted","TextMessageChunk","Custom","RunFinished","RunError"]}),!0):"POST"===t.method&&"/ag-ui/runs"===t.url&&(await async function streamAgui(e,t,s){const a=readString(t.runId)??crypto.randomUUID(),n=readString(t.threadId)??readString(t.sessionId)??`thread-${a}`,r=readPromptText(t);writeSseHeaders(s),writeSse(s,{type:"RunStarted",threadId:n,runId:a,input:{messages:t.messages,input:r}});const o=e.subscribe(e=>{e.requestId===a&&writeSse(s,function toAguiEvent(e){return"runtime.progress.narration"===e.type?{type:"Custom",name:"stable-harness.progress",value:e}:"runtime.tool.direct.started"===e.type?{type:"ToolCallStart",toolCallId:`${e.requestId}:${e.toolId}`,toolCallName:e.toolId}:"runtime.tool.direct.completed"===e.type?{type:"ToolCallResult",messageId:`${e.requestId}-message`,toolCallId:`${e.requestId}:${e.toolId}`,content:e.output,role:"tool"}:{type:"Custom",name:`stable-harness.${e.type}`,value:e}}(e))});try{const o=await e.request({input:r,requestId:a,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),i=`${a}-message`;o.output&&writeSse(s,{type:"TextMessageChunk",messageId:i,role:"assistant",delta:o.output}),writeSse(s,{type:"RunFinished",threadId:n,runId:a,outcome:{type:"completed"===o.state?"success":"interrupt"},result:o.output})}catch(e){writeSse(s,{type:"RunError",threadId:n,runId:a,message:errorMessage(e)})}finally{o(),s.end()}}(e,await readJson(t),s),!0)}(t,e,n))return;if(a.has("acp")&&await async function handleAcp(e,t,s){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(s,200,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"}}),!0;if("POST"===t.method&&"/acp"===t.url){const a=await readJson(t),n=await async function handleAcpMessage(e,t){if(!t.id&&"session/cancel"===t.method){const s=readString(readRecord(t.params)?.sessionId);return void(s&&e.listRequests({sessionId:s,state:"running"}).forEach(t=>e.cancel(t.requestId,"acp_cancel")))}if("initialize"===t.method)return jsonRpcResult(t.id,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"}});if("session/new"===t.method)return jsonRpcResult(t.id,{sessionId:`acp-${crypto.randomUUID()}`});if("session/load"===t.method)return jsonRpcResult(t.id,{sessionId:readString(readRecord(t.params)?.sessionId)});if("session/list"===t.method)return jsonRpcResult(t.id,{sessions:e.listSessions()});if("session/prompt"===t.method){const s=readRecord(t.params)??{},a=await e.request({input:readPromptText(s),sessionId:readString(s.sessionId),agentId:readString(s.agentId),metadata:{protocol:"acp",transport:"http-jsonrpc"}});return jsonRpcResult(t.id,{stopReason:"completed"===a.state?"end_turn":"refusal",_meta:{requestId:a.requestId,output:a.output}})}return jsonRpcError(t.id,-32601,"method_not_found")}(e,a);return n?sendJson(s,200,n):s.writeHead(204).end(),!0}return!1}(t,e,n))return;sendJson(n,404,{error:"not_found"})}catch(e){sendJson(n,400,{error:errorMessage(e)})}})}async function streamA2a(e,t,s,a){const n=toA2aRuntimeRequest(t);writeSseHeaders(s);const r=e.subscribe(e=>{e.requestId===n.requestId&&writeSse(s,{jsonrpc:"2.0",id:a,result:{event:toA2aEvent(e)}})});try{const t=await e.request(n);writeSse(s,{jsonrpc:"2.0",id:a,result:{task:toA2aTask(t),final:!0}})}finally{r(),s.end()}}function toA2aRuntimeRequest(e){const t=readRecord(e.message)??e;return{input:readPromptText(t),requestId:readString(e.requestId)??readString(t.messageId),sessionId:readString(e.taskId)??readString(t.taskId)??readString(t.contextId),agentId:readString(e.agentId)??readString(e.metadata&&readRecord(e.metadata)?.agentId),metadata:{protocol:"a2a",configuration:e.configuration,metadata:e.metadata}}}function toA2aTask(e){const t="state"in e?e.state:e.summary.state,s="output"in e?e.output:e.output??"",a="requestId"in e?e.requestId:e.summary.requestId;return{id:a,contextId:"sessionId"in e?e.sessionId:e.summary.sessionId,status:{state:"completed"===t?"completed":t,message:s?{role:"agent",messageId:`${a}-response`,parts:[{kind:"text",text:s}]}:void 0}}}function toA2aTaskSummary(e){return{id:e.requestId,contextId:e.sessionId,status:{state:e.state},metadata:{agentId:e.agentId}}}function toA2aEvent(e){return{kind:"status-update",taskId:e.requestId,contextId:e.sessionId,status:{state:e.type},metadata:e}}function readPromptText(e){return"string"==typeof e.input?e.input:"string"==typeof e.text?e.text:"string"==typeof e.prompt?e.prompt:Array.isArray(e.parts)?e.parts.map(readPartText).filter(Boolean).join("\n"):Array.isArray(e.messages)?readPromptText(readRecord(e.messages.at(-1))??{}):Array.isArray(e.content)?e.content.map(readPartText).filter(Boolean).join("\n"):"string"==typeof e.content?e.content:""}function readPartText(e){const t=readRecord(e);return t?readString(t.text)??readString(t.content)??"":"string"==typeof e?e:""}function matchPath(e,t){const s=(e??"").match(t);return s?.[1]?decodeURIComponent(s[1]):void 0}function readRecord(e){return"object"!=typeof e||null===e||Array.isArray(e)?void 0:e}function readString(e){return"string"==typeof e&&e.trim()?e:void 0}function jsonRpcResult(e,t){return{jsonrpc:"2.0",id:e,result:t}}function jsonRpcError(e,t,s){return{jsonrpc:"2.0",id:e,error:{code:t,message:s}}}function sendJson(e,t,s){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(s))}function writeSseHeaders(e){e.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive"})}function writeSse(e,t){e.write(`data: ${JSON.stringify(t)}\n\n`)}async function readJson(e){const t=[];for await(const s of e)t.push(Buffer.isBuffer(s)?s:Buffer.from(s));return t.length>0?JSON.parse(Buffer.concat(t).toString("utf8")):{}}function errorMessage(e){return e instanceof Error?e.message:String(e)}
@@ -1,4 +1,6 @@
1
1
  export { createInProcessClient } from "./in-process-client.js";
2
+ export { createAgentProtocolHttpServer } from "./agent-protocols.js";
3
+ export type { AgentProtocolServerOptions } from "./agent-protocols.js";
2
4
  export { createHttpServer } from "./http-server.js";
3
5
  export { createOpenAiCompatibleHttpServer } from "./openai-compatible.js";
4
6
  export type { OpenAiCompatibleServerOptions } from "./openai-compatible.js";
@@ -1 +1 @@
1
- export{createInProcessClient}from"./in-process-client.js";export{createHttpServer}from"./http-server.js";export{createOpenAiCompatibleHttpServer}from"./openai-compatible.js";
1
+ export{createInProcessClient}from"./in-process-client.js";export{createAgentProtocolHttpServer}from"./agent-protocols.js";export{createHttpServer}from"./http-server.js";export{createOpenAiCompatibleHttpServer}from"./openai-compatible.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/protocols",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -10,6 +10,6 @@
10
10
  "main": "dist/src/index.js",
11
11
  "types": "dist/src/index.d.ts",
12
12
  "peerDependencies": {
13
- "@stable-harness/core": "0.0.101"
13
+ "@stable-harness/core": "0.0.103"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/workspace-yaml",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -11,6 +11,6 @@
11
11
  ".": "./dist/index.js"
12
12
  },
13
13
  "peerDependencies": {
14
- "@stable-harness/core": "0.0.101"
14
+ "@stable-harness/core": "0.0.103"
15
15
  }
16
16
  }