stable-harness 0.0.109 → 0.0.111

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 (30) hide show
  1. package/docs/guides/runtime-governance-proof.md +24 -0
  2. package/docs/protocols/agent-protocols.md +13 -0
  3. package/docs/protocols/external-sdk-interop.md +78 -0
  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 +1 -0
  10. package/node_modules/@stable-harness/protocols/dist/src/agent-protocols.js +1 -1
  11. package/node_modules/@stable-harness/protocols/package.json +2 -2
  12. package/node_modules/@stable-harness/tool-gateway/package.json +1 -1
  13. package/node_modules/@stable-harness/workspace-yaml/package.json +2 -2
  14. package/package.json +10 -9
  15. package/packages/adapter-deepagents/package.json +2 -2
  16. package/packages/adapter-langgraph/package.json +2 -2
  17. package/packages/cli/dist/src/console/session.js +1 -1
  18. package/packages/cli/dist/src/daemon/client.d.ts +1 -1
  19. package/packages/cli/dist/src/daemon/client.js +1 -1
  20. package/packages/cli/dist/src/server.js +1 -1
  21. package/packages/cli/package.json +8 -8
  22. package/packages/core/package.json +3 -3
  23. package/packages/evaluation/package.json +2 -2
  24. package/packages/governance/package.json +1 -1
  25. package/packages/memory/package.json +1 -1
  26. package/packages/protocols/dist/src/agent-protocols.d.ts +1 -0
  27. package/packages/protocols/dist/src/agent-protocols.js +1 -1
  28. package/packages/protocols/package.json +2 -2
  29. package/packages/tool-gateway/package.json +1 -1
  30. package/packages/workspace-yaml/package.json +2 -2
@@ -46,6 +46,30 @@ curl -fsS http://127.0.0.1:8642/v1/models
46
46
  | Runtime work can be inspected | request, session, event, artifact, approval, and replay APIs |
47
47
  | Deployment has a stable boundary | generated Dockerfile, Compose file, `.env.example`, and `stable-workspace.json` |
48
48
 
49
+ ## Automated Contract Coverage
50
+
51
+ The product proof should stay tied to tests that exercise structured runtime
52
+ contracts rather than marketing prose:
53
+
54
+ ```bash
55
+ npm run build
56
+ node --test dist/test/protocol/http-runtime.test.js
57
+ node --test dist/test/runtime/policy/direct-tool-guardrail.test.js
58
+ ```
59
+
60
+ The key contract checks are:
61
+
62
+ - `same workspace runs through SDK and HTTP runtime surfaces`: the same YAML
63
+ workspace inventory runs through the in-process SDK and HTTP runtime facade
64
+ while preserving request, trace, and tool evidence.
65
+ - `native runtime blocks unknown direct tools with structured inventory
66
+ diagnostics before execution`: missing or unavailable tools emit
67
+ `runtime.inventory.repair` diagnostics before any direct tool execution
68
+ starts.
69
+ - `native runtime reports invalid direct-tool arguments before invoking the
70
+ tool`: malformed arguments emit structured `runtime.tool.failure` evidence
71
+ and do not call the registered tool implementation.
72
+
49
73
  ## Public Copy Boundary
50
74
 
51
75
  Stable Harness proof should describe runtime governance: sessions, events,
@@ -38,6 +38,19 @@ Stable Runtime operator API. Stable Runtime HTTP/SSE and the SDK remain the
38
38
  first-party surfaces for listing sessions, deleting session history, inspecting
39
39
  requests, managing memory, approvals, artifacts, and native event filters.
40
40
 
41
+ Reconnect and replay behavior is intentionally layered:
42
+
43
+ | Surface | Reconnect behavior |
44
+ | --- | --- |
45
+ | Stable Runtime SSE | Replays stored runtime events when clients reconnect with request/session/type filters. |
46
+ | A2A streaming | Emits live task status updates for a request stream. |
47
+ | A2A task subscribe | Replays the current task snapshot and streams future updates when the task is still running. |
48
+ | AG-UI stream | Emits one run stream; use Stable Runtime SSE for first-party replay/resume. |
49
+ | ACP stdio/HTTP | Session IDs resume prompt continuity; progress replay is not provided through ACP. |
50
+
51
+ For external SDK compatibility status, see
52
+ [External SDK Interop Spike](./external-sdk-interop.md).
53
+
41
54
  ## A2A
42
55
 
43
56
  A2A is exposed as an HTTP+JSON and SSE facade:
@@ -0,0 +1,78 @@
1
+ # External SDK Interop Spike
2
+
3
+ Stable Harness protocol facades remain adapters over the native Stable Runtime
4
+ contract. External SDK interop tests must validate protocol payloads and
5
+ transport behavior without moving SDK-specific execution semantics into the
6
+ runtime.
7
+
8
+ ## Current CI-Safe Interop
9
+
10
+ | Protocol | External package | CI status | Notes |
11
+ | --- | --- | --- | --- |
12
+ | AG-UI | `@ag-ui/core` | Enabled | `test/protocol/protocol-conformance.test.ts` parses emitted events with official `EventSchemas`. |
13
+ | A2A | `@a2a-js/sdk` | Fixture-ready | A2A stream fixtures assert task snapshot, status updates, artifacts, and version handling. A full SDK client test should be added only after the client API is stable enough for CI. |
14
+ | ACP | `@agentclientprotocol/sdk` | Fixture-ready | ACP JSON-RPC transcript fixtures exercise the same handler used by HTTP and stdio. A full `ClientSideConnection` test needs a small stdio transport harness. |
15
+
16
+ ## Compatibility Findings
17
+
18
+ ### A2A
19
+
20
+ Stable Harness now exposes the core HTTP/SSE facade expected by A2A clients:
21
+
22
+ - agent card discovery
23
+ - `message:send`
24
+ - `message:stream`
25
+ - task lookup
26
+ - task subscribe
27
+ - cancel
28
+ - task artifact projection
29
+ - `A2A-Version` rejection for unsupported versions
30
+
31
+ Remaining SDK interop work is client-specific. `@a2a-js/sdk` is appropriate for
32
+ a future real client canary, but the CI test should be isolated from optional
33
+ Express/server helpers and should exercise only client-side HTTP/SSE calls.
34
+
35
+ ### AG-UI
36
+
37
+ AG-UI is the strongest current external SDK validation path. The facade emits
38
+ canonical event enum values and validates against `@ag-ui/core` runtime schemas:
39
+
40
+ - `RUN_STARTED`
41
+ - `CUSTOM`
42
+ - `TEXT_MESSAGE_START`
43
+ - `TEXT_MESSAGE_CONTENT`
44
+ - `TEXT_MESSAGE_END`
45
+ - `RUN_FINISHED`
46
+ - `RUN_ERROR`
47
+
48
+ This should stay in CI because it is schema-only, deterministic, and does not
49
+ require a browser or frontend framework.
50
+
51
+ ### ACP
52
+
53
+ Stable Harness supports both:
54
+
55
+ - `stable-harness acp-stdio -w ./workspace`
56
+ - `/acp` HTTP JSON-RPC custom transport
57
+
58
+ Both transports share `handleAcpJsonRpcMessage`. The current CI fixture uses the
59
+ shared handler directly, which proves protocol response shape without needing a
60
+ subprocess. A future `@agentclientprotocol/sdk` client test should create a
61
+ small stdio bridge around the CLI entrypoint and verify:
62
+
63
+ - initialize
64
+ - session/new
65
+ - session/prompt
66
+ - session/cancel notification
67
+
68
+ ## Follow-Up Gate
69
+
70
+ Do not add a heavyweight SDK dependency or browser test to the default full
71
+ suite unless it satisfies all of these:
72
+
73
+ - deterministic under local and GitHub Actions execution
74
+ - no external service dependency
75
+ - no long-lived subprocess leak
76
+ - no protocol-specific runtime semantics in core
77
+ - failure message points to the protocol facade boundary
78
+
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
18
+ "@stable-harness/core": "0.0.111",
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.109",
3
+ "version": "0.0.111",
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.109"
14
+ "@stable-harness/core": "0.0.111"
15
15
  }
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
15
- "@stable-harness/memory": "0.0.109"
14
+ "@stable-harness/governance": "0.0.111",
15
+ "@stable-harness/memory": "0.0.111"
16
16
  }
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
3
+ "version": "0.0.111",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -3,6 +3,7 @@ import type { StableHarnessRuntime } from "@stable-harness/core";
3
3
  export type AgentProtocolServerOptions = {
4
4
  baseUrl?: string;
5
5
  enabledProtocols?: Array<"acp" | "a2a" | "agui">;
6
+ bearerToken?: string;
6
7
  };
7
8
  type JsonRpcMessage = {
8
9
  jsonrpc?: string;
@@ -1 +1 @@
1
- import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,a={}){const s=new Set(a.enabledProtocols??["acp","a2a","agui"]);return e(async(e,r)=>{try{if("GET"===e.method&&"/health"===e.url)return void sendJson(r,200,{ok:!0,protocols:[...s]});if("GET"===e.method&&"/capabilities"===e.url)return void sendJson(r,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}}}}(s));if(s.has("a2a")&&await async function handleA2a(e,t,a,s){if(!function validateA2aVersion(e,t){const a=e.headers["a2a-version"],s=Array.isArray(a)?a[0]:a;return!s||"0.3"===s||"1.0"===s||(sendJson(t,400,jsonRpcError(null,-32006,"version_not_supported")),!1)}(t,a))return!0;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,pushNotifications:!1},defaultInputModes:["text/plain"],defaultOutputModes:["text/plain"],skills:a.agents.map(e=>({id:e,name:e,description:e}))}}(e,s)),!0;const r=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&r){const t=e.inspectRequest(r);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\/([^/]+):subscribe$/u);if("GET"===t.method&&n)return function streamA2aTask(e,t,a){const s=e.inspectRequest(t);if(!s)return void sendJson(a,404,{error:"task_not_found"});if(writeSseHeaders(a),writeSse(a,{jsonrpc:"2.0",result:{task:toA2aTask(s)}}),function isTerminalState(e){return"completed"===e||"failed"===e||"cancelled"===e||"canceled"===e||"rejected"===e}(s.summary.state))return void a.end();const r=e.subscribe(e=>{e.requestId===t&&writeSse(a,{jsonrpc:"2.0",result:{event:toA2aEvent(e)}})});a.on("close",r)}(e,n,a),!0;const o=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);return"POST"===t.method&&o?(e.cancel(o,"a2a_cancel"),sendJson(a,200,{task:e.inspectRequest(o)}),!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 s=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(a,200,jsonRpcResult(t.id,{task:toA2aTask(s)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const s=readString(readRecord(t.params)?.id),r=s?e.inspectRequest(s):void 0;return void sendJson(a,r?200:404,r?jsonRpcResult(t.id,toA2aTask(r)):jsonRpcError(t.id,-32004,"task_not_found"))}if("ListTasks"!==t.method){if("CancelTask"===t.method){const s=readString(readRecord(t.params)?.id);s&&e.cancel(s,"a2a_cancel");const r=s?e.inspectRequest(s):void 0;return void sendJson(a,r?200:404,r?jsonRpcResult(t.id,toA2aTask(r)):jsonRpcError(t.id,-32004,"task_not_found"))}sendJson(a,404,jsonRpcError(t.id,-32601,"method_not_found"))}else 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,r,a))return;if(s.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:["RUN_STARTED","TEXT_MESSAGE_START","TEXT_MESSAGE_CONTENT","TEXT_MESSAGE_END","CUSTOM","RUN_FINISHED","RUN_ERROR"]}),!0):"POST"===t.method&&"/ag-ui/runs"===t.url&&(await async function streamAgui(e,t,a){const s=readRuntimeRequestExtension(t),r=s?.requestId??readString(t.runId)??crypto.randomUUID(),n=s?.sessionId??readString(t.threadId)??readString(t.sessionId)??`thread-${r}`,o=readPromptText(t);writeSseHeaders(a),writeSse(a,{type:"RUN_STARTED",timestamp:Date.now(),threadId:n,runId:r,input:{messages:t.messages,input:o}});const i=e.subscribe(e=>{e.requestId===r&&writeSse(a,function toAguiEvent(e){return"runtime.progress.narration"===e.type?{type:"CUSTOM",timestamp:Date.now(),name:"stable-harness.progress",value:e}:"runtime.tool.direct.started"===e.type?{type:"TOOL_CALL_START",timestamp:Date.now(),toolCallId:`${e.requestId}:${e.toolId}`,toolCallName:e.toolId}:"runtime.tool.direct.completed"===e.type?{type:"TOOL_CALL_RESULT",timestamp:Date.now(),messageId:`${e.requestId}-message`,toolCallId:`${e.requestId}:${e.toolId}`,content:JSON.stringify(e.output),role:"tool"}:{type:"CUSTOM",timestamp:Date.now(),name:`stable-harness.${e.type}`,value:e}}(e))});try{const i=await e.request(s??{input:o,requestId:r,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),d=`${r}-message`;i.output&&function writeAguiText(e,t,a){writeSse(e,{type:"TEXT_MESSAGE_START",timestamp:Date.now(),messageId:t,role:"assistant"}),writeSse(e,{type:"TEXT_MESSAGE_CONTENT",timestamp:Date.now(),messageId:t,delta:a}),writeSse(e,{type:"TEXT_MESSAGE_END",timestamp:Date.now(),messageId:t})}(a,d,i.output),writeSse(a,{type:"RUN_FINISHED",timestamp:Date.now(),threadId:n,runId:r,result:i.output})}catch(e){writeSse(a,{type:"RUN_ERROR",timestamp:Date.now(),threadId:n,runId:r,message:errorMessage(e),code:"runtime_error"})}finally{i(),a.end()}}(e,await readJson(t),a),!0)}(t,e,r))return;if(s.has("acp")&&await async function handleAcp(e,t,a){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(a,200,acpInitializeResult()),!0;if("POST"===t.method&&"/acp"===t.url){const s=await readJson(t),r=await handleAcpJsonRpcMessage(e,s);return r?sendJson(a,200,r):a.writeHead(204).end(),!0}return!1}(t,e,r))return;sendJson(r,404,{error:"not_found"})}catch(e){sendJson(r,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,acpInitializeResult(0,readRecord(t.params)));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)??{},s=readRuntimeRequestExtension(a),r=await e.request(s??{input:readPromptText(a),sessionId:readString(a.sessionId),agentId:readString(a.agentId),metadata:{protocol:"acp",transport:"http-jsonrpc"}});return jsonRpcResult(t.id,{stopReason:"completed"===r.state?"end_turn":"refusal",_meta:{requestId:r.requestId,output:r.output}})}return jsonRpcError(t.id,-32601,"method_not_found")}async function streamA2a(e,t,a,s){const r=toA2aRuntimeRequest(t);var n;writeSseHeaders(a),writeSse(a,{jsonrpc:"2.0",id:s,result:{task:(n=r,{id:n.requestId??crypto.randomUUID(),contextId:n.sessionId,status:{state:"submitted",timestamp:(new Date).toISOString()},metadata:{agentId:n.agentId}})}});const o=e.subscribe(e=>{e.requestId===r.requestId&&writeSse(a,{jsonrpc:"2.0",id:s,result:{event:toA2aEvent(e)}})});try{const t=await e.request(r);writeSse(a,{jsonrpc:"2.0",id:s,result:{task:toA2aTask(t),final:!0}})}finally{o(),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??"",s="requestId"in e?e.requestId:e.summary.requestId;return{id:s,contextId:"sessionId"in e?e.sessionId:e.summary.sessionId,status:{state:"completed"===t?"completed":t,message:a?{role:"agent",messageId:`${s}-response`,parts:[{kind:"text",text:a}]}:void 0,timestamp:(new Date).toISOString()},artifacts:a?[{artifactId:`${s}-output`,name:"result",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,timestamp:(new Date).toISOString()},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 acpInitializeResult(e,t){return"number"==typeof t?.protocolVersion&&t.protocolVersion,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},mcpCapabilities:{http:!1,sse:!1},session:{update:!0,cancel:!0,list:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"},authMethods:[]}}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
+ import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,r={}){const a=new Set(r.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:[...a]});if(!function isAuthorized(e,t){return!t.bearerToken||e.headers.authorization===`Bearer ${t.bearerToken}`}(e,r))return void sendJson(s,401,{error:"unauthorized"});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}}}}(a));if(a.has("a2a")&&await async function handleA2a(e,t,r,a){if(!function validateA2aVersion(e,t){const r=e.headers["a2a-version"],a=Array.isArray(r)?r[0]:r;return!a||"0.3"===a||"1.0"===a||(sendJson(t,400,jsonRpcError(null,-32006,"version_not_supported")),!1)}(t,r))return!0;if("GET"===t.method&&("/.well-known/agent-card.json"===t.url||"/a2a/agent-card.json"===t.url))return sendJson(r,200,function createAgentCard(e,t){const r=e.inspect();return{protocolVersion:"1.0",name:r.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,pushNotifications:!1},defaultInputModes:["text/plain"],defaultOutputModes:["text/plain"],skills:r.agents.map(e=>({id:e,name:e,description:e}))}}(e,a)),!0;const s=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):subscribe$/u);if("GET"===t.method&&s)return function streamA2aTask(e,t,r){const a=e.inspectRequest(t);if(!a)return void sendJson(r,404,{error:"task_not_found"});if(writeSseHeaders(r),writeSse(r,{jsonrpc:"2.0",result:{task:toA2aTask(a)}}),function isTerminalState(e){return"completed"===e||"failed"===e||"cancelled"===e||"canceled"===e||"rejected"===e}(a.summary.state))return void r.end();const s=e.subscribe(e=>{e.requestId===t&&writeSse(r,{jsonrpc:"2.0",result:{event:toA2aEvent(e)}})});r.on("close",s)}(e,s,r),!0;const n=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);if("POST"===t.method&&n)return e.cancel(n,"a2a_cancel"),sendJson(r,200,{task:e.inspectRequest(n)}),!0;const o=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&o){const t=e.inspectRequest(o);return sendJson(r,t?200:404,t?{task:toA2aTask(t)}:{error:"task_not_found"}),!0}return"GET"===t.method&&"/a2a/tasks"===t.url?(sendJson(r,200,{tasks:e.listRequests().map(toA2aTaskSummary)}),!0):"POST"===t.method&&"/a2a/message:send"===t.url?(sendJson(r,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),r),!0):"POST"===t.method&&"/a2a/rpc"===t.url&&(await async function handleA2aRpc(e,t,r){if("SendMessage"===t.method){const a=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(r,200,jsonRpcResult(t.id,{task:toA2aTask(a)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const a=readString(readRecord(t.params)?.id),s=a?e.inspectRequest(a):void 0;return void sendJson(r,s?200:404,s?jsonRpcResult(t.id,toA2aTask(s)):jsonRpcError(t.id,-32004,"task_not_found"))}if("ListTasks"!==t.method){if("CancelTask"===t.method){const a=readString(readRecord(t.params)?.id);a&&e.cancel(a,"a2a_cancel");const s=a?e.inspectRequest(a):void 0;return void sendJson(r,s?200:404,s?jsonRpcResult(t.id,toA2aTask(s)):jsonRpcError(t.id,-32004,"task_not_found"))}sendJson(r,404,jsonRpcError(t.id,-32601,"method_not_found"))}else sendJson(r,200,jsonRpcResult(t.id,{tasks:e.listRequests().map(toA2aTaskSummary)}))}else await streamA2a(e,readRecord(t.params)??{},r,t.id)}(e,await readJson(t),r),!0)}(t,e,s,r))return;if(a.has("agui")&&await async function handleAgui(e,t,r){return"GET"===t.method&&"/ag-ui/capabilities"===t.url?(sendJson(r,200,{protocol:"ag-ui",transports:["http+sse"],events:["RUN_STARTED","TEXT_MESSAGE_START","TEXT_MESSAGE_CONTENT","TEXT_MESSAGE_END","CUSTOM","RUN_FINISHED","RUN_ERROR"]}),!0):"POST"===t.method&&"/ag-ui/runs"===t.url&&(await async function streamAgui(e,t,r){const a=readRuntimeRequestExtension(t),s=a?.requestId??readString(t.runId)??crypto.randomUUID(),n=a?.sessionId??readString(t.threadId)??readString(t.sessionId)??`thread-${s}`,o=readPromptText(t);var i;writeSseHeaders(r),writeSse(r,{type:"RUN_STARTED",timestamp:Date.now(),threadId:n,runId:s,input:(i={body:t,input:o,threadId:n,runId:s},{threadId:i.threadId,runId:i.runId,messages:Array.isArray(i.body.messages)?i.body.messages:[{id:`${i.runId}-user`,role:"user",content:i.input}],tools:Array.isArray(i.body.tools)?i.body.tools:[],context:Array.isArray(i.body.context)?i.body.context:[]})});const d=e.subscribe(e=>{e.requestId===s&&writeSse(r,function toAguiEvent(e){return"runtime.progress.narration"===e.type?{type:"CUSTOM",timestamp:Date.now(),name:"stable-harness.progress",value:e}:"runtime.tool.direct.started"===e.type?{type:"TOOL_CALL_START",timestamp:Date.now(),toolCallId:`${e.requestId}:${e.toolId}`,toolCallName:e.toolId}:"runtime.tool.direct.completed"===e.type?{type:"TOOL_CALL_RESULT",timestamp:Date.now(),messageId:`${e.requestId}-message`,toolCallId:`${e.requestId}:${e.toolId}`,content:JSON.stringify(e.output),role:"tool"}:{type:"CUSTOM",timestamp:Date.now(),name:`stable-harness.${e.type}`,value:e}}(e))});try{const i=await e.request(a??{input:o,requestId:s,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),d=`${s}-message`;i.output&&function writeAguiText(e,t,r){writeSse(e,{type:"TEXT_MESSAGE_START",timestamp:Date.now(),messageId:t,role:"assistant"}),writeSse(e,{type:"TEXT_MESSAGE_CONTENT",timestamp:Date.now(),messageId:t,delta:r}),writeSse(e,{type:"TEXT_MESSAGE_END",timestamp:Date.now(),messageId:t})}(r,d,i.output),writeSse(r,{type:"RUN_FINISHED",timestamp:Date.now(),threadId:n,runId:s,result:i.output})}catch(e){writeSse(r,{type:"RUN_ERROR",timestamp:Date.now(),threadId:n,runId:s,message:errorMessage(e),code:"runtime_error"})}finally{d(),r.end()}}(e,await readJson(t),r),!0)}(t,e,s))return;if(a.has("acp")&&await async function handleAcp(e,t,r){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(r,200,acpInitializeResult()),!0;if("POST"===t.method&&"/acp"===t.url){const a=await readJson(t),s=await handleAcpJsonRpcMessage(e,a);return s?sendJson(r,200,s):r.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 r=readString(readRecord(t.params)?.sessionId);return void(r&&e.listRequests({sessionId:r,state:"running"}).forEach(t=>e.cancel(t.requestId,"acp_cancel")))}if("initialize"===t.method)return jsonRpcResult(t.id,acpInitializeResult(0,readRecord(t.params)));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 r=readRecord(t.params)??{},a=readRuntimeRequestExtension(r),s=await e.request(a??{input:readPromptText(r),sessionId:readString(r.sessionId),agentId:readString(r.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,r,a){const s=toA2aRuntimeRequest(t);var n;writeSseHeaders(r),writeSse(r,{jsonrpc:"2.0",id:a,result:{task:(n=s,{id:n.requestId??crypto.randomUUID(),contextId:n.sessionId,status:{state:"submitted",timestamp:(new Date).toISOString()},metadata:{agentId:n.agentId}})}});const o=e.subscribe(e=>{e.requestId===s.requestId&&writeSse(r,{jsonrpc:"2.0",id:a,result:{event:toA2aEvent(e)}})});try{const t=await e.request(s);writeSse(r,{jsonrpc:"2.0",id:a,result:{task:toA2aTask(t),final:!0}})}finally{o(),r.end()}}function toA2aRuntimeRequest(e){const t=readRuntimeRequestExtension(e);if(t)return{...t,metadata:{...t.metadata,protocol:"a2a"}};const r=readRecord(e.message)??e;return{input:readPromptText(r),requestId:readString(e.requestId)??readString(r.messageId),sessionId:readString(e.taskId)??readString(r.taskId)??readString(r.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,r="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:r?{role:"agent",messageId:`${a}-response`,parts:[{kind:"text",text:r}]}:void 0,timestamp:(new Date).toISOString()},artifacts:r?[{artifactId:`${a}-output`,name:"result",parts:[{kind:"text",text:r}]}]: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:(r=e.type,"runtime.request.started"===r?"working":"runtime.request.completed"===r?"completed":"runtime.request.failed"===r?"failed":"runtime.request.cancelled"===r?"canceled":"working"),message:t,timestamp:(new Date).toISOString()},metadata:{stableHarnessEvent:e}};var r}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),r=readString(t?.toolId);return r?{toolId:r,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 acpInitializeResult(e,t){return"number"==typeof t?.protocolVersion&&t.protocolVersion,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},mcpCapabilities:{http:!1,sse:!1},session:{update:!0,cancel:!0,list:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"},authMethods:[]}}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 r=(e??"").match(t);return r?.[1]?decodeURIComponent(r[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,r){return{jsonrpc:"2.0",id:e,error:{code:t,message:r}}}function sendJson(e,t,r){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}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 r of e)t.push(Buffer.isBuffer(r)?r:Buffer.from(r));return t.length>0?JSON.parse(Buffer.concat(t).toString("utf8")):{}}function errorMessage(e){return e instanceof Error?e.message:String(e)}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/protocols",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109"
13
+ "@stable-harness/core": "0.0.111"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
3
+ "version": "0.0.111",
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.109"
14
+ "@stable-harness/core": "0.0.111"
15
15
  }
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stable-harness",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
4
4
  "type": "module",
5
5
  "description": "Stable application runtime and operator control plane for agent workspaces.",
6
6
  "license": "Apache-2.0",
@@ -82,20 +82,21 @@
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.109",
86
- "@stable-harness/adapter-langgraph": "0.0.109",
87
- "@stable-harness/core": "0.0.109",
88
- "@stable-harness/governance": "0.0.109",
89
- "@stable-harness/memory": "0.0.109",
90
- "@stable-harness/protocols": "0.0.109",
91
- "@stable-harness/tool-gateway": "0.0.109",
92
- "@stable-harness/workspace-yaml": "0.0.109",
85
+ "@stable-harness/adapter-deepagents": "0.0.111",
86
+ "@stable-harness/adapter-langgraph": "0.0.111",
87
+ "@stable-harness/core": "0.0.111",
88
+ "@stable-harness/governance": "0.0.111",
89
+ "@stable-harness/memory": "0.0.111",
90
+ "@stable-harness/protocols": "0.0.111",
91
+ "@stable-harness/tool-gateway": "0.0.111",
92
+ "@stable-harness/workspace-yaml": "0.0.111",
93
93
  "deepagents": "^1.10.1",
94
94
  "langchain": "^1.4.0",
95
95
  "yaml": "^2.8.2",
96
96
  "zod": "^4.1.13"
97
97
  },
98
98
  "devDependencies": {
99
+ "@ag-ui/core": "^0.0.53",
99
100
  "@types/node": "^25.6.2",
100
101
  "terser": "^5.47.1",
101
102
  "typescript": "^5.9.3"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
18
+ "@stable-harness/core": "0.0.111",
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.109",
3
+ "version": "0.0.111",
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.109"
14
+ "@stable-harness/core": "0.0.111"
15
15
  }
16
16
  }
@@ -1 +1 @@
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
+ 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:s=>a(n,t,s,e.apiKey),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()}
@@ -9,4 +9,4 @@ export declare function runRequestThroughDaemon(input: {
9
9
  export declare function daemonBaseUrl(policy: WorkspaceRuntimePolicy, overrideUrl?: string): string;
10
10
  export declare function agentProtocolBaseUrl(policy: WorkspaceRuntimePolicy, overrideUrl?: string): string;
11
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>;
12
+ export declare function sendAgentProtocolRuntimeRequest(baseUrl: string, protocol: Exclude<CliArgs["clientProtocol"], "stable-runtime">, request: RuntimeRequest, bearerToken?: string): 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("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"TEXT_MESSAGE_CONTENT"!==e?.type&&"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
+ 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,r){try{const o=await fetch(`${t}/health`,{headers:authHeaders(r),signal:AbortSignal.timeout(300)});if(!o.ok)return!1;const n=await o.json();return Array.isArray(n.protocols)&&n.protocols.includes("agui"===e?"agui":e)}catch{return!1}}(o,r,e.args.apiKey))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()),e.args.apiKey);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,n){const s="a2a"===r?await async function sendA2aRequest(t,e,r){const o=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}]}},r);return o.task?.status?.message?.parts?.map(t=>t.text).filter(Boolean).join("\n")??""}(e,o,n):"agui"===r?await async function sendAguiRequest(t,e,r){const o=await fetch(`${t}/ag-ui/runs`,{method:"POST",headers:authHeaders(r),body:JSON.stringify({input:e.input,runId:e.requestId,threadId:e.sessionId,agentId:e.agentId,runtimeRequest:e})});if(!o.ok||!o.body)throw new Error(`AG-UI request failed: HTTP ${o.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))}}}(o.body.getReader())).map(readAguiText).filter(Boolean).join("")}(e,o,n):await async function sendAcpRequest(e,r,o){const n=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}},o);if(n.error)throw new Error(n.error.message??"ACP request failed");return n.result?._meta?.output??""}(e,o,n);return{requestId:o.requestId??t(),sessionId:o.sessionId??"",agentId:o.agentId??"",state:"completed",output:s}}async function postJson(t,e,r){const o=await fetch(t,{method:"POST",headers:authHeaders(r),body:JSON.stringify(e)});if(!o.ok)throw new Error(`protocol request failed: HTTP ${o.status}`);return await o.json()}function authHeaders(t){return t?{authorization:`Bearer ${t}`}:{}}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"TEXT_MESSAGE_CONTENT"!==e?.type&&"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 +1 @@
1
- import{execFile as t}from"node:child_process";import{promisify as r}from"node:util";import{createAgentProtocolHttpServer as o,createHttpServer as e,createOpenAiCompatibleHttpServer as n}from"@stable-harness/protocols";import{startOfficialLangGraphServer as s}from"./langgraph-official.js";const i="127.0.0.1",a=r(t);export async function serveProtocol(t,r){const o=createConfiguredServers(t,r),e=[];let n=0;for(const r of o)if("http"===r.kind){if(!await listen(r)){process.stdout.write(`stable-harness ${r.protocol} API already running on ${serverUrl(r)}\n`);continue}e.push(()=>closeHttpServer(r.server)),n+=1;const t=r.server.address(),o="object"==typeof t&&t?t.port:r.port;process.stdout.write(`stable-harness ${r.protocol} API listening on ${serverUrl({...r,port:o})}\n`)}else{const o=await startLangGraphServer(t,r);if(!o){process.stdout.write(`stable-harness ${r.protocol} API already running on http://${r.config.host}:${r.config.port}\n`);continue}e.push(o.cleanup),n+=1,process.stdout.write(`stable-harness ${r.protocol} API listening on ${o.url}\n`)}0!==n&&await async function waitForShutdown(t){const r=setInterval(()=>{},864e5);await new Promise(o=>{const shutdown=()=>{clearInterval(r),Promise.allSettled(t.map(t=>t())).finally(()=>process.exit(0))};process.once("SIGINT",shutdown),process.once("SIGTERM",shutdown)})}(e)}export async function stopProtocol(t,r){const o=createConfiguredServers({getRuntimePolicy:()=>t.runtime},r).map(t=>"http"===t.kind?{protocol:t.protocol,host:t.host,port:t.port}:{protocol:t.protocol,host:t.config.host,port:t.config.port}),e=await Promise.all(o.map(async t=>({target:t,pids:await stableHarnessListenerPids(t.port)}))),n=[...new Set(e.flatMap(t=>t.pids))];for(const t of n)process.kill(t,"SIGTERM");for(const{target:t,pids:r}of e)0!==r.length?process.stdout.write(`stable-harness ${t.protocol} API stopped on ${t.host}:${t.port} pid=${r.join(",")}\n`):process.stdout.write(`stable-harness ${t.protocol} API not running on ${t.host}:${t.port}\n`)}function createConfiguredServers(t,r){const o=readRecord(t.getRuntimePolicy().protocols)??{},e=protocolConfig(o,"stableRuntime","stable-runtime","http")??{},n=protocolConfig(o,"openaiCompatible","openai-compatible","openai")??{},s=function agentProtocolConfig(t){const r=protocolConfig(t,"agentProtocols","agent-protocols");if(r)return enabled(r)?r:void 0;const o=["acp","a2a","agui"].filter(r=>enabled(protocolConfig(t,r)??{enabled:!1}));return o.length>0?{protocols:o}:void 0}(o),i=protocolConfig(o,"langgraph")??{};return[...enabled(e)?[stableRuntimeServer(t,e,r)]:[],...enabled(n)?[openAiServer(t,n,r)]:[],...s?[agentProtocolServer(t,s,r)]:[],...enabled(i)?[langGraphServer(i)]:[]]}function stableRuntimeServer(t,r,o){return{kind:"http",protocol:"stable-runtime",server:e(t),host:o.host??configString(r.host)??i,port:configNumber(r.port)??configNumber(process.env.STABLE_HARNESS_RUNTIME_PORT)??8641}}function openAiServer(t,r,o){const e=configString(r.host)??i,s=o.port??configNumber(r.port)??8642,a=o.host??e,c=configString(r.bearerToken)??configString(r.apiKey)??o.apiKey;return{kind:"http",protocol:"openai-compatible",server:n(t,{bearerToken:c}),host:a,port:s,...c?{bearerToken:c}:{}}}function agentProtocolServer(t,r,e){const n=configString(r.host)??i,s=configNumber(r.port)??8650,a=e.host??n;return{kind:"http",protocol:"agent-protocols",server:o(t,{baseUrl:`http://${a}:${s}`,enabledProtocols:configProtocolIds(r.protocols)}),host:a,port:s}}function langGraphServer(t){const r=configString(t.host)??i,o=configNumber(t.port)??2024,e=configStringArray(t.exposeAgents);return{kind:"langgraph",protocol:"langgraph-compatible",config:{host:r,port:o,nWorkers:configNumber(t.nWorkers)??10,...e?{exposeAgents:e}:{},...void 0!==t.env?{env:t.env}:{},...void 0!==t.envFile?{envFile:t.envFile}:{}}}}function protocolConfig(t,...r){for(const o of r){const r=readRecord(t[o]);if(r)return r}}function enabled(t){return!1!==t.enabled}function configString(t){if("string"!=typeof t||!t.trim())return;const r=t.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return r?process.env[r[1]]??r[2]:t}function configNumber(t){return"number"==typeof t&&Number.isFinite(t)?t:"string"==typeof t&&t.trim()?Number(t):void 0}function configStringArray(t){if(Array.isArray(t)&&t.every(t=>"string"==typeof t))return t.filter(t=>t.trim()).map(t=>t.trim())}function configProtocolIds(t){const r=configStringArray(t)?.filter(t=>"acp"===t||"a2a"===t||"agui"===t);return r&&r.length>0?r:void 0}function readRecord(t){return"object"!=typeof t||null===t||Array.isArray(t)?void 0:t}async function listen(t){try{return await new Promise((r,o)=>{t.server.once("error",o),t.server.listen(t.port,t.host,()=>{t.server.off("error",o),r()})}),!0}catch(r){if(isAddressInUse(r)&&await async function isHttpServerAlreadyRunning(t){return"stable-runtime"===t.protocol?await async function isStableRuntimeServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/inspect`);return Array.isArray(r?.agents)&&Array.isArray(r?.runs)}(t):"openai-compatible"===t.protocol?await async function isOpenAiServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/v1/capabilities`,{...t.bearerToken?{authorization:`Bearer ${t.bearerToken}`}:{}});return"stable_harness.capabilities"===r?.object}(t):await async function isAgentProtocolServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/health`);return!0===r?.ok&&Array.isArray(r.protocols)}(t)}(t))return!1;throw portConflictError(r,t.protocol,t.host,t.port)}}async function startLangGraphServer(t,r){if(!await isLangGraphServerAlreadyRunning(r))try{return await s(t,r.config)}catch(t){if(isAddressInUse(t)&&await isLangGraphServerAlreadyRunning(r))return;throw portConflictError(t,r.protocol,r.config.host,r.config.port)}}function serverUrl(t){const r=`http://${t.host}:${t.port}`;return"openai-compatible"===t.protocol?`${r}/v1`:"agent-protocols"===t.protocol?`${r}/acp / ${r}/a2a / ${r}/ag-ui`:r}async function isLangGraphServerAlreadyRunning(t){const r=await fetchJson(`http://${t.config.host}:${t.config.port}/ok`);return!0===r?.ok}async function fetchJson(t,r={}){try{const o=await fetch(t,{headers:r});if(!o.ok)return;return await o.json()}catch{return}}function isAddressInUse(t){return"EADDRINUSE"===function readErrorCode(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}(t)||String(t).includes("EADDRINUSE")}function portConflictError(t,r,o,e){return isAddressInUse(t)?new Error([`stable-harness ${r} port is already in use: ${o}:${e}.`,`Use --port <port>, update config/runtime/workspace.yaml, or stop the process currently listening on ${o}:${e}.`].join("\n")):t}async function stableHarnessListenerPids(t){const r=await async function listenerPids(t){try{const{stdout:r}=await a("lsof",[`-tiTCP:${t}`,"-sTCP:LISTEN"]);return r.split(/\s+/u).map(t=>Number(t)).filter(t=>Number.isInteger(t)&&t>0)}catch{return[]}}(t);return(await Promise.all(r.map(async t=>{const r=await async function processCommand(t){try{const{stdout:r}=await a("ps",["-p",String(t),"-o","command="]);return r.trim()}catch{return""}}(t);return isStableHarnessStartCommand(r)?t:void 0}))).filter(t=>"number"==typeof t)}export function isStableHarnessStartCommand(t){if(function hasUnsafeCommandCharacters(t){return/[\u0000-\u001F\u007F;|`&<>]/u.test(t)}(t))return!1;const r=function splitCommandLine(t){const r=[];let o,e="";for(const n of t)'"'!==n&&"'"!==n||void 0!==o?n!==o?/\s/u.test(n)&&void 0===o?e&&(r.push(e),e=""):e+=n:o=void 0:o=n;return e&&r.push(e),r}(t),o=function stableHarnessCommandIndex(t){return isStableHarnessExecutableToken(t[0]??"")?0:function isNodeExecutableToken(t){const r=t.split(/[\\/]/u).at(-1);return"node"===r||"nodejs"===r}(t[0]??"")&&(isStableHarnessExecutableToken(t[1]??"")||function isStableHarnessScriptToken(t){if(hasTraversalSegment(t))return!1;const r=t.replaceAll("\\","/");return r.includes("/stable-harness/")&&r.endsWith("/packages/cli/dist/src/cli.js")}(t[1]??""))?1:-1}(r);return o>=0&&r.slice(o+1).includes("start")}function isStableHarnessExecutableToken(t){if(hasTraversalSegment(t))return!1;const r=t.split(/[\\/]/u).at(-1);return"stable-harness"===r||"botbotgo"===r}function hasTraversalSegment(t){return t.split(/[\\/]/u).some(t=>"."===t||".."===t)}async function closeHttpServer(t){await new Promise((r,o)=>{t.close(t=>{t?o(t):r()})})}
1
+ import{execFile as t}from"node:child_process";import{promisify as r}from"node:util";import{createAgentProtocolHttpServer as e,createHttpServer as o,createOpenAiCompatibleHttpServer as n}from"@stable-harness/protocols";import{startOfficialLangGraphServer as s}from"./langgraph-official.js";const i="127.0.0.1",a=r(t);export async function serveProtocol(t,r){const e=createConfiguredServers(t,r),o=[];let n=0;for(const r of e)if("http"===r.kind){if(!await listen(r)){process.stdout.write(`stable-harness ${r.protocol} API already running on ${serverUrl(r)}\n`);continue}o.push(()=>closeHttpServer(r.server)),n+=1;const t=r.server.address(),e="object"==typeof t&&t?t.port:r.port;process.stdout.write(`stable-harness ${r.protocol} API listening on ${serverUrl({...r,port:e})}\n`)}else{const e=await startLangGraphServer(t,r);if(!e){process.stdout.write(`stable-harness ${r.protocol} API already running on http://${r.config.host}:${r.config.port}\n`);continue}o.push(e.cleanup),n+=1,process.stdout.write(`stable-harness ${r.protocol} API listening on ${e.url}\n`)}0!==n&&await async function waitForShutdown(t){const r=setInterval(()=>{},864e5);await new Promise(e=>{const shutdown=()=>{clearInterval(r),Promise.allSettled(t.map(t=>t())).finally(()=>process.exit(0))};process.once("SIGINT",shutdown),process.once("SIGTERM",shutdown)})}(o)}export async function stopProtocol(t,r){const e=createConfiguredServers({getRuntimePolicy:()=>t.runtime},r).map(t=>"http"===t.kind?{protocol:t.protocol,host:t.host,port:t.port}:{protocol:t.protocol,host:t.config.host,port:t.config.port}),o=await Promise.all(e.map(async t=>({target:t,pids:await stableHarnessListenerPids(t.port)}))),n=[...new Set(o.flatMap(t=>t.pids))];for(const t of n)process.kill(t,"SIGTERM");for(const{target:t,pids:r}of o)0!==r.length?process.stdout.write(`stable-harness ${t.protocol} API stopped on ${t.host}:${t.port} pid=${r.join(",")}\n`):process.stdout.write(`stable-harness ${t.protocol} API not running on ${t.host}:${t.port}\n`)}function createConfiguredServers(t,r){const e=readRecord(t.getRuntimePolicy().protocols)??{},o=protocolConfig(e,"stableRuntime","stable-runtime","http")??{},n=protocolConfig(e,"openaiCompatible","openai-compatible","openai")??{},s=function agentProtocolConfig(t){const r=protocolConfig(t,"agentProtocols","agent-protocols");if(r)return enabled(r)?r:void 0;const e=["acp","a2a","agui"].filter(r=>enabled(protocolConfig(t,r)??{enabled:!1}));return e.length>0?{protocols:e}:void 0}(e),i=protocolConfig(e,"langgraph")??{};return[...enabled(o)?[stableRuntimeServer(t,o,r)]:[],...enabled(n)?[openAiServer(t,n,r)]:[],...s?[agentProtocolServer(t,s,r)]:[],...enabled(i)?[langGraphServer(i)]:[]]}function stableRuntimeServer(t,r,e){return{kind:"http",protocol:"stable-runtime",server:o(t),host:e.host??configString(r.host)??i,port:configNumber(r.port)??configNumber(process.env.STABLE_HARNESS_RUNTIME_PORT)??8641}}function openAiServer(t,r,e){const o=configString(r.host)??i,s=e.port??configNumber(r.port)??8642,a=e.host??o,c=configString(r.bearerToken)??configString(r.apiKey)??e.apiKey;return{kind:"http",protocol:"openai-compatible",server:n(t,{bearerToken:c}),host:a,port:s,...c?{bearerToken:c}:{}}}function agentProtocolServer(t,r,o){const n=configString(r.host)??i,s=configNumber(r.port)??8650,a=o.host??n,c=configString(r.bearerToken)??configString(r.apiKey)??o.apiKey;return{kind:"http",protocol:"agent-protocols",server:e(t,{baseUrl:`http://${a}:${s}`,enabledProtocols:configProtocolIds(r.protocols),bearerToken:c}),host:a,port:s,...c?{bearerToken:c}:{}}}function langGraphServer(t){const r=configString(t.host)??i,e=configNumber(t.port)??2024,o=configStringArray(t.exposeAgents);return{kind:"langgraph",protocol:"langgraph-compatible",config:{host:r,port:e,nWorkers:configNumber(t.nWorkers)??10,...o?{exposeAgents:o}:{},...void 0!==t.env?{env:t.env}:{},...void 0!==t.envFile?{envFile:t.envFile}:{}}}}function protocolConfig(t,...r){for(const e of r){const r=readRecord(t[e]);if(r)return r}}function enabled(t){return!1!==t.enabled}function configString(t){if("string"!=typeof t||!t.trim())return;const r=t.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return r?process.env[r[1]]??r[2]:t}function configNumber(t){return"number"==typeof t&&Number.isFinite(t)?t:"string"==typeof t&&t.trim()?Number(t):void 0}function configStringArray(t){if(Array.isArray(t)&&t.every(t=>"string"==typeof t))return t.filter(t=>t.trim()).map(t=>t.trim())}function configProtocolIds(t){const r=configStringArray(t)?.filter(t=>"acp"===t||"a2a"===t||"agui"===t);return r&&r.length>0?r:void 0}function readRecord(t){return"object"!=typeof t||null===t||Array.isArray(t)?void 0:t}async function listen(t){try{return await new Promise((r,e)=>{t.server.once("error",e),t.server.listen(t.port,t.host,()=>{t.server.off("error",e),r()})}),!0}catch(r){if(isAddressInUse(r)&&await async function isHttpServerAlreadyRunning(t){return"stable-runtime"===t.protocol?await async function isStableRuntimeServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/inspect`);return Array.isArray(r?.agents)&&Array.isArray(r?.runs)}(t):"openai-compatible"===t.protocol?await async function isOpenAiServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/v1/capabilities`,{...t.bearerToken?{authorization:`Bearer ${t.bearerToken}`}:{}});return"stable_harness.capabilities"===r?.object}(t):await async function isAgentProtocolServerAlreadyRunning(t){const r=await fetchJson(`http://${t.host}:${t.port}/health`);return!0===r?.ok&&Array.isArray(r.protocols)}(t)}(t))return!1;throw portConflictError(r,t.protocol,t.host,t.port)}}async function startLangGraphServer(t,r){if(!await isLangGraphServerAlreadyRunning(r))try{return await s(t,r.config)}catch(t){if(isAddressInUse(t)&&await isLangGraphServerAlreadyRunning(r))return;throw portConflictError(t,r.protocol,r.config.host,r.config.port)}}function serverUrl(t){const r=`http://${t.host}:${t.port}`;return"openai-compatible"===t.protocol?`${r}/v1`:"agent-protocols"===t.protocol?`${r}/acp / ${r}/a2a / ${r}/ag-ui`:r}async function isLangGraphServerAlreadyRunning(t){const r=await fetchJson(`http://${t.config.host}:${t.config.port}/ok`);return!0===r?.ok}async function fetchJson(t,r={}){try{const e=await fetch(t,{headers:r});if(!e.ok)return;return await e.json()}catch{return}}function isAddressInUse(t){return"EADDRINUSE"===function readErrorCode(t){return"object"==typeof t&&null!==t&&"code"in t?t.code:void 0}(t)||String(t).includes("EADDRINUSE")}function portConflictError(t,r,e,o){return isAddressInUse(t)?new Error([`stable-harness ${r} port is already in use: ${e}:${o}.`,`Use --port <port>, update config/runtime/workspace.yaml, or stop the process currently listening on ${e}:${o}.`].join("\n")):t}async function stableHarnessListenerPids(t){const r=await async function listenerPids(t){try{const{stdout:r}=await a("lsof",[`-tiTCP:${t}`,"-sTCP:LISTEN"]);return r.split(/\s+/u).map(t=>Number(t)).filter(t=>Number.isInteger(t)&&t>0)}catch{return[]}}(t);return(await Promise.all(r.map(async t=>{const r=await async function processCommand(t){try{const{stdout:r}=await a("ps",["-p",String(t),"-o","command="]);return r.trim()}catch{return""}}(t);return isStableHarnessStartCommand(r)?t:void 0}))).filter(t=>"number"==typeof t)}export function isStableHarnessStartCommand(t){if(function hasUnsafeCommandCharacters(t){return/[\u0000-\u001F\u007F;|`&<>]/u.test(t)}(t))return!1;const r=function splitCommandLine(t){const r=[];let e,o="";for(const n of t)'"'!==n&&"'"!==n||void 0!==e?n!==e?/\s/u.test(n)&&void 0===e?o&&(r.push(o),o=""):o+=n:e=void 0:e=n;return o&&r.push(o),r}(t),e=function stableHarnessCommandIndex(t){return isStableHarnessExecutableToken(t[0]??"")?0:function isNodeExecutableToken(t){const r=t.split(/[\\/]/u).at(-1);return"node"===r||"nodejs"===r}(t[0]??"")&&(isStableHarnessExecutableToken(t[1]??"")||function isStableHarnessScriptToken(t){if(hasTraversalSegment(t))return!1;const r=t.replaceAll("\\","/");return r.includes("/stable-harness/")&&r.endsWith("/packages/cli/dist/src/cli.js")}(t[1]??""))?1:-1}(r);return e>=0&&r.slice(e+1).includes("start")}function isStableHarnessExecutableToken(t){if(hasTraversalSegment(t))return!1;const r=t.split(/[\\/]/u).at(-1);return"stable-harness"===r||"botbotgo"===r}function hasTraversalSegment(t){return t.split(/[\\/]/u).some(t=>"."===t||".."===t)}async function closeHttpServer(t){await new Promise((r,e)=>{t.close(t=>{t?e(t):r()})})}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/cli",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
18
- "@stable-harness/adapter-langgraph": "0.0.109",
19
- "@stable-harness/core": "0.0.109",
20
- "@stable-harness/memory": "0.0.109",
21
- "@stable-harness/protocols": "0.0.109",
22
- "@stable-harness/tool-gateway": "0.0.109",
23
- "@stable-harness/workspace-yaml": "0.0.109"
17
+ "@stable-harness/adapter-deepagents": "0.0.111",
18
+ "@stable-harness/adapter-langgraph": "0.0.111",
19
+ "@stable-harness/core": "0.0.111",
20
+ "@stable-harness/memory": "0.0.111",
21
+ "@stable-harness/protocols": "0.0.111",
22
+ "@stable-harness/tool-gateway": "0.0.111",
23
+ "@stable-harness/workspace-yaml": "0.0.111"
24
24
  }
25
25
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
15
- "@stable-harness/memory": "0.0.109"
14
+ "@stable-harness/governance": "0.0.111",
15
+ "@stable-harness/memory": "0.0.111"
16
16
  }
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/evaluation",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109"
13
+ "@stable-harness/core": "0.0.111"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
3
+ "version": "0.0.111",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*.js",
@@ -3,6 +3,7 @@ import type { StableHarnessRuntime } from "@stable-harness/core";
3
3
  export type AgentProtocolServerOptions = {
4
4
  baseUrl?: string;
5
5
  enabledProtocols?: Array<"acp" | "a2a" | "agui">;
6
+ bearerToken?: string;
6
7
  };
7
8
  type JsonRpcMessage = {
8
9
  jsonrpc?: string;
@@ -1 +1 @@
1
- import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,a={}){const s=new Set(a.enabledProtocols??["acp","a2a","agui"]);return e(async(e,r)=>{try{if("GET"===e.method&&"/health"===e.url)return void sendJson(r,200,{ok:!0,protocols:[...s]});if("GET"===e.method&&"/capabilities"===e.url)return void sendJson(r,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}}}}(s));if(s.has("a2a")&&await async function handleA2a(e,t,a,s){if(!function validateA2aVersion(e,t){const a=e.headers["a2a-version"],s=Array.isArray(a)?a[0]:a;return!s||"0.3"===s||"1.0"===s||(sendJson(t,400,jsonRpcError(null,-32006,"version_not_supported")),!1)}(t,a))return!0;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,pushNotifications:!1},defaultInputModes:["text/plain"],defaultOutputModes:["text/plain"],skills:a.agents.map(e=>({id:e,name:e,description:e}))}}(e,s)),!0;const r=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&r){const t=e.inspectRequest(r);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\/([^/]+):subscribe$/u);if("GET"===t.method&&n)return function streamA2aTask(e,t,a){const s=e.inspectRequest(t);if(!s)return void sendJson(a,404,{error:"task_not_found"});if(writeSseHeaders(a),writeSse(a,{jsonrpc:"2.0",result:{task:toA2aTask(s)}}),function isTerminalState(e){return"completed"===e||"failed"===e||"cancelled"===e||"canceled"===e||"rejected"===e}(s.summary.state))return void a.end();const r=e.subscribe(e=>{e.requestId===t&&writeSse(a,{jsonrpc:"2.0",result:{event:toA2aEvent(e)}})});a.on("close",r)}(e,n,a),!0;const o=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);return"POST"===t.method&&o?(e.cancel(o,"a2a_cancel"),sendJson(a,200,{task:e.inspectRequest(o)}),!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 s=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(a,200,jsonRpcResult(t.id,{task:toA2aTask(s)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const s=readString(readRecord(t.params)?.id),r=s?e.inspectRequest(s):void 0;return void sendJson(a,r?200:404,r?jsonRpcResult(t.id,toA2aTask(r)):jsonRpcError(t.id,-32004,"task_not_found"))}if("ListTasks"!==t.method){if("CancelTask"===t.method){const s=readString(readRecord(t.params)?.id);s&&e.cancel(s,"a2a_cancel");const r=s?e.inspectRequest(s):void 0;return void sendJson(a,r?200:404,r?jsonRpcResult(t.id,toA2aTask(r)):jsonRpcError(t.id,-32004,"task_not_found"))}sendJson(a,404,jsonRpcError(t.id,-32601,"method_not_found"))}else 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,r,a))return;if(s.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:["RUN_STARTED","TEXT_MESSAGE_START","TEXT_MESSAGE_CONTENT","TEXT_MESSAGE_END","CUSTOM","RUN_FINISHED","RUN_ERROR"]}),!0):"POST"===t.method&&"/ag-ui/runs"===t.url&&(await async function streamAgui(e,t,a){const s=readRuntimeRequestExtension(t),r=s?.requestId??readString(t.runId)??crypto.randomUUID(),n=s?.sessionId??readString(t.threadId)??readString(t.sessionId)??`thread-${r}`,o=readPromptText(t);writeSseHeaders(a),writeSse(a,{type:"RUN_STARTED",timestamp:Date.now(),threadId:n,runId:r,input:{messages:t.messages,input:o}});const i=e.subscribe(e=>{e.requestId===r&&writeSse(a,function toAguiEvent(e){return"runtime.progress.narration"===e.type?{type:"CUSTOM",timestamp:Date.now(),name:"stable-harness.progress",value:e}:"runtime.tool.direct.started"===e.type?{type:"TOOL_CALL_START",timestamp:Date.now(),toolCallId:`${e.requestId}:${e.toolId}`,toolCallName:e.toolId}:"runtime.tool.direct.completed"===e.type?{type:"TOOL_CALL_RESULT",timestamp:Date.now(),messageId:`${e.requestId}-message`,toolCallId:`${e.requestId}:${e.toolId}`,content:JSON.stringify(e.output),role:"tool"}:{type:"CUSTOM",timestamp:Date.now(),name:`stable-harness.${e.type}`,value:e}}(e))});try{const i=await e.request(s??{input:o,requestId:r,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),d=`${r}-message`;i.output&&function writeAguiText(e,t,a){writeSse(e,{type:"TEXT_MESSAGE_START",timestamp:Date.now(),messageId:t,role:"assistant"}),writeSse(e,{type:"TEXT_MESSAGE_CONTENT",timestamp:Date.now(),messageId:t,delta:a}),writeSse(e,{type:"TEXT_MESSAGE_END",timestamp:Date.now(),messageId:t})}(a,d,i.output),writeSse(a,{type:"RUN_FINISHED",timestamp:Date.now(),threadId:n,runId:r,result:i.output})}catch(e){writeSse(a,{type:"RUN_ERROR",timestamp:Date.now(),threadId:n,runId:r,message:errorMessage(e),code:"runtime_error"})}finally{i(),a.end()}}(e,await readJson(t),a),!0)}(t,e,r))return;if(s.has("acp")&&await async function handleAcp(e,t,a){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(a,200,acpInitializeResult()),!0;if("POST"===t.method&&"/acp"===t.url){const s=await readJson(t),r=await handleAcpJsonRpcMessage(e,s);return r?sendJson(a,200,r):a.writeHead(204).end(),!0}return!1}(t,e,r))return;sendJson(r,404,{error:"not_found"})}catch(e){sendJson(r,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,acpInitializeResult(0,readRecord(t.params)));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)??{},s=readRuntimeRequestExtension(a),r=await e.request(s??{input:readPromptText(a),sessionId:readString(a.sessionId),agentId:readString(a.agentId),metadata:{protocol:"acp",transport:"http-jsonrpc"}});return jsonRpcResult(t.id,{stopReason:"completed"===r.state?"end_turn":"refusal",_meta:{requestId:r.requestId,output:r.output}})}return jsonRpcError(t.id,-32601,"method_not_found")}async function streamA2a(e,t,a,s){const r=toA2aRuntimeRequest(t);var n;writeSseHeaders(a),writeSse(a,{jsonrpc:"2.0",id:s,result:{task:(n=r,{id:n.requestId??crypto.randomUUID(),contextId:n.sessionId,status:{state:"submitted",timestamp:(new Date).toISOString()},metadata:{agentId:n.agentId}})}});const o=e.subscribe(e=>{e.requestId===r.requestId&&writeSse(a,{jsonrpc:"2.0",id:s,result:{event:toA2aEvent(e)}})});try{const t=await e.request(r);writeSse(a,{jsonrpc:"2.0",id:s,result:{task:toA2aTask(t),final:!0}})}finally{o(),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??"",s="requestId"in e?e.requestId:e.summary.requestId;return{id:s,contextId:"sessionId"in e?e.sessionId:e.summary.sessionId,status:{state:"completed"===t?"completed":t,message:a?{role:"agent",messageId:`${s}-response`,parts:[{kind:"text",text:a}]}:void 0,timestamp:(new Date).toISOString()},artifacts:a?[{artifactId:`${s}-output`,name:"result",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,timestamp:(new Date).toISOString()},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 acpInitializeResult(e,t){return"number"==typeof t?.protocolVersion&&t.protocolVersion,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},mcpCapabilities:{http:!1,sse:!1},session:{update:!0,cancel:!0,list:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"},authMethods:[]}}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
+ import{createServer as e}from"node:http";export function createAgentProtocolHttpServer(t,r={}){const a=new Set(r.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:[...a]});if(!function isAuthorized(e,t){return!t.bearerToken||e.headers.authorization===`Bearer ${t.bearerToken}`}(e,r))return void sendJson(s,401,{error:"unauthorized"});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}}}}(a));if(a.has("a2a")&&await async function handleA2a(e,t,r,a){if(!function validateA2aVersion(e,t){const r=e.headers["a2a-version"],a=Array.isArray(r)?r[0]:r;return!a||"0.3"===a||"1.0"===a||(sendJson(t,400,jsonRpcError(null,-32006,"version_not_supported")),!1)}(t,r))return!0;if("GET"===t.method&&("/.well-known/agent-card.json"===t.url||"/a2a/agent-card.json"===t.url))return sendJson(r,200,function createAgentCard(e,t){const r=e.inspect();return{protocolVersion:"1.0",name:r.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,pushNotifications:!1},defaultInputModes:["text/plain"],defaultOutputModes:["text/plain"],skills:r.agents.map(e=>({id:e,name:e,description:e}))}}(e,a)),!0;const s=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):subscribe$/u);if("GET"===t.method&&s)return function streamA2aTask(e,t,r){const a=e.inspectRequest(t);if(!a)return void sendJson(r,404,{error:"task_not_found"});if(writeSseHeaders(r),writeSse(r,{jsonrpc:"2.0",result:{task:toA2aTask(a)}}),function isTerminalState(e){return"completed"===e||"failed"===e||"cancelled"===e||"canceled"===e||"rejected"===e}(a.summary.state))return void r.end();const s=e.subscribe(e=>{e.requestId===t&&writeSse(r,{jsonrpc:"2.0",result:{event:toA2aEvent(e)}})});r.on("close",s)}(e,s,r),!0;const n=matchPath(t.url,/^\/a2a\/tasks\/([^/]+):cancel$/u);if("POST"===t.method&&n)return e.cancel(n,"a2a_cancel"),sendJson(r,200,{task:e.inspectRequest(n)}),!0;const o=matchPath(t.url,/^\/a2a\/tasks\/([^/]+)$/u);if("GET"===t.method&&o){const t=e.inspectRequest(o);return sendJson(r,t?200:404,t?{task:toA2aTask(t)}:{error:"task_not_found"}),!0}return"GET"===t.method&&"/a2a/tasks"===t.url?(sendJson(r,200,{tasks:e.listRequests().map(toA2aTaskSummary)}),!0):"POST"===t.method&&"/a2a/message:send"===t.url?(sendJson(r,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),r),!0):"POST"===t.method&&"/a2a/rpc"===t.url&&(await async function handleA2aRpc(e,t,r){if("SendMessage"===t.method){const a=await e.request(toA2aRuntimeRequest(readRecord(t.params)??{}));return void sendJson(r,200,jsonRpcResult(t.id,{task:toA2aTask(a)}))}if("SendStreamingMessage"!==t.method){if("GetTask"===t.method){const a=readString(readRecord(t.params)?.id),s=a?e.inspectRequest(a):void 0;return void sendJson(r,s?200:404,s?jsonRpcResult(t.id,toA2aTask(s)):jsonRpcError(t.id,-32004,"task_not_found"))}if("ListTasks"!==t.method){if("CancelTask"===t.method){const a=readString(readRecord(t.params)?.id);a&&e.cancel(a,"a2a_cancel");const s=a?e.inspectRequest(a):void 0;return void sendJson(r,s?200:404,s?jsonRpcResult(t.id,toA2aTask(s)):jsonRpcError(t.id,-32004,"task_not_found"))}sendJson(r,404,jsonRpcError(t.id,-32601,"method_not_found"))}else sendJson(r,200,jsonRpcResult(t.id,{tasks:e.listRequests().map(toA2aTaskSummary)}))}else await streamA2a(e,readRecord(t.params)??{},r,t.id)}(e,await readJson(t),r),!0)}(t,e,s,r))return;if(a.has("agui")&&await async function handleAgui(e,t,r){return"GET"===t.method&&"/ag-ui/capabilities"===t.url?(sendJson(r,200,{protocol:"ag-ui",transports:["http+sse"],events:["RUN_STARTED","TEXT_MESSAGE_START","TEXT_MESSAGE_CONTENT","TEXT_MESSAGE_END","CUSTOM","RUN_FINISHED","RUN_ERROR"]}),!0):"POST"===t.method&&"/ag-ui/runs"===t.url&&(await async function streamAgui(e,t,r){const a=readRuntimeRequestExtension(t),s=a?.requestId??readString(t.runId)??crypto.randomUUID(),n=a?.sessionId??readString(t.threadId)??readString(t.sessionId)??`thread-${s}`,o=readPromptText(t);var i;writeSseHeaders(r),writeSse(r,{type:"RUN_STARTED",timestamp:Date.now(),threadId:n,runId:s,input:(i={body:t,input:o,threadId:n,runId:s},{threadId:i.threadId,runId:i.runId,messages:Array.isArray(i.body.messages)?i.body.messages:[{id:`${i.runId}-user`,role:"user",content:i.input}],tools:Array.isArray(i.body.tools)?i.body.tools:[],context:Array.isArray(i.body.context)?i.body.context:[]})});const d=e.subscribe(e=>{e.requestId===s&&writeSse(r,function toAguiEvent(e){return"runtime.progress.narration"===e.type?{type:"CUSTOM",timestamp:Date.now(),name:"stable-harness.progress",value:e}:"runtime.tool.direct.started"===e.type?{type:"TOOL_CALL_START",timestamp:Date.now(),toolCallId:`${e.requestId}:${e.toolId}`,toolCallName:e.toolId}:"runtime.tool.direct.completed"===e.type?{type:"TOOL_CALL_RESULT",timestamp:Date.now(),messageId:`${e.requestId}-message`,toolCallId:`${e.requestId}:${e.toolId}`,content:JSON.stringify(e.output),role:"tool"}:{type:"CUSTOM",timestamp:Date.now(),name:`stable-harness.${e.type}`,value:e}}(e))});try{const i=await e.request(a??{input:o,requestId:s,sessionId:n,agentId:readString(t.agentId),metadata:{protocol:"ag-ui"}}),d=`${s}-message`;i.output&&function writeAguiText(e,t,r){writeSse(e,{type:"TEXT_MESSAGE_START",timestamp:Date.now(),messageId:t,role:"assistant"}),writeSse(e,{type:"TEXT_MESSAGE_CONTENT",timestamp:Date.now(),messageId:t,delta:r}),writeSse(e,{type:"TEXT_MESSAGE_END",timestamp:Date.now(),messageId:t})}(r,d,i.output),writeSse(r,{type:"RUN_FINISHED",timestamp:Date.now(),threadId:n,runId:s,result:i.output})}catch(e){writeSse(r,{type:"RUN_ERROR",timestamp:Date.now(),threadId:n,runId:s,message:errorMessage(e),code:"runtime_error"})}finally{d(),r.end()}}(e,await readJson(t),r),!0)}(t,e,s))return;if(a.has("acp")&&await async function handleAcp(e,t,r){if("GET"===t.method&&"/acp/capabilities"===t.url)return sendJson(r,200,acpInitializeResult()),!0;if("POST"===t.method&&"/acp"===t.url){const a=await readJson(t),s=await handleAcpJsonRpcMessage(e,a);return s?sendJson(r,200,s):r.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 r=readString(readRecord(t.params)?.sessionId);return void(r&&e.listRequests({sessionId:r,state:"running"}).forEach(t=>e.cancel(t.requestId,"acp_cancel")))}if("initialize"===t.method)return jsonRpcResult(t.id,acpInitializeResult(0,readRecord(t.params)));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 r=readRecord(t.params)??{},a=readRuntimeRequestExtension(r),s=await e.request(a??{input:readPromptText(r),sessionId:readString(r.sessionId),agentId:readString(r.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,r,a){const s=toA2aRuntimeRequest(t);var n;writeSseHeaders(r),writeSse(r,{jsonrpc:"2.0",id:a,result:{task:(n=s,{id:n.requestId??crypto.randomUUID(),contextId:n.sessionId,status:{state:"submitted",timestamp:(new Date).toISOString()},metadata:{agentId:n.agentId}})}});const o=e.subscribe(e=>{e.requestId===s.requestId&&writeSse(r,{jsonrpc:"2.0",id:a,result:{event:toA2aEvent(e)}})});try{const t=await e.request(s);writeSse(r,{jsonrpc:"2.0",id:a,result:{task:toA2aTask(t),final:!0}})}finally{o(),r.end()}}function toA2aRuntimeRequest(e){const t=readRuntimeRequestExtension(e);if(t)return{...t,metadata:{...t.metadata,protocol:"a2a"}};const r=readRecord(e.message)??e;return{input:readPromptText(r),requestId:readString(e.requestId)??readString(r.messageId),sessionId:readString(e.taskId)??readString(r.taskId)??readString(r.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,r="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:r?{role:"agent",messageId:`${a}-response`,parts:[{kind:"text",text:r}]}:void 0,timestamp:(new Date).toISOString()},artifacts:r?[{artifactId:`${a}-output`,name:"result",parts:[{kind:"text",text:r}]}]: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:(r=e.type,"runtime.request.started"===r?"working":"runtime.request.completed"===r?"completed":"runtime.request.failed"===r?"failed":"runtime.request.cancelled"===r?"canceled":"working"),message:t,timestamp:(new Date).toISOString()},metadata:{stableHarnessEvent:e}};var r}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),r=readString(t?.toolId);return r?{toolId:r,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 acpInitializeResult(e,t){return"number"==typeof t?.protocolVersion&&t.protocolVersion,{protocolVersion:1,agentCapabilities:{loadSession:!0,promptCapabilities:{embeddedContext:!0},mcpCapabilities:{http:!1,sse:!1},session:{update:!0,cancel:!0,list:!0},_meta:{transport:"http-jsonrpc"}},agentInfo:{name:"stable-harness",title:"Stable Harness",version:"0.0.0"},authMethods:[]}}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 r=(e??"").match(t);return r?.[1]?decodeURIComponent(r[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,r){return{jsonrpc:"2.0",id:e,error:{code:t,message:r}}}function sendJson(e,t,r){e.writeHead(t,{"content-type":"application/json"}),e.end(JSON.stringify(r))}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 r of e)t.push(Buffer.isBuffer(r)?r:Buffer.from(r));return t.length>0?JSON.parse(Buffer.concat(t).toString("utf8")):{}}function errorMessage(e){return e instanceof Error?e.message:String(e)}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/protocols",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109"
13
+ "@stable-harness/core": "0.0.111"
14
14
  }
15
15
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
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.109",
3
+ "version": "0.0.111",
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.109"
14
+ "@stable-harness/core": "0.0.111"
15
15
  }
16
16
  }