stable-harness 0.0.105 → 0.0.107

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 (36) hide show
  1. package/README.md +14 -0
  2. package/docs/guides/integration-guide.md +54 -4
  3. package/docs/protocols/agent-protocols.md +44 -3
  4. package/node_modules/@stable-harness/adapter-deepagents/package.json +2 -2
  5. package/node_modules/@stable-harness/adapter-langgraph/package.json +2 -2
  6. package/node_modules/@stable-harness/core/package.json +3 -3
  7. package/node_modules/@stable-harness/governance/package.json +1 -1
  8. package/node_modules/@stable-harness/memory/package.json +1 -1
  9. package/node_modules/@stable-harness/protocols/dist/src/agent-protocols.d.ts +19 -0
  10. package/node_modules/@stable-harness/protocols/dist/src/agent-protocols.js +1 -1
  11. package/node_modules/@stable-harness/protocols/dist/src/index.d.ts +1 -1
  12. package/node_modules/@stable-harness/protocols/dist/src/index.js +1 -1
  13. package/node_modules/@stable-harness/protocols/package.json +2 -2
  14. package/node_modules/@stable-harness/tool-gateway/package.json +1 -1
  15. package/node_modules/@stable-harness/workspace-yaml/package.json +2 -2
  16. package/package.json +9 -9
  17. package/packages/adapter-deepagents/package.json +2 -2
  18. package/packages/adapter-langgraph/package.json +2 -2
  19. package/packages/cli/dist/src/args.d.ts +3 -1
  20. package/packages/cli/dist/src/args.js +1 -1
  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/daemon/client.d.ts +3 -1
  24. package/packages/cli/dist/src/daemon/client.js +1 -1
  25. package/packages/cli/package.json +8 -8
  26. package/packages/core/package.json +3 -3
  27. package/packages/evaluation/package.json +2 -2
  28. package/packages/governance/package.json +1 -1
  29. package/packages/memory/package.json +1 -1
  30. package/packages/protocols/dist/src/agent-protocols.d.ts +19 -0
  31. package/packages/protocols/dist/src/agent-protocols.js +1 -1
  32. package/packages/protocols/dist/src/index.d.ts +1 -1
  33. package/packages/protocols/dist/src/index.js +1 -1
  34. package/packages/protocols/package.json +2 -2
  35. package/packages/tool-gateway/package.json +1 -1
  36. package/packages/workspace-yaml/package.json +2 -2
package/README.md CHANGED
@@ -114,6 +114,20 @@ and `local` always runs an in-process runtime. The CLI prints the selected path
114
114
  to stderr. Console mode keeps slash commands such as `/session`, `/sessions`,
115
115
  `/memory`, `/health`, `/debug`, and `/clear` in the active session.
116
116
 
117
+ When a protocol facade is enabled, the same CLI can also choose the client
118
+ protocol:
119
+
120
+ ```bash
121
+ stable-harness -w ./examples/minimal-deepagents --protocol stable-runtime --tool echo_tool --tool-args-json '{"value":"native"}'
122
+ stable-harness -w ./examples/minimal-deepagents --protocol a2a --protocol-url http://127.0.0.1:8650 --tool echo_tool --tool-args-json '{"value":"a2a"}'
123
+ stable-harness -w ./examples/minimal-deepagents --protocol agui --protocol-url http://127.0.0.1:8650 --tool echo_tool --tool-args-json '{"value":"agui"}'
124
+ stable-harness -w ./examples/minimal-deepagents --protocol acp --protocol-url http://127.0.0.1:8650 --tool echo_tool --tool-args-json '{"value":"acp"}'
125
+ ```
126
+
127
+ `stable-runtime` exposes the full operator control plane. A2A, AG-UI, and ACP
128
+ map request turns through their protocol facades; use Stable Runtime HTTP + SSE
129
+ for traces, approvals, memory administration, and full session inspection.
130
+
117
131
  Build a portable Docker runtime artifact for the workspace:
118
132
 
119
133
  ```bash
@@ -43,6 +43,28 @@ stable-harness console -w ./workspace --runtime daemon
43
43
  | `daemon` | Require a matching Stable Runtime daemon and fail clearly if unavailable. |
44
44
  | `local` | Always create an in-process runtime for this CLI process. |
45
45
 
46
+ Clients can also choose the wire protocol used for daemon/server requests:
47
+
48
+ ```bash
49
+ stable-harness -w ./workspace --protocol stable-runtime "Review the release evidence."
50
+ stable-harness -w ./workspace --protocol a2a --protocol-url http://127.0.0.1:8650 "Review the release evidence."
51
+ stable-harness -w ./workspace --protocol agui --protocol-url http://127.0.0.1:8650 "Review the release evidence."
52
+ stable-harness -w ./workspace --protocol acp --protocol-url http://127.0.0.1:8650 "Review the release evidence."
53
+ stable-harness console -w ./workspace --protocol a2a --protocol-url http://127.0.0.1:8650
54
+ ```
55
+
56
+ | Protocol | Behavior |
57
+ | --- | --- |
58
+ | `stable-runtime` | Native Stable Runtime HTTP + SSE with request/session/trace/operator features. |
59
+ | `a2a` | Sends request turns through the A2A facade. |
60
+ | `agui` | Sends request turns through the AG-UI HTTP+SSE facade. |
61
+ | `acp` | Sends request turns through the ACP HTTP JSON-RPC custom transport. |
62
+
63
+ `--protocol-url` overrides the configured agent protocol server. A2A, AG-UI,
64
+ and ACP carry explicit Stable Runtime request extensions for CLI parity, but
65
+ they do not replace the Stable Runtime operator API. Use `stable-runtime` for
66
+ traces, approvals, memory administration, and full session inspection.
67
+
46
68
  The CLI prints the selected runtime path to stderr:
47
69
 
48
70
  - `connected to daemon` means the request is handled by the workspace daemon.
@@ -68,6 +90,12 @@ supports ordinary prompts plus slash commands:
68
90
  | `/debug` | Print runtime inspection JSON. |
69
91
  | `/exit` | Leave console mode. |
70
92
 
93
+ When the console uses `--protocol a2a`, `--protocol agui`, or `--protocol acp`,
94
+ the selected protocol carries the current session ID for prompt turns, but the
95
+ full operator commands still belong to `stable-runtime`. Use
96
+ `--protocol stable-runtime` when the console must list sessions, inspect
97
+ requests, manage memory, clear sessions, or stream native control-plane events.
98
+
71
99
  ## Embedded Runtime
72
100
 
73
101
  Use the SDK when Stable Harness runs inside your service:
@@ -158,15 +186,30 @@ See [Stable Runtime HTTP + SSE](../protocols/http-runtime.md).
158
186
 
159
187
  | Client kind | Recommended connection | When to use it |
160
188
  | --- | --- | --- |
161
- | CLI one-shot | `--runtime auto`, `--runtime daemon`, or `--runtime local` | Use `auto` for developer ergonomics, `daemon` for shared runtime state, and `local` for isolated commands. |
162
- | CLI console | Same `--runtime` modes as one-shot CLI | Use `daemon` when multiple terminals should share sessions and events. |
189
+ | CLI one-shot | `--runtime ...` plus `--protocol ...` | Use runtime mode for process selection and protocol mode for wire compatibility. |
190
+ | CLI console | Same runtime and protocol options as one-shot CLI | Use `stable-runtime` for full slash-command operator features; use A2A/AG-UI/ACP for protocol turn testing. |
163
191
  | Embedded app | SDK / in-process runtime | Use when the application owns the process lifecycle. |
164
192
  | First-party UI | Stable Runtime HTTP + SSE | Use for sessions, request inspection, approvals, artifacts, memory, traces, and cross-client realtime events. |
165
193
  | OpenAI clients | OpenAI-compatible `/v1` | Use for existing chat-completion clients and evaluation harnesses. |
166
194
  | LangGraph Studio | LangGraph-compatible server | Use for LangGraph/LangSmith Studio workflows. |
167
195
  | A2A clients | A2A facade | Use when the caller already speaks A2A task/message APIs. |
168
196
  | AG-UI clients | AG-UI facade | Use when the UI expects AG-UI event streams. |
169
- | ACP clients | ACP HTTP JSON-RPC custom transport | Use for ACP-shaped clients until a stdio ACP entrypoint is added. |
197
+ | ACP clients | ACP stdio or ACP HTTP JSON-RPC custom transport | Use stdio for ACP-shaped local process clients and HTTP JSON-RPC for server-style clients. |
198
+
199
+ Session support is intentionally layered:
200
+
201
+ | Surface | Session level |
202
+ | --- | --- |
203
+ | Stable Runtime HTTP + SSE | Full operator session API: request history, events, deletion, memory, approvals, and inspection. |
204
+ | SDK / in-process runtime | Full operator session API through `StableHarnessRuntime`. |
205
+ | OpenAI-compatible `/v1` | Chat continuity through session headers or metadata. |
206
+ | LangGraph-compatible | Maps LangGraph `thread_id` to Stable Runtime `sessionId`. |
207
+ | A2A | Maps A2A `contextId` or `taskId` to Stable Runtime `sessionId`. |
208
+ | AG-UI | Maps AG-UI `threadId` or `sessionId` to Stable Runtime `sessionId`. |
209
+ | ACP HTTP JSON-RPC | Uses ACP `session/*` methods and maps `sessionId` to Stable Runtime. |
210
+
211
+ Protocol facades preserve continuity for their ecosystems, but they do not
212
+ replace the Stable Runtime operator API.
170
213
 
171
214
  ## ACP, A2A, and AG-UI Facades
172
215
 
@@ -186,7 +229,14 @@ spec:
186
229
  ```
187
230
 
188
231
  The server exposes A2A over HTTP+JSON/SSE, AG-UI over HTTP+SSE, and ACP over a
189
- documented HTTP JSON-RPC custom transport. See
232
+ documented HTTP JSON-RPC custom transport. Local ACP clients can also launch:
233
+
234
+ ```bash
235
+ stable-harness acp-stdio -w ./workspace
236
+ ```
237
+
238
+ The stdio entrypoint accepts one JSON-RPC message per line and reuses the same
239
+ ACP method handler as the HTTP endpoint. See
190
240
  [ACP, A2A, and AG-UI Protocol Facades](../protocols/agent-protocols.md).
191
241
 
192
242
  ## Backend Adapters
@@ -23,6 +23,21 @@ You can also enable a single protocol by configuring one of `acp`, `a2a`, or
23
23
  `agui` with `enabled: true`. The CLI starts one combined HTTP server for the
24
24
  enabled adapter set.
25
25
 
26
+ Shared discovery endpoints:
27
+
28
+ - `GET /health`
29
+ - `GET /capabilities`
30
+
31
+ `/capabilities` returns a normalized manifest with enabled protocol IDs,
32
+ transports, session mapping level, and whether the protocol exposes the full
33
+ operator session API. ACP/A2A/AG-UI report `operatorApi: false`; Stable Runtime
34
+ HTTP/SSE remains the first-party operator API.
35
+
36
+ These facades preserve protocol-native continuity, but they are not the full
37
+ Stable Runtime operator API. Stable Runtime HTTP/SSE and the SDK remain the
38
+ first-party surfaces for listing sessions, deleting session history, inspecting
39
+ requests, managing memory, approvals, artifacts, and native event filters.
40
+
26
41
  ## A2A
27
42
 
28
43
  A2A is exposed as an HTTP+JSON and SSE facade:
@@ -40,6 +55,14 @@ A2A is exposed as an HTTP+JSON and SSE facade:
40
55
  Streaming responses subscribe to native runtime events and project them as A2A
41
56
  status updates.
42
57
 
58
+ Stable Harness clients may include a `runtimeRequest` extension field when they
59
+ need explicit runtime fields such as `toolCall`, `workflow`, `agentId`, or
60
+ `sessionId`. External A2A clients can ignore that extension and use normal A2A
61
+ message parts.
62
+
63
+ Session mapping: `taskId` or message `contextId` becomes the Stable Runtime
64
+ `sessionId`.
65
+
43
66
  ## AG-UI
44
67
 
45
68
  AG-UI is exposed as an HTTP+SSE event stream:
@@ -51,9 +74,22 @@ AG-UI is exposed as an HTTP+SSE event stream:
51
74
  Runtime events, `TextMessageChunk` for final assistant output, and `RunFinished`
52
75
  or `RunError`.
53
76
 
77
+ Stable Harness clients may include `runtimeRequest` in the run body to preserve
78
+ explicit runtime fields while still using the AG-UI event stream.
79
+
80
+ Session mapping: `threadId` or `sessionId` becomes the Stable Runtime
81
+ `sessionId`.
82
+
54
83
  ## ACP
55
84
 
56
- ACP's standard transport is JSON-RPC over stdio. Stable Harness currently
85
+ ACP's standard transport is JSON-RPC over stdio. Stable Harness exposes a local
86
+ stdio entrypoint:
87
+
88
+ ```bash
89
+ stable-harness acp-stdio -w ./workspace
90
+ ```
91
+
92
+ The stdio entrypoint accepts one JSON-RPC message per line. Stable Harness also
57
93
  exposes ACP over an HTTP JSON-RPC endpoint as a documented custom transport:
58
94
 
59
95
  - `GET /acp/capabilities`
@@ -69,5 +105,10 @@ Supported methods:
69
105
  - `session/cancel`
70
106
 
71
107
  `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.
108
+ reason plus Stable Harness request metadata. Stdio and HTTP use the same
109
+ JSON-RPC method handler and do not change runtime semantics.
110
+
111
+ Stable Harness clients may include `params.runtimeRequest` to carry explicit
112
+ runtime fields through the ACP-shaped transport.
113
+
114
+ Session mapping: ACP `sessionId` becomes the Stable Runtime `sessionId`.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
18
+ "@stable-harness/core": "0.0.107",
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.105",
3
+ "version": "0.0.107",
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.105"
14
+ "@stable-harness/core": "0.0.107"
15
15
  }
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
15
- "@stable-harness/memory": "0.0.105"
14
+ "@stable-harness/governance": "0.0.107",
15
+ "@stable-harness/memory": "0.0.107"
16
16
  }
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
3
+ "version": "0.0.107",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -4,4 +4,23 @@ export type AgentProtocolServerOptions = {
4
4
  baseUrl?: string;
5
5
  enabledProtocols?: Array<"acp" | "a2a" | "agui">;
6
6
  };
7
+ type JsonRpcMessage = {
8
+ jsonrpc?: string;
9
+ id?: string | number | null;
10
+ method?: string;
11
+ params?: unknown;
12
+ };
7
13
  export declare function createAgentProtocolHttpServer(runtime: StableHarnessRuntime, options?: AgentProtocolServerOptions): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
14
+ export declare function handleAcpJsonRpcMessage(runtime: StableHarnessRuntime, message: JsonRpcMessage): Promise<{
15
+ jsonrpc: string;
16
+ id: string | number | null | undefined;
17
+ result: unknown;
18
+ } | {
19
+ jsonrpc: string;
20
+ id: string | number | null | undefined;
21
+ error: {
22
+ code: number;
23
+ message: string;
24
+ };
25
+ } | undefined>;
26
+ export {};
@@ -1 +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
+ import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,a={}){const r=new Set(a.enabledProtocols??["acp","a2a","agui"]);return e(async(e,s)=>{try{if("GET"===e.method&&"/health"===e.url)return void sendJson(s,200,{ok:!0,protocols:[...r]});if("GET"===e.method&&"/capabilities"===e.url)return void sendJson(s,200,function createProtocolCapabilityManifest(e){return{protocol:"stable-harness-agent-protocols",protocols:[...e].sort().map(e=>({id:e,transports:"acp"===e?["stdio","http-jsonrpc"]:"a2a"===e?["http-json","sse"]:["http-sse"],session:"acp"===e?{level:"protocol-session",mapsTo:"sessionId",operatorApi:!1}:"a2a"===e?{level:"continuity",mapsTo:"contextId|taskId",operatorApi:!1}:{level:"continuity",mapsTo:"threadId|sessionId",operatorApi:!1}})),stableRuntime:{session:{level:"operator-api",operatorApi:!0}}}}(r));if(r.has("a2a")&&await async function handleA2a(e,t,a,r){if("GET"===t.method&&("/.well-known/agent-card.json"===t.url||"/a2a/agent-card.json"===t.url))return sendJson(a,200,function createAgentCard(e,t){const a=e.inspect();return{protocolVersion:"1.0",name:a.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:a.agents.map(e=>({id:e,name:e,description:e}))}}(e,r)),!0;const s=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&s){const t=e.inspectRequest(s);return sendJson(a,t?200:404,t?{task:toA2aTask(t)}:{error:"task_not_found"}),!0}if("GET"===t.method&&"/a2a/tasks"===t.url)return sendJson(a,200,{tasks:e.listRequests().map(toA2aTaskSummary)}),!0;const n=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);return"POST"===t.method&&n?(e.cancel(n,"a2a_cancel"),sendJson(a,200,{task:e.inspectRequest(n)}),!0):"POST"===t.method&&"/a2a/message:send"===t.url?(sendJson(a,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),a),!0):"POST"===t.method&&"/a2a/rpc"===t.url&&(await async function handleA2aRpc(e,t,a){if("SendMessage"===t.method){const r=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(a,200,jsonRpcResult(t.id,{task:toA2aTask(r)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const r=readString(readRecord(t.params)?.id),s=r?e.inspectRequest(r):void 0;return void sendJson(a,s?200:404,s?jsonRpcResult(t.id,toA2aTask(s)):jsonRpcError(t.id,-32004,"task_not_found"))}"ListTasks"!==t.method?sendJson(a,404,jsonRpcError(t.id,-32601,"method_not_found")):sendJson(a,200,jsonRpcResult(t.id,{tasks:e.listRequests().map(toA2aTaskSummary)}))}else await streamA2a(e,readRecord(t.params)??{},a,t.id)}(e,await readJson(t),a),!0)}(t,e,s,a))return;if(r.has("agui")&&await async function handleAgui(e,t,a){return"GET"===t.method&&"/ag-ui/capabilities"===t.url?(sendJson(a,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,a){const r=readRuntimeRequestExtension(t),s=r?.requestId??readString(t.runId)??crypto.randomUUID(),n=r?.sessionId??readString(t.threadId)??readString(t.sessionId)??`thread-${s}`,o=readPromptText(t);writeSseHeaders(a),writeSse(a,{type:"RunStarted",threadId:n,runId:s,input:{messages:t.messages,input:o}});const d=e.subscribe(e=>{e.requestId===s&&writeSse(a,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 d=await e.request(r??{input:o,requestId:s,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),i=`${s}-message`;d.output&&writeSse(a,{type:"TextMessageChunk",messageId:i,role:"assistant",delta:d.output}),writeSse(a,{type:"RunFinished",threadId:n,runId:s,outcome:{type:"completed"===d.state?"success":"interrupt"},result:d.output})}catch(e){writeSse(a,{type:"RunError",threadId:n,runId:s,message:errorMessage(e)})}finally{d(),a.end()}}(e,await readJson(t),a),!0)}(t,e,s))return;if(r.has("acp")&&await async function handleAcp(e,t,a){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(a,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 r=await readJson(t),s=await handleAcpJsonRpcMessage(e,r);return s?sendJson(a,200,s):a.writeHead(204).end(),!0}return!1}(t,e,s))return;sendJson(s,404,{error:"not_found"})}catch(e){sendJson(s,400,{error:errorMessage(e)})}})}export async function handleAcpJsonRpcMessage(e,t){if(!t.id&&"session/cancel"===t.method){const a=readString(readRecord(t.params)?.sessionId);return void(a&&e.listRequests({sessionId:a,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 a=readRecord(t.params)??{},r=readRuntimeRequestExtension(a),s=await e.request(r??{input:readPromptText(a),sessionId:readString(a.sessionId),agentId:readString(a.agentId),metadata:{protocol:"acp",transport:"http-jsonrpc"}});return jsonRpcResult(t.id,{stopReason:"completed"===s.state?"end_turn":"refusal",_meta:{requestId:s.requestId,output:s.output}})}return jsonRpcError(t.id,-32601,"method_not_found")}async function streamA2a(e,t,a,r){const s=toA2aRuntimeRequest(t);writeSseHeaders(a);const n=e.subscribe(e=>{e.requestId===s.requestId&&writeSse(a,{jsonrpc:"2.0",id:r,result:{event:toA2aEvent(e)}})});try{const t=await e.request(s);writeSse(a,{jsonrpc:"2.0",id:r,result:{task:toA2aTask(t),final:!0}})}finally{n(),a.end()}}function toA2aRuntimeRequest(e){const t=readRuntimeRequestExtension(e);if(t)return{...t,metadata:{...t.metadata,protocol:"a2a"}};const a=readRecord(e.message)??e;return{input:readPromptText(a),requestId:readString(e.requestId)??readString(a.messageId),sessionId:readString(e.taskId)??readString(a.taskId)??readString(a.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,a="output"in e?e.output:e.output??"",r="requestId"in e?e.requestId:e.summary.requestId;return{id:r,contextId:"sessionId"in e?e.sessionId:e.summary.sessionId,status:{state:"completed"===t?"completed":t,message:a?{role:"agent",messageId:`${r}-response`,parts:[{kind:"text",text:a}]}:void 0}}}function toA2aTaskSummary(e){return{id:e.requestId,contextId:e.sessionId,status:{state:e.state},metadata:{agentId:e.agentId}}}function toA2aEvent(e){const t="runtime.request.completed"===e.type?{role:"agent",messageId:`${e.requestId}-response`,parts:[{kind:"text",text:e.output}]}:void 0;return{kind:"status-update",taskId:e.requestId,contextId:e.sessionId,status:{state:(a=e.type,"runtime.request.started"===a?"working":"runtime.request.completed"===a?"completed":"runtime.request.failed"===a?"failed":"runtime.request.cancelled"===a?"canceled":"working"),message:t},metadata:{stableHarnessEvent:e}};var a}function readRuntimeRequestExtension(e){const t=readRecord(e.runtimeRequest)??readRecord(readRecord(e.metadata)?.runtimeRequest);if(t)return{input:readString(t.input)??"",...readString(t.requestId)?{requestId:readString(t.requestId)}:{},...readString(t.sessionId)?{sessionId:readString(t.sessionId)}:{},...readString(t.agentId)?{agentId:readString(t.agentId)}:{},...readToolCall(t.toolCall)?{toolCall:readToolCall(t.toolCall)}:{},...readWorkflow(t.workflow)?{workflow:readWorkflow(t.workflow)}:{},...readRecord(t.metadata)?{metadata:readRecord(t.metadata)}:{}}}function readToolCall(e){const t=readRecord(e),a=readString(t?.toolId);return a?{toolId:a,args:t?.args}:void 0}function readWorkflow(e){const t=readRecord(e);if(t)return{...readString(t.workflowId)?{workflowId:readString(t.workflowId)}:{},...readString(t.routeId)?{routeId:readString(t.routeId)}:{},...void 0!==t.input?{input:t.input}:{},...readRecord(t.metadata)?{metadata:readRecord(t.metadata)}:{}}}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 a=(e??"").match(t);return a?.[1]?decodeURIComponent(a[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,a){return{jsonrpc:"2.0",id:e,error:{code:t,message:a}}}function sendJson(e,t,a){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(a))}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 a of e)t.push(Buffer.isBuffer(a)?a:Buffer.from(a));return t.length>0?JSON.parse(Buffer.concat(t).toString("utf8")):{}}function errorMessage(e){return e instanceof Error?e.message:String(e)}
@@ -1,5 +1,5 @@
1
1
  export { createInProcessClient } from "./in-process-client.js";
2
- export { createAgentProtocolHttpServer } from "./agent-protocols.js";
2
+ export { createAgentProtocolHttpServer, handleAcpJsonRpcMessage } from "./agent-protocols.js";
3
3
  export type { AgentProtocolServerOptions } from "./agent-protocols.js";
4
4
  export { createHttpServer } from "./http-server.js";
5
5
  export { createOpenAiCompatibleHttpServer } from "./openai-compatible.js";
@@ -1 +1 @@
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
+ export{createInProcessClient}from"./in-process-client.js";export{createAgentProtocolHttpServer,handleAcpJsonRpcMessage}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.105",
3
+ "version": "0.0.107",
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.105"
13
+ "@stable-harness/core": "0.0.107"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
3
+ "version": "0.0.107",
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.105"
14
+ "@stable-harness/core": "0.0.107"
15
15
  }
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stable-harness",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
86
- "@stable-harness/adapter-langgraph": "0.0.105",
87
- "@stable-harness/core": "0.0.105",
88
- "@stable-harness/governance": "0.0.105",
89
- "@stable-harness/memory": "0.0.105",
90
- "@stable-harness/protocols": "0.0.105",
91
- "@stable-harness/tool-gateway": "0.0.105",
92
- "@stable-harness/workspace-yaml": "0.0.105",
85
+ "@stable-harness/adapter-deepagents": "0.0.107",
86
+ "@stable-harness/adapter-langgraph": "0.0.107",
87
+ "@stable-harness/core": "0.0.107",
88
+ "@stable-harness/governance": "0.0.107",
89
+ "@stable-harness/memory": "0.0.107",
90
+ "@stable-harness/protocols": "0.0.107",
91
+ "@stable-harness/tool-gateway": "0.0.107",
92
+ "@stable-harness/workspace-yaml": "0.0.107",
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.105",
3
+ "version": "0.0.107",
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.105",
18
+ "@stable-harness/core": "0.0.107",
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.105",
3
+ "version": "0.0.107",
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.105"
14
+ "@stable-harness/core": "0.0.107"
15
15
  }
16
16
  }
@@ -1,6 +1,6 @@
1
1
  export type CliArgs = {
2
2
  workspaceRoot: string;
3
- command: "request" | "console" | "start" | "stop" | "init" | "build";
3
+ command: "request" | "console" | "start" | "stop" | "init" | "build" | "acp-stdio";
4
4
  buildTarget?: "docker";
5
5
  buildOutput?: string;
6
6
  workflowRenderId?: string;
@@ -20,6 +20,8 @@ export type CliArgs = {
20
20
  apiKey?: string;
21
21
  runtimeMode: "auto" | "daemon" | "local";
22
22
  daemonUrl?: string;
23
+ clientProtocol: "stable-runtime" | "a2a" | "agui" | "acp";
24
+ protocolUrl?: string;
23
25
  timeoutMs: number;
24
26
  help: boolean;
25
27
  prompt: string;
@@ -1 +1 @@
1
- export function parseArgs(e){const r=function createDefaultArgs(){return{workspaceRoot:process.cwd(),command:"request",toolArgs:void 0,trace:!1,traceJson:!1,progress:!1,serveOpenAi:!1,host:process.env.STABLE_HARNESS_OPENAI_HOST,port:process.env.STABLE_HARNESS_OPENAI_PORT?Number(process.env.STABLE_HARNESS_OPENAI_PORT):void 0,apiKey:process.env.STABLE_HARNESS_OPENAI_API_KEY,runtimeMode:readRuntimeMode(process.env.STABLE_HARNESS_CLIENT_RUNTIME)??"auto",daemonUrl:process.env.STABLE_HARNESS_DAEMON_URL,timeoutMs:Number(process.env.STABLE_HARNESS_CLI_TIMEOUT_MS??3e5),help:!1,prompt:"",shouldRunRequest:!1}}(),t=[];for(let o=0;o<e.length;o+=1)o=parseOneArg(e,o,r,t);return{...r,prompt:t.join(" "),shouldRunRequest:Boolean(r.toolId||r.workflowRunId||t.length>0)}}function parseOneArg(e,r,t,o){const n=function readNextArg(e,r){return{index:r+1,value:e[r+1]}}(e,r);if(0===o.length&&function parseTopLevelCommand(e,r,t){return"start"===e[r]?(t.command="start",t.serveOpenAi=!0,!0):"stop"===e[r]?(t.command="stop",!0):"init"===e[r]?(t.command="init",!0):"build"===e[r]?(t.command="build",!0):"console"===e[r]||"session"===e[r]?(t.command="console",!0):"workflow"!==e[r]||"render"!==e[r+1]&&"inspect"!==e[r+1]?"agent"===e[r]&&"render"===e[r+1]&&(Object.assign(t,function parseAgentCommand(e,r){if("render"===e[r+1])return{index:r+2,agentRenderId:e[r+2]};throw new Error("Usage: stable-harness agent render <agent-id>")}(e,r)),!0):(Object.assign(t,function parseWorkflowCommand(e,r){if("render"===e[r+1])return{index:r+2,workflowRenderId:e[r+2],workflowInspectId:void 0};if("inspect"===e[r+1])return{index:r+2,workflowRenderId:void 0,workflowInspectId:e[r+2]};throw new Error("Usage: stable-harness workflow <render|inspect> <workflow-id>")}(e,r)),!0)}(e,r,t))return function stateIndex(e,r){return"workflow"===e[r]||"agent"===e[r]?r+2:r}(e,r);if("-h"===e[r]||"--help"===e[r])t.help=!0;else if("start"===t.command&&function isProtocolName(e){return"openai"===e||"openai-compatible"===e}(e[r]))t.serveOpenAi=!0;else{if("-w"===e[r]||"--workspace"===e[r])return setString(n,t,"workspaceRoot");if("--agent"===e[r])return setString(n,t,"agentId");if("--workflow"===e[r])return setString(n,t,"workflowRunId");if("--session-id"===e[r])return setString(n,t,"sessionId");if("--tool"===e[r])return setString(n,t,"toolId");if("--tool-args-json"===e[r])return t.toolArgs=function parseJsonArg(e){try{return JSON.parse(e)}catch(e){const r=e instanceof Error?e.message:String(e);throw new Error(`Invalid --tool-args-json value: ${r}`)}}(n.value??"{}"),n.index;if("--trace"===e[r])t.trace=!0;else if("--trace-json"===e[r])t.traceJson=!0;else if("--progress"===e[r])t.progress=!0;else if("--serve-openai"===e[r])t.serveOpenAi=!0;else{if("--host"===e[r])return setString(n,t,"host");if("--port"===e[r])return t.port=Number(n.value??t.port),n.index;if("--api-key"===e[r])return setString(n,t,"apiKey");if("--runtime"===e[r])return function setRuntimeMode(e,r){const t=readRuntimeMode(e.value);if(!t)throw new Error("Unsupported --runtime value. Supported values: auto, daemon, local");return r.runtimeMode=t,e.index}(n,t);if("--daemon-url"===e[r])return setString(n,t,"daemonUrl");if("--target"===e[r])return function setBuildTarget(e,r){if("docker"===e.value)return r.buildTarget="docker",e.index;throw new Error("Unsupported build target. Supported targets: docker")}(n,t);if("--output"===e[r]||"-o"===e[r])return setString(n,t,"buildOutput");if("--timeout-ms"===e[r])return t.timeoutMs=Number(n.value??t.timeoutMs),n.index;o.push(e[r])}}return r}function setString(e,r,t){return"string"==typeof e.value&&Object.assign(r,{[t]:e.value}),e.index}export function helpText(){return["Usage:"," stable-harness -w <workspace> [--agent <id>] [prompt]"," stable-harness console -w <workspace>"," stable-harness workflow render <workflow-id> -w <workspace>"," stable-harness workflow inspect <workflow-id> -w <workspace>"," stable-harness agent render <agent-id> -w <workspace>"," stable-harness init [workspace]"," stable-harness build --target docker -w <workspace> --output <dir>"," stable-harness start -w <workspace>"," stable-harness stop -w <workspace>","","Options:"," -w, --workspace <path> Workspace root."," --serve-openai Legacy alias for start."," --agent <id> Select an agent for a request."," --workflow <id> Run a configured workflow."," --session-id <id> Attach the request to an existing runtime session."," --tool <id> Invoke an explicit registered tool."," --tool-args-json <json> Tool arguments for --tool."," --trace Print trace lines."," --trace-json Print trace JSON."," --progress Legacy alias; CLI events are controlled by runtime.cli.events."," --runtime <mode> Client runtime mode: auto, daemon, or local."," --daemon-url <url> Stable Runtime daemon URL for daemon/auto modes."," --target docker Build target for workspace artifacts."," -o, --output <dir> Build output directory."," --timeout-ms <ms> Request timeout."," -h, --help Show this help.",""].join("\n")}function readRuntimeMode(e){return"auto"===e||"daemon"===e||"local"===e?e:void 0}
1
+ export function parseArgs(e){const r=function createDefaultArgs(){return{workspaceRoot:process.cwd(),command:"request",toolArgs:void 0,trace:!1,traceJson:!1,progress:!1,serveOpenAi:!1,host:process.env.STABLE_HARNESS_OPENAI_HOST,port:process.env.STABLE_HARNESS_OPENAI_PORT?Number(process.env.STABLE_HARNESS_OPENAI_PORT):void 0,apiKey:process.env.STABLE_HARNESS_OPENAI_API_KEY,runtimeMode:readRuntimeMode(process.env.STABLE_HARNESS_CLIENT_RUNTIME)??"auto",daemonUrl:process.env.STABLE_HARNESS_DAEMON_URL,clientProtocol:readClientProtocol(process.env.STABLE_HARNESS_CLIENT_PROTOCOL)??"stable-runtime",protocolUrl:process.env.STABLE_HARNESS_PROTOCOL_URL,timeoutMs:Number(process.env.STABLE_HARNESS_CLI_TIMEOUT_MS??3e5),help:!1,prompt:"",shouldRunRequest:!1}}(),t=[];for(let o=0;o<e.length;o+=1)o=parseOneArg(e,o,r,t);return{...r,prompt:t.join(" "),shouldRunRequest:Boolean(r.toolId||r.workflowRunId||t.length>0)}}function parseOneArg(e,r,t,o){const n=function readNextArg(e,r){return{index:r+1,value:e[r+1]}}(e,r);if(0===o.length&&function parseTopLevelCommand(e,r,t){return"start"===e[r]?(t.command="start",t.serveOpenAi=!0,!0):"stop"===e[r]?(t.command="stop",!0):"init"===e[r]?(t.command="init",!0):"build"===e[r]?(t.command="build",!0):"acp-stdio"===e[r]?(t.command="acp-stdio",!0):"console"===e[r]||"session"===e[r]?(t.command="console",!0):"workflow"!==e[r]||"render"!==e[r+1]&&"inspect"!==e[r+1]?"agent"===e[r]&&"render"===e[r+1]&&(Object.assign(t,function parseAgentCommand(e,r){if("render"===e[r+1])return{index:r+2,agentRenderId:e[r+2]};throw new Error("Usage: stable-harness agent render <agent-id>")}(e,r)),!0):(Object.assign(t,function parseWorkflowCommand(e,r){if("render"===e[r+1])return{index:r+2,workflowRenderId:e[r+2],workflowInspectId:void 0};if("inspect"===e[r+1])return{index:r+2,workflowRenderId:void 0,workflowInspectId:e[r+2]};throw new Error("Usage: stable-harness workflow <render|inspect> <workflow-id>")}(e,r)),!0)}(e,r,t))return function stateIndex(e,r){return"workflow"===e[r]||"agent"===e[r]?r+2:r}(e,r);if("-h"===e[r]||"--help"===e[r])t.help=!0;else if("start"===t.command&&function isProtocolName(e){return"openai"===e||"openai-compatible"===e}(e[r]))t.serveOpenAi=!0;else{if("-w"===e[r]||"--workspace"===e[r])return setString(n,t,"workspaceRoot");if("--agent"===e[r])return setString(n,t,"agentId");if("--workflow"===e[r])return setString(n,t,"workflowRunId");if("--session-id"===e[r])return setString(n,t,"sessionId");if("--tool"===e[r])return setString(n,t,"toolId");if("--tool-args-json"===e[r])return t.toolArgs=function parseJsonArg(e){try{return JSON.parse(e)}catch(e){const r=e instanceof Error?e.message:String(e);throw new Error(`Invalid --tool-args-json value: ${r}`)}}(n.value??"{}"),n.index;if("--trace"===e[r])t.trace=!0;else if("--trace-json"===e[r])t.traceJson=!0;else if("--progress"===e[r])t.progress=!0;else if("--serve-openai"===e[r])t.serveOpenAi=!0;else{if("--host"===e[r])return setString(n,t,"host");if("--port"===e[r])return t.port=Number(n.value??t.port),n.index;if("--api-key"===e[r])return setString(n,t,"apiKey");if("--runtime"===e[r])return function setRuntimeMode(e,r){const t=readRuntimeMode(e.value);if(!t)throw new Error("Unsupported --runtime value. Supported values: auto, daemon, local");return r.runtimeMode=t,e.index}(n,t);if("--daemon-url"===e[r])return setString(n,t,"daemonUrl");if("--protocol"===e[r])return function setClientProtocol(e,r){const t=readClientProtocol(e.value);if(!t)throw new Error("Unsupported --protocol value. Supported values: stable-runtime, a2a, agui, acp");return r.clientProtocol=t,e.index}(n,t);if("--protocol-url"===e[r])return setString(n,t,"protocolUrl");if("--target"===e[r])return function setBuildTarget(e,r){if("docker"===e.value)return r.buildTarget="docker",e.index;throw new Error("Unsupported build target. Supported targets: docker")}(n,t);if("--output"===e[r]||"-o"===e[r])return setString(n,t,"buildOutput");if("--timeout-ms"===e[r])return t.timeoutMs=Number(n.value??t.timeoutMs),n.index;o.push(e[r])}}return r}function setString(e,r,t){return"string"==typeof e.value&&Object.assign(r,{[t]:e.value}),e.index}export function helpText(){return["Usage:"," stable-harness -w <workspace> [--agent <id>] [prompt]"," stable-harness console -w <workspace>"," stable-harness workflow render <workflow-id> -w <workspace>"," stable-harness workflow inspect <workflow-id> -w <workspace>"," stable-harness agent render <agent-id> -w <workspace>"," stable-harness init [workspace]"," stable-harness build --target docker -w <workspace> --output <dir>"," stable-harness acp-stdio -w <workspace>"," stable-harness start -w <workspace>"," stable-harness stop -w <workspace>","","Options:"," -w, --workspace <path> Workspace root."," --serve-openai Legacy alias for start."," --agent <id> Select an agent for a request."," --workflow <id> Run a configured workflow."," --session-id <id> Attach the request to an existing runtime session."," --tool <id> Invoke an explicit registered tool."," --tool-args-json <json> Tool arguments for --tool."," --trace Print trace lines."," --trace-json Print trace JSON."," --progress Legacy alias; CLI events are controlled by runtime.cli.events."," --runtime <mode> Client runtime mode: auto, daemon, or local."," --daemon-url <url> Stable Runtime daemon URL for daemon/auto modes."," --protocol <name> Client protocol: stable-runtime, a2a, agui, or acp."," --protocol-url <url> Protocol server URL for a2a/agui/acp."," --target docker Build target for workspace artifacts."," -o, --output <dir> Build output directory."," --timeout-ms <ms> Request timeout."," -h, --help Show this help.",""].join("\n")}function readRuntimeMode(e){return"auto"===e||"daemon"===e||"local"===e?e:void 0}function readClientProtocol(e){return"stable"===e||"stable-runtime"===e||"http"===e?"stable-runtime":"a2a"===e||"agui"===e||"acp"===e?e:"ag-ui"===e?"agui":void 0}
@@ -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 n,projectRuntimeTrace as a}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 R}from"./daemon/client.js";import{ensureCliMemoryServices as I}from"./memory/lifecycle.js";import{createCliMemoryProviders as h}from"./memory/providers.js";import{formatDetail as k,inspectWorkflow as b,renderAgent as q,renderWorkflow as A,workspaceStatus as C}from"./output.js";import{serveProtocol as M,stopProtocol as j}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 j(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"}}}),n=u(),a=await async function createCliMemoryProvidersForCommand(e,t){return"console"===t.command||t.serveOpenAi||t.shouldRunRequest&&!t.toolId?(await I(e),h(e)):[]}(e,t);let d;return d=i({workspace:e,toolGateway:s,approvals:n,memoryProviders:a,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 R({args:t,runtimePolicy:e.runtime,eventView:c}))return;t.shouldRunRequest&&!t.serveOpenAi&&"auto"===t.runtimeMode&&process.stderr.write("stable-harness runtime: no matching daemon found; running in-process\n"),t.shouldRunRequest&&!t.serveOpenAi&&"local"===t.runtimeMode&&process.stderr.write("stable-harness runtime: local mode selected; running in-process\n");const p=await createRuntime();if(t.serveOpenAi)return clearTimeout(r),void await M(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=n(e);t&&process.stdout.write(`trace:${t.agentId}:${t.label}${k(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?a(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:n,workspace:a})=>{return(await e.invoke({toolId:t,args:(u=r.config,l=o.input,d=n.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:a.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 n,a,u,l;return(await t().request({input:(n=e,a=o.input,u=i.outputs,l=r.config,[`Workflow node agents.${n}: synthesize the workflow evidence into the requested final output.`,`Original request: ${"string"==typeof a?a:JSON.stringify(a)}`,"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 n,projectRuntimeTrace as a}from"@stable-harness/core";import{createInMemoryApprovalQueue as u}from"@stable-harness/governance";import{handleAcpJsonRpcMessage as c}from"@stable-harness/protocols";import{createModuleToolGateway as d}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as l}from"@stable-harness/workspace-yaml";import{helpText as p,parseArgs as m}from"./args.js";import{buildWorkspaceArtifact as f}from"./build.js";import{runConsole as w}from"./console/session.js";import{formatCliRuntimeEvent as v,readCliEventViewConfig as g,shouldEnableCliProgressNarration as y}from"./event-view.js";import{initWorkspace as R}from"./init.js";import{runRequestThroughDaemon as h}from"./daemon/client.js";import{ensureCliMemoryServices as I}from"./memory/lifecycle.js";import{createCliMemoryProviders as k}from"./memory/providers.js";import{formatDetail as b,inspectWorkflow as A,renderAgent as q,renderWorkflow as C,workspaceStatus as M}from"./output.js";import{serveProtocol as S,stopProtocol as j}from"./server.js";export async function runCli(e=process.argv.slice(2)){const t=m(e);if(t.help)return void process.stdout.write(p());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 R(t.prompt||s));const e=await l(s);if(t.workflowRenderId)return void process.stdout.write(C(e,t.workflowRenderId));if(t.workflowInspectId)return void process.stdout.write(A(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 f({workspace:e,workspaceRoot:s,outputDir:t.buildOutput,target:t.buildTarget}));if("stop"===t.command)return clearTimeout(r),void await j(e,t);const c=g(e.runtime),createRuntime=()=>async function createCliRuntime(e,t,r){const s=await d({tools:e.tools.values(),options:{betterCall:{mode:"repair"}}}),n=u(),a=await async function createCliMemoryProvidersForCommand(e,t){return"console"===t.command||"acp-stdio"===t.command||t.serveOpenAi||t.shouldRunRequest&&!t.toolId?(await I(e),k(e)):[]}(e,t);let c;return c=i({workspace:e,toolGateway:s,approvals:n,memoryProviders:a,adapters:[o()],workflowAdapters:[createCliWorkflowAdapter(s,()=>c)],progressNarration:y(r,e.runtime)?{enabled:!0,style:"cli"}:void 0,qualityReviewModel:createQualityReviewModel(e)}),c}(e,t,c);if("console"===t.command)return clearTimeout(r),void await w({args:t,runtimePolicy:e.runtime,eventView:c,createRuntime:createRuntime});if("acp-stdio"===t.command)return clearTimeout(r),void await async function runAcpStdio(e){process.stdin.setEncoding("utf8");let t="";for await(const r of process.stdin){t+=String(r);const o=t.split(/\r?\n/u);t=o.pop()??"";for(const t of o)await handleAcpStdioLine(e,t)}t.trim()&&await handleAcpStdioLine(e,t)}(await createRuntime());if(t.shouldRunRequest&&!t.serveOpenAi&&await h({args:t,runtimePolicy:e.runtime,eventView:c}))return;t.shouldRunRequest&&!t.serveOpenAi&&"auto"===t.runtimeMode&&process.stderr.write("stable-harness runtime: no matching daemon found; running in-process\n"),t.shouldRunRequest&&!t.serveOpenAi&&"local"===t.runtimeMode&&process.stderr.write("stable-harness runtime: local mode selected; running in-process\n");const p=await createRuntime();if(t.serveOpenAi)return clearTimeout(r),void await S(p,t);if(!t.shouldRunRequest)return void process.stdout.write(M(e,s));await async function runInProcessRequest(e,t,r){t.trace&&e.subscribe(e=>{const t=n(e);t&&process.stdout.write(`trace:${t.agentId}:${t.label}${b(t.detail)}\n`)}),e.subscribe(e=>{const t=v(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?a(r):[];t.traceJson&&process.stdout.write(`${JSON.stringify({trace:s})}\n`)}process.stdout.write(`${o.output}\n`)}(p,t,c)}finally{clearTimeout(r)}}async function handleAcpStdioLine(e,t){if(!t.trim())return;const r=await c(e,JSON.parse(t));r&&process.stdout.write(`${JSON.stringify(r)}\n`)}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:n,workspace:a})=>{return(await e.invoke({toolId:t,args:(u=r.config,c=o.input,d=n.outputs,!0===u?.inputFromState?{...u,requestInput:c,outputs:d}:u&&"requiredInput"in u?u.requiredInput:u&&("args"in u||"cwd"in u||"timeoutMs"in u)?u:"object"==typeof c&&null!==c?c:{}),context:{workspaceRoot:a.root,requestId:s,sessionId:i,agentId:`workflow:${r.id}`,approvalIds:readApprovalIds(o.metadata)}})).output;var u,c,d},agents:async({id:e,node:r,request:o,sessionId:s,state:i})=>{var n,a,u,c;return(await t().request({input:(n=e,a=o.input,u=i.outputs,c=r.config,[`Workflow node agents.${n}: synthesize the workflow evidence into the requested final output.`,`Original request: ${"string"==typeof a?a:JSON.stringify(a)}`,"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.",...c?[`Workflow node config: ${JSON.stringify(c)}`]:[],"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 r}from"../event-view.js";export async function runConsole(a){const c=await async function createConsoleClient(e){if("local"===e.args.runtimeMode)return createLocalConsoleClient(await e.createRuntime(),e.eventView);const s=o(e.runtimePolicy,e.args.daemonUrl);if(await i(s,e.args.workspaceRoot))return 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);if("daemon"===e.args.runtimeMode)throw new Error(`stable-harness console: daemon required but unavailable at ${s}`);return createLocalConsoleClient(await e.createRuntime(),e.eventView)}(a);let d=a.args.sessionId??e(),u=a.args.agentId;process.stderr.write(`stable-harness console: ${function consoleRuntimeMessage(e,s){return"daemon"===e.mode?`connected to daemon at ${e.baseUrl}`:"local"===s?"local mode selected; running in-process":"no matching daemon found; running in-process"}(c,a.args.runtimeMode)}\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 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)}})})}}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
+ import{randomUUID as e}from"node:crypto";import{createInterface as s}from"node:readline/promises";import{stdin as t,stdout as n}from"node:process";import{agentProtocolBaseUrl as o,daemonBaseUrl as r,isDaemonAvailable as i,sendAgentProtocolRuntimeRequest as a}from"../daemon/client.js";import{formatCliRuntimeEvent as c}from"../event-view.js";export async function runConsole(l){const u=await async function createConsoleClient(e){if("stable-runtime"!==e.args.clientProtocol){if("local"===e.args.runtimeMode)throw new Error(`stable-harness console: protocol ${e.args.clientProtocol} requires a protocol server; use --protocol stable-runtime for local mode`);return await async function createAgentProtocolConsoleClient(e,s){const t=e.clientProtocol,n=o(s,e.protocolUrl);if("stable-runtime"===t)throw new Error("stable-runtime is handled by the native console client");if(!(await fetch(`${n}/health`,{signal:AbortSignal.timeout(300)})).ok)throw new Error(`stable-harness console: protocol ${t} unavailable at ${n}`);return{mode:"protocol",protocol:t,baseUrl:n,request:e=>a(n,t,e),health:async()=>"ok",sessions:unsupportedList("sessions",t),requests:unsupportedList("requests",t),inspectRequest:async()=>{},memories:unsupportedList("memory",t),clearSession:unsupportedNumber("clear",t),debug:async()=>({protocol:t,baseUrl:n,note:"Protocol console exposes prompt turns only. Use --protocol stable-runtime for operator inspection."}),stream:()=>({close:()=>{}})}}(e.args,e.runtimePolicy)}if("local"===e.args.runtimeMode)return createLocalConsoleClient(await e.createRuntime(),e.eventView);const s=r(e.runtimePolicy,e.args.daemonUrl);if(await i(s,e.args.workspaceRoot))return function createDaemonConsoleClient(e,s){return{mode:"daemon",baseUrl:e,request:s=>async function postJson(e,s){const t=await fetch(e,{method:"POST",body:JSON.stringify(s)});if(!t.ok)throw new Error(`HTTP ${t.status}`);return await t.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 t=await fetch(`${e}/requests/${encodeURIComponent(s)}`);return t.ok?await t.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:(t,n)=>function streamDaemonEvents(e,s,t,n){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:r}=await s.read();if(r)return;o+=Buffer.from(e).toString("utf8");const i=o.split("\n\n");o=i.pop()??"";for(const e of i){const s=e.split("\n").find(e=>e.startsWith("data: "))?.slice(6);if(!s)continue;const o=c(JSON.parse(s),t);o&&n(o)}}}).catch(e=>{o.signal.aborted||process.stderr.write(`stable-harness console event stream failed: ${String(e)}\n`)}),{close:()=>o.abort()}}(e,t,s,n)}}(s,e.eventView);if("daemon"===e.args.runtimeMode)throw new Error(`stable-harness console: daemon required but unavailable at ${s}`);return createLocalConsoleClient(await e.createRuntime(),e.eventView)}(l);let d=l.args.sessionId??e(),m=l.args.agentId;process.stderr.write(`stable-harness console: ${function consoleRuntimeMessage(e,s){return"daemon"===e.mode?`connected to daemon at ${e.baseUrl}`:"protocol"===e.mode?`connected through ${e.protocol} at ${e.baseUrl}; prompt turns only, use --protocol stable-runtime for operator session APIs`:"local"===s?"local mode selected; running in-process":"no matching daemon found; running in-process"}(u,l.args.runtimeMode)}\n`),process.stdout.write(`session ${d}\n`);const p=s({input:t,output:n,terminal:process.stdout.isTTY});try{const e=process.stdout.isTTY?await async function runInteractiveLoop(e){let{sessionId:s,agentId:t}=e;for(;;){const n=await e.rl.question("stable> "),o=await handleConsoleLineSafely({client:e.client,line:n.trim(),sessionId:s,agentId:t,eventView:e.eventView});if(o.done)return o;s=o.sessionId,t=o.agentId}}({rl:p,client:u,sessionId:d,agentId:m,eventView:l.eventView}):await async function runPipedLoop(e){let{sessionId:s,agentId:t}=e;for await(const n of e.rl){const o=await handleConsoleLineSafely({client:e.client,line:n.trim(),sessionId:s,agentId:t,eventView:e.eventView});if(o.done)return o;s=o.sessionId,t=o.agentId}return{done:!0,sessionId:s,agentId:t}}({rl:p,client:u,sessionId:d,agentId:m,eventView:l.eventView});d=e.sessionId,m=e.agentId}catch(e){if(!function isEndOfInput(e){return e instanceof Error&&e.message.includes("closed")}(e))throw e}finally{p.close()}}async function handleConsoleLineSafely(s){try{return await async function handleConsoleLine(s){const t=function parseConsoleCommand(e){if(!e.startsWith("/"))return;const[s="",...t]=e.slice(1).trim().split(/\s+/u);return{name:s,args:t.join(" ").trim()}}(s.line);return s.line?t?await async function runConsoleCommand(s){const{client:t,command:n}=s;if("exit"===n.name||"quit"===n.name)return{...s,done:!0};if("help"===n.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.","/protocol Show connection protocol and capability level.","/capabilities Show connection protocol and capability level.","/debug Print runtime inspection JSON.","/exit Leave console.",""].join("\n"))}();else if("health"===n.name)process.stdout.write(`${await t.health()}\n`);else if("debug"===n.name)process.stdout.write(`${JSON.stringify(await t.debug())}\n`);else if("protocol"===n.name||"capabilities"===n.name)!function printProtocolCapabilities(e){const s="protocol"===e.mode?e.protocol:"stable-runtime",t="protocol"===e.mode?"prompt-turns-only":"full-operator-session-api";process.stdout.write(`${JSON.stringify({mode:e.mode,protocol:s,baseUrl:e.baseUrl,session:t})}\n`)}(t);else{if("session"===n.name)return switchSession(s,n.args);if("new"===n.name)return switchSession(s,e());if("agent"===n.name)return function switchAgent(e,s){const t=s.trim()||void 0;return process.stdout.write(`agent ${t??"default"}\n`),{done:!1,sessionId:e.sessionId,agentId:t}}(s,n.args);"sessions"===n.name?printJsonLines(await t.sessions(),"sessionId"):"requests"===n.name?printJsonLines(await t.requests("all"===n.args?void 0:s.sessionId),"requestId"):"memory"===n.name||"memories"===n.name?printJsonLines(await t.memories(n.args||void 0),"id"):"clear"===n.name?process.stdout.write(`cleared ${await t.clearSession(s.sessionId)} requests from ${s.sessionId}\n`):"tool"===n.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: /${n.name}\n`)}return{done:!1,sessionId:s.sessionId,agentId:s.agentId}}({...s,command:t}):(await runConsoleRequest(s.client,await async function withSessionHistory(e,s){if(!s.sessionId||s.toolCall||s.workflow)return s;if("protocol"===e.mode)return{...s,metadata:{...s.metadata,consoleSession:!0,protocol:e.protocol}};const t=await async function buildSessionHistory(e,s){const t=(await e.requests(s)).filter(e=>"completed"===e.state).slice(-6),n=[];for(const s of t){const t=await e.inspectRequest(s.requestId),o=readHistoryInput(t),r=t?.output?.trim();o&&r&&n.push({role:"user",content:o},{role:"assistant",content:r})}return n.slice(-12)}(e,s.sessionId);return{...s,metadata:{...s.metadata,consoleSession:!0,openaiMessages:[...t,{role:"user",content:s.input}],openaiSessionHistory:t.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,t){const n=t.requestId??e(),o=s.stream(n,e=>process.stdout.write(`${e}\n`));try{const e=await s.request({...t,requestId:n});process.stdout.write(`${e.output}\n`)}finally{o.close()}}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:(t,n)=>({close:e.subscribe(e=>{if(e.requestId===t){const t=c(e,s);t&&n(t)}})})}}function unsupportedList(e,s){return async()=>(process.stdout.write(`/${e} is not available through ${s}; use --protocol stable-runtime for operator features.\n`),[])}function unsupportedNumber(e,s){return async()=>(process.stdout.write(`/${e} is not available through ${s}; use --protocol stable-runtime for operator features.\n`),0)}function readHistoryInput(e){const s=Array.isArray(e?.metadata?.openaiMessages)?e.metadata.openaiMessages:[],t=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 t?t:e?.input.trim()||void 0}function switchSession(e,s){const t=s.trim()||e.sessionId;return process.stdout.write(`session ${t}\n`),{done:!1,sessionId:t,agentId:e.agentId}}function printJsonLines(e,s){if(0!==e.length)for(const t of e){const e="object"==typeof t&&t?t:{};process.stdout.write(`${String(e[s]??"")} ${JSON.stringify(t)}\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,4 +1,4 @@
1
- import { type WorkspaceRuntimePolicy } from "@stable-harness/core";
1
+ import { type RuntimeRequest, type RuntimeResponse, type WorkspaceRuntimePolicy } from "@stable-harness/core";
2
2
  import type { CliArgs } from "../args.js";
3
3
  import { type CliEventViewConfig } from "../event-view.js";
4
4
  export declare function runRequestThroughDaemon(input: {
@@ -7,4 +7,6 @@ export declare function runRequestThroughDaemon(input: {
7
7
  eventView: CliEventViewConfig;
8
8
  }): Promise<boolean>;
9
9
  export declare function daemonBaseUrl(policy: WorkspaceRuntimePolicy, overrideUrl?: string): string;
10
+ export declare function agentProtocolBaseUrl(policy: WorkspaceRuntimePolicy, overrideUrl?: string): string;
10
11
  export declare function isDaemonAvailable(baseUrl: string, workspaceRoot: string): Promise<boolean>;
12
+ export declare function sendAgentProtocolRuntimeRequest(baseUrl: string, protocol: Exclude<CliArgs["clientProtocol"], "stable-runtime">, request: RuntimeRequest): Promise<RuntimeResponse>;
@@ -1 +1 @@
1
- import{randomUUID as t}from"node:crypto";import e from"node:path";import{formatCliRuntimeEvent as r}from"../event-view.js";export async function runRequestThroughDaemon(e){if("local"===e.args.runtimeMode)return!1;const n=daemonBaseUrl(e.runtimePolicy,e.args.daemonUrl);if(!await isDaemonAvailable(n,e.args.workspaceRoot)){if("daemon"===e.args.runtimeMode)throw new Error(`stable-harness runtime: daemon required but unavailable at ${n}`);return!1}process.stderr.write(`stable-harness runtime: connected to daemon at ${n}\n`);const o=t(),s=function streamDaemonEvents(t,e,n){const o=new AbortController;return async function readDaemonEventStream(t,e,n,o){try{const s=await fetch(`${t}/events?requestId=${encodeURIComponent(e)}`,{signal:o}),a=s.body?.getReader();if(!a)return;await async function readSseMessages(t,e){let r="";for(;;){const{value:n,done:o}=await t.read();if(o)return;r+=Buffer.from(n).toString("utf8");const s=r.split("\n\n");r=s.pop()??"";for(const t of s){const r=parseSseEvent(t);r&&e(r)}}}(a,t=>{const e=r(t,n);e&&process.stdout.write(`${e}\n`)})}catch(t){o.aborted||process.stderr.write(`stable-harness daemon event stream failed: ${function errorMessage(t){return t instanceof Error?t.message:String(t)}(t)}\n`)}}(t,e,n,o.signal),{close:()=>o.abort()}}(n,o,e.eventView);try{const t=await async function postRuntimeRequest(t,e,r){const n=await fetch(`${t}/requests`,{method:"POST",body:JSON.stringify(toRuntimeRequest(e,r))});if(!n.ok)throw new Error(`stable-harness daemon request failed: HTTP ${n.status}`);return await n.json()}(n,e.args,o);return(e.args.trace||e.args.traceJson)&&await async function printDaemonTrace(t,e,r){const n=await fetch(`${t}/runs/${encodeURIComponent(e)}/trace`);if(!n.ok)return;const o=await n.json();if(r)process.stdout.write(`${JSON.stringify({trace:o})}\n`);else for(const t of o)process.stdout.write(`trace:${t.agentId}:${t.label}${t.detail?` ${JSON.stringify(t.detail)}`:""}\n`)}(n,t.requestId,e.args.traceJson),process.stdout.write(`${t.output}\n`),!0}finally{s.close()}}export function daemonBaseUrl(t,e){if(e?.trim())return e.replace(/\/$/u,"");const r=function protocolConfig(t,...e){for(const r of e){const e=readRecord(t[r]);if(e)return e}}(readRecord(t.protocols)??{},"stableRuntime","stable-runtime","http")??{};return`http://${function configString(t){if("string"!=typeof t||!t.trim())return;const e=t.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return e?process.env[e[1]]??e[2]:t}(r.host)??process.env.STABLE_HARNESS_RUNTIME_HOST??"127.0.0.1"}:${configNumber(r.port)??configNumber(process.env.STABLE_HARNESS_RUNTIME_PORT)??8641}`}export async function isDaemonAvailable(t,r){try{const n=await fetch(`${t}/inspect`,{signal:AbortSignal.timeout(300)});if(!n.ok)return!1;const o=await n.json();return Array.isArray(o.agents)&&Array.isArray(o.runs)&&o.workspaceRoot===e.resolve(r)}catch{return!1}}function toRuntimeRequest(t,e){return{input:t.prompt,requestId:e,...t.agentId?{agentId:t.agentId}:{},...t.sessionId?{sessionId:t.sessionId}:{},...t.toolId?{toolCall:{toolId:t.toolId,args:t.toolArgs}}:{},...t.workflowRunId?{workflow:{workflowId:t.workflowRunId,input:t.prompt}}:{}}}function parseSseEvent(t){const e=t.split("\n").find(t=>t.startsWith("data: "))?.slice(6);return e?JSON.parse(e):void 0}function readRecord(t){return"object"!=typeof t||null===t||Array.isArray(t)?void 0:t}function configNumber(t){return"number"==typeof t&&Number.isFinite(t)?t:"string"==typeof t&&t.trim()?Number(t):void 0}
1
+ import{randomUUID as t}from"node:crypto";import e from"node:path";import{formatCliRuntimeEvent as r}from"../event-view.js";export async function runRequestThroughDaemon(e){if("stable-runtime"!==e.args.clientProtocol)return await async function runRequestThroughAgentProtocol(e){if("local"===e.args.runtimeMode)throw new Error(`stable-harness protocol ${e.args.clientProtocol} requires a protocol server; use --protocol stable-runtime for local mode`);const r=function readAgentProtocol(t){if("stable-runtime"!==t)return t;throw new Error("stable-runtime is handled by the native daemon client")}(e.args.clientProtocol);if(e.args.trace||e.args.traceJson)throw new Error(`stable-harness protocol ${r} does not expose Stable Runtime traces; use --protocol stable-runtime for traces`);const o=agentProtocolBaseUrl(e.runtimePolicy,e.args.protocolUrl);if(!await async function isAgentProtocolAvailable(t,e){try{const r=await fetch(`${t}/health`,{signal:AbortSignal.timeout(300)});if(!r.ok)return!1;const o=await r.json();return Array.isArray(o.protocols)&&o.protocols.includes("agui"===e?"agui":e)}catch{return!1}}(o,r))throw new Error(`stable-harness protocol ${r} required but unavailable at ${o}`);process.stderr.write(`stable-harness runtime: connected through ${r} at ${o}\n`);const n=await sendAgentProtocolRuntimeRequest(o,r,toRuntimeRequest(e.args,t()));return process.stdout.write(`${n.output}\n`),!0}(e);if("local"===e.args.runtimeMode)return!1;const o=daemonBaseUrl(e.runtimePolicy,e.args.daemonUrl);if(!await isDaemonAvailable(o,e.args.workspaceRoot)){if("daemon"===e.args.runtimeMode)throw new Error(`stable-harness runtime: daemon required but unavailable at ${o}`);return!1}process.stderr.write(`stable-harness runtime: connected to daemon at ${o}\n`);const n=t(),s=function streamDaemonEvents(t,e,o){const n=new AbortController;return async function readDaemonEventStream(t,e,o,n){try{const s=await fetch(`${t}/events?requestId=${encodeURIComponent(e)}`,{signal:n}),a=s.body?.getReader();if(!a)return;await async function readSseMessages(t,e){let r="";for(;;){const{value:o,done:n}=await t.read();if(n)return;r+=Buffer.from(o).toString("utf8");const s=r.split("\n\n");r=s.pop()??"";for(const t of s){const r=parseSseEvent(t);r&&e(r)}}}(a,t=>{const e=r(t,o);e&&process.stdout.write(`${e}\n`)})}catch(t){n.aborted||process.stderr.write(`stable-harness daemon event stream failed: ${function errorMessage(t){return t instanceof Error?t.message:String(t)}(t)}\n`)}}(t,e,o,n.signal),{close:()=>n.abort()}}(o,n,e.eventView);try{const t=await async function postRuntimeRequest(t,e,r){const o=await fetch(`${t}/requests`,{method:"POST",body:JSON.stringify(toRuntimeRequest(e,r))});if(!o.ok)throw new Error(`stable-harness daemon request failed: HTTP ${o.status}`);return await o.json()}(o,e.args,n);return(e.args.trace||e.args.traceJson)&&await async function printDaemonTrace(t,e,r){const o=await fetch(`${t}/runs/${encodeURIComponent(e)}/trace`);if(!o.ok)return;const n=await o.json();if(r)process.stdout.write(`${JSON.stringify({trace:n})}\n`);else for(const t of n)process.stdout.write(`trace:${t.agentId}:${t.label}${t.detail?` ${JSON.stringify(t.detail)}`:""}\n`)}(o,t.requestId,e.args.traceJson),process.stdout.write(`${t.output}\n`),!0}finally{s.close()}}export function daemonBaseUrl(t,e){if(e?.trim())return e.replace(/\/$/u,"");const r=protocolConfig(readRecord(t.protocols)??{},"stableRuntime","stable-runtime","http")??{};return`http://${configString(r.host)??process.env.STABLE_HARNESS_RUNTIME_HOST??"127.0.0.1"}:${configNumber(r.port)??configNumber(process.env.STABLE_HARNESS_RUNTIME_PORT)??8641}`}export function agentProtocolBaseUrl(t,e){if(e?.trim())return e.replace(/\/$/u,"");const r=readRecord(t.protocols)??{},o=protocolConfig(r,"agentProtocols","agent-protocols")??function agentProtocolConfigFromIndividual(t){for(const e of["acp","a2a","agui"]){const r=protocolConfig(t,e);if(r)return r}}(r)??{};return`http://${configString(o.host)??process.env.STABLE_HARNESS_AGENT_PROTOCOL_HOST??"127.0.0.1"}:${configNumber(o.port)??configNumber(process.env.STABLE_HARNESS_AGENT_PROTOCOL_PORT)??8650}`}export async function isDaemonAvailable(t,r){try{const o=await fetch(`${t}/inspect`,{signal:AbortSignal.timeout(300)});if(!o.ok)return!1;const n=await o.json();return Array.isArray(n.agents)&&Array.isArray(n.runs)&&n.workspaceRoot===e.resolve(r)}catch{return!1}}export async function sendAgentProtocolRuntimeRequest(e,r,o){const n="a2a"===r?await async function sendA2aRequest(t,e){const r=await postJson(`${t}/a2a/message:send`,{requestId:e.requestId,agentId:e.agentId,runtimeRequest:e,message:{role:"user",contextId:e.sessionId,parts:[{kind:"text",text:e.input}]}});return r.task?.status?.message?.parts?.map(t=>t.text).filter(Boolean).join("\n")??""}(e,o):"agui"===r?await async function sendAguiRequest(t,e){const r=await fetch(`${t}/ag-ui/runs`,{method:"POST",body:JSON.stringify({input:e.input,runId:e.requestId,threadId:e.sessionId,agentId:e.agentId,runtimeRequest:e})});if(!r.ok||!r.body)throw new Error(`AG-UI request failed: HTTP ${r.status}`);return(await async function readProtocolSseMessages(t){const e=[];let r="";for(;;){const{value:o,done:n}=await t.read();if(n)return e;r+=Buffer.from(o).toString("utf8");const s=r.split("\n\n");r=s.pop()??"";for(const t of s){const r=t.split("\n").find(t=>t.startsWith("data: "))?.slice(6);r&&e.push(JSON.parse(r))}}}(r.body.getReader())).map(readAguiText).filter(Boolean).join("")}(e,o):await async function sendAcpRequest(e,r){const o=await postJson(`${e}/acp`,{jsonrpc:"2.0",id:r.requestId??t(),method:"session/prompt",params:{prompt:r.input,sessionId:r.sessionId,agentId:r.agentId,runtimeRequest:r}});if(o.error)throw new Error(o.error.message??"ACP request failed");return o.result?._meta?.output??""}(e,o);return{requestId:o.requestId??t(),sessionId:o.sessionId??"",agentId:o.agentId??"",state:"completed",output:n}}async function postJson(t,e){const r=await fetch(t,{method:"POST",body:JSON.stringify(e)});if(!r.ok)throw new Error(`protocol request failed: HTTP ${r.status}`);return await r.json()}function toRuntimeRequest(t,e){return{input:t.prompt,requestId:e,...t.agentId?{agentId:t.agentId}:{},...t.sessionId?{sessionId:t.sessionId}:{},...t.toolId?{toolCall:{toolId:t.toolId,args:t.toolArgs}}:{},...t.workflowRunId?{workflow:{workflowId:t.workflowRunId,input:t.prompt}}:{}}}function readAguiText(t){const e=readRecord(t);return"TextMessageChunk"===e?.type&&"string"==typeof e.delta?e.delta:""}function parseSseEvent(t){const e=t.split("\n").find(t=>t.startsWith("data: "))?.slice(6);return e?JSON.parse(e):void 0}function protocolConfig(t,...e){for(const r of e){const e=readRecord(t[r]);if(e)return e}}function readRecord(t){return"object"!=typeof t||null===t||Array.isArray(t)?void 0:t}function configString(t){if("string"!=typeof t||!t.trim())return;const e=t.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return e?process.env[e[1]]??e[2]:t}function configNumber(t){return"number"==typeof t&&Number.isFinite(t)?t:"string"==typeof t&&t.trim()?Number(t):void 0}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/cli",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
18
- "@stable-harness/adapter-langgraph": "0.0.105",
19
- "@stable-harness/core": "0.0.105",
20
- "@stable-harness/memory": "0.0.105",
21
- "@stable-harness/protocols": "0.0.105",
22
- "@stable-harness/tool-gateway": "0.0.105",
23
- "@stable-harness/workspace-yaml": "0.0.105"
17
+ "@stable-harness/adapter-deepagents": "0.0.107",
18
+ "@stable-harness/adapter-langgraph": "0.0.107",
19
+ "@stable-harness/core": "0.0.107",
20
+ "@stable-harness/memory": "0.0.107",
21
+ "@stable-harness/protocols": "0.0.107",
22
+ "@stable-harness/tool-gateway": "0.0.107",
23
+ "@stable-harness/workspace-yaml": "0.0.107"
24
24
  }
25
25
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
15
- "@stable-harness/memory": "0.0.105"
14
+ "@stable-harness/governance": "0.0.107",
15
+ "@stable-harness/memory": "0.0.107"
16
16
  }
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/evaluation",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105"
13
+ "@stable-harness/core": "0.0.107"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
3
+ "version": "0.0.107",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -4,4 +4,23 @@ export type AgentProtocolServerOptions = {
4
4
  baseUrl?: string;
5
5
  enabledProtocols?: Array<"acp" | "a2a" | "agui">;
6
6
  };
7
+ type JsonRpcMessage = {
8
+ jsonrpc?: string;
9
+ id?: string | number | null;
10
+ method?: string;
11
+ params?: unknown;
12
+ };
7
13
  export declare function createAgentProtocolHttpServer(runtime: StableHarnessRuntime, options?: AgentProtocolServerOptions): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
14
+ export declare function handleAcpJsonRpcMessage(runtime: StableHarnessRuntime, message: JsonRpcMessage): Promise<{
15
+ jsonrpc: string;
16
+ id: string | number | null | undefined;
17
+ result: unknown;
18
+ } | {
19
+ jsonrpc: string;
20
+ id: string | number | null | undefined;
21
+ error: {
22
+ code: number;
23
+ message: string;
24
+ };
25
+ } | undefined>;
26
+ export {};
@@ -1 +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
+ import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,a={}){const r=new Set(a.enabledProtocols??["acp","a2a","agui"]);return e(async(e,s)=>{try{if("GET"===e.method&&"/health"===e.url)return void sendJson(s,200,{ok:!0,protocols:[...r]});if("GET"===e.method&&"/capabilities"===e.url)return void sendJson(s,200,function createProtocolCapabilityManifest(e){return{protocol:"stable-harness-agent-protocols",protocols:[...e].sort().map(e=>({id:e,transports:"acp"===e?["stdio","http-jsonrpc"]:"a2a"===e?["http-json","sse"]:["http-sse"],session:"acp"===e?{level:"protocol-session",mapsTo:"sessionId",operatorApi:!1}:"a2a"===e?{level:"continuity",mapsTo:"contextId|taskId",operatorApi:!1}:{level:"continuity",mapsTo:"threadId|sessionId",operatorApi:!1}})),stableRuntime:{session:{level:"operator-api",operatorApi:!0}}}}(r));if(r.has("a2a")&&await async function handleA2a(e,t,a,r){if("GET"===t.method&&("/.well-known/agent-card.json"===t.url||"/a2a/agent-card.json"===t.url))return sendJson(a,200,function createAgentCard(e,t){const a=e.inspect();return{protocolVersion:"1.0",name:a.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:a.agents.map(e=>({id:e,name:e,description:e}))}}(e,r)),!0;const s=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&s){const t=e.inspectRequest(s);return sendJson(a,t?200:404,t?{task:toA2aTask(t)}:{error:"task_not_found"}),!0}if("GET"===t.method&&"/a2a/tasks"===t.url)return sendJson(a,200,{tasks:e.listRequests().map(toA2aTaskSummary)}),!0;const n=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);return"POST"===t.method&&n?(e.cancel(n,"a2a_cancel"),sendJson(a,200,{task:e.inspectRequest(n)}),!0):"POST"===t.method&&"/a2a/message:send"===t.url?(sendJson(a,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),a),!0):"POST"===t.method&&"/a2a/rpc"===t.url&&(await async function handleA2aRpc(e,t,a){if("SendMessage"===t.method){const r=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(a,200,jsonRpcResult(t.id,{task:toA2aTask(r)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const r=readString(readRecord(t.params)?.id),s=r?e.inspectRequest(r):void 0;return void sendJson(a,s?200:404,s?jsonRpcResult(t.id,toA2aTask(s)):jsonRpcError(t.id,-32004,"task_not_found"))}"ListTasks"!==t.method?sendJson(a,404,jsonRpcError(t.id,-32601,"method_not_found")):sendJson(a,200,jsonRpcResult(t.id,{tasks:e.listRequests().map(toA2aTaskSummary)}))}else await streamA2a(e,readRecord(t.params)??{},a,t.id)}(e,await readJson(t),a),!0)}(t,e,s,a))return;if(r.has("agui")&&await async function handleAgui(e,t,a){return"GET"===t.method&&"/ag-ui/capabilities"===t.url?(sendJson(a,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,a){const r=readRuntimeRequestExtension(t),s=r?.requestId??readString(t.runId)??crypto.randomUUID(),n=r?.sessionId??readString(t.threadId)??readString(t.sessionId)??`thread-${s}`,o=readPromptText(t);writeSseHeaders(a),writeSse(a,{type:"RunStarted",threadId:n,runId:s,input:{messages:t.messages,input:o}});const d=e.subscribe(e=>{e.requestId===s&&writeSse(a,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 d=await e.request(r??{input:o,requestId:s,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),i=`${s}-message`;d.output&&writeSse(a,{type:"TextMessageChunk",messageId:i,role:"assistant",delta:d.output}),writeSse(a,{type:"RunFinished",threadId:n,runId:s,outcome:{type:"completed"===d.state?"success":"interrupt"},result:d.output})}catch(e){writeSse(a,{type:"RunError",threadId:n,runId:s,message:errorMessage(e)})}finally{d(),a.end()}}(e,await readJson(t),a),!0)}(t,e,s))return;if(r.has("acp")&&await async function handleAcp(e,t,a){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(a,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 r=await readJson(t),s=await handleAcpJsonRpcMessage(e,r);return s?sendJson(a,200,s):a.writeHead(204).end(),!0}return!1}(t,e,s))return;sendJson(s,404,{error:"not_found"})}catch(e){sendJson(s,400,{error:errorMessage(e)})}})}export async function handleAcpJsonRpcMessage(e,t){if(!t.id&&"session/cancel"===t.method){const a=readString(readRecord(t.params)?.sessionId);return void(a&&e.listRequests({sessionId:a,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 a=readRecord(t.params)??{},r=readRuntimeRequestExtension(a),s=await e.request(r??{input:readPromptText(a),sessionId:readString(a.sessionId),agentId:readString(a.agentId),metadata:{protocol:"acp",transport:"http-jsonrpc"}});return jsonRpcResult(t.id,{stopReason:"completed"===s.state?"end_turn":"refusal",_meta:{requestId:s.requestId,output:s.output}})}return jsonRpcError(t.id,-32601,"method_not_found")}async function streamA2a(e,t,a,r){const s=toA2aRuntimeRequest(t);writeSseHeaders(a);const n=e.subscribe(e=>{e.requestId===s.requestId&&writeSse(a,{jsonrpc:"2.0",id:r,result:{event:toA2aEvent(e)}})});try{const t=await e.request(s);writeSse(a,{jsonrpc:"2.0",id:r,result:{task:toA2aTask(t),final:!0}})}finally{n(),a.end()}}function toA2aRuntimeRequest(e){const t=readRuntimeRequestExtension(e);if(t)return{...t,metadata:{...t.metadata,protocol:"a2a"}};const a=readRecord(e.message)??e;return{input:readPromptText(a),requestId:readString(e.requestId)??readString(a.messageId),sessionId:readString(e.taskId)??readString(a.taskId)??readString(a.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,a="output"in e?e.output:e.output??"",r="requestId"in e?e.requestId:e.summary.requestId;return{id:r,contextId:"sessionId"in e?e.sessionId:e.summary.sessionId,status:{state:"completed"===t?"completed":t,message:a?{role:"agent",messageId:`${r}-response`,parts:[{kind:"text",text:a}]}:void 0}}}function toA2aTaskSummary(e){return{id:e.requestId,contextId:e.sessionId,status:{state:e.state},metadata:{agentId:e.agentId}}}function toA2aEvent(e){const t="runtime.request.completed"===e.type?{role:"agent",messageId:`${e.requestId}-response`,parts:[{kind:"text",text:e.output}]}:void 0;return{kind:"status-update",taskId:e.requestId,contextId:e.sessionId,status:{state:(a=e.type,"runtime.request.started"===a?"working":"runtime.request.completed"===a?"completed":"runtime.request.failed"===a?"failed":"runtime.request.cancelled"===a?"canceled":"working"),message:t},metadata:{stableHarnessEvent:e}};var a}function readRuntimeRequestExtension(e){const t=readRecord(e.runtimeRequest)??readRecord(readRecord(e.metadata)?.runtimeRequest);if(t)return{input:readString(t.input)??"",...readString(t.requestId)?{requestId:readString(t.requestId)}:{},...readString(t.sessionId)?{sessionId:readString(t.sessionId)}:{},...readString(t.agentId)?{agentId:readString(t.agentId)}:{},...readToolCall(t.toolCall)?{toolCall:readToolCall(t.toolCall)}:{},...readWorkflow(t.workflow)?{workflow:readWorkflow(t.workflow)}:{},...readRecord(t.metadata)?{metadata:readRecord(t.metadata)}:{}}}function readToolCall(e){const t=readRecord(e),a=readString(t?.toolId);return a?{toolId:a,args:t?.args}:void 0}function readWorkflow(e){const t=readRecord(e);if(t)return{...readString(t.workflowId)?{workflowId:readString(t.workflowId)}:{},...readString(t.routeId)?{routeId:readString(t.routeId)}:{},...void 0!==t.input?{input:t.input}:{},...readRecord(t.metadata)?{metadata:readRecord(t.metadata)}:{}}}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 a=(e??"").match(t);return a?.[1]?decodeURIComponent(a[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,a){return{jsonrpc:"2.0",id:e,error:{code:t,message:a}}}function sendJson(e,t,a){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(a))}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 a of e)t.push(Buffer.isBuffer(a)?a:Buffer.from(a));return t.length>0?JSON.parse(Buffer.concat(t).toString("utf8")):{}}function errorMessage(e){return e instanceof Error?e.message:String(e)}
@@ -1,5 +1,5 @@
1
1
  export { createInProcessClient } from "./in-process-client.js";
2
- export { createAgentProtocolHttpServer } from "./agent-protocols.js";
2
+ export { createAgentProtocolHttpServer, handleAcpJsonRpcMessage } from "./agent-protocols.js";
3
3
  export type { AgentProtocolServerOptions } from "./agent-protocols.js";
4
4
  export { createHttpServer } from "./http-server.js";
5
5
  export { createOpenAiCompatibleHttpServer } from "./openai-compatible.js";
@@ -1 +1 @@
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
+ export{createInProcessClient}from"./in-process-client.js";export{createAgentProtocolHttpServer,handleAcpJsonRpcMessage}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.105",
3
+ "version": "0.0.107",
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.105"
13
+ "@stable-harness/core": "0.0.107"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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.105",
3
+ "version": "0.0.107",
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.105"
14
+ "@stable-harness/core": "0.0.107"
15
15
  }
16
16
  }