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