stable-harness 0.0.3 → 0.0.5

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 (120) hide show
  1. package/README.md +189 -9
  2. package/dist/cli.js +1 -1
  3. package/dist/compat/agent-harness.js +1 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.js +1 -1
  6. package/dist/runtime/compat/agent-harness-compat-runner.js +1 -1
  7. package/dist/runtime/compat/json.js +1 -1
  8. package/dist/runtime/compat/presentation.js +1 -1
  9. package/dist/runtime/compat/prompts.js +1 -1
  10. package/dist/runtime/model/ollama.js +1 -1
  11. package/dist/runtime/skills/skill-metadata.js +1 -1
  12. package/dist/workspace/compile.js +1 -1
  13. package/package.json +4 -3
  14. package/packages/adapter-deepagents/dist/src/adapter.js +1 -1
  15. package/packages/adapter-deepagents/dist/src/internal/builtin-args.d.ts +4 -0
  16. package/packages/adapter-deepagents/dist/src/internal/builtin-args.js +1 -0
  17. package/packages/adapter-deepagents/dist/src/internal/builtin-tool-policy.d.ts +9 -4
  18. package/packages/adapter-deepagents/dist/src/internal/builtin-tool-policy.js +1 -1
  19. package/packages/adapter-deepagents/dist/src/internal/gateway-tools.d.ts +29 -1
  20. package/packages/adapter-deepagents/dist/src/internal/gateway-tools.js +1 -1
  21. package/packages/adapter-deepagents/dist/src/internal/messages.js +1 -1
  22. package/packages/adapter-deepagents/dist/src/internal/raw-tool-call-parser.d.ts +12 -0
  23. package/packages/adapter-deepagents/dist/src/internal/raw-tool-call-parser.js +1 -0
  24. package/packages/adapter-deepagents/dist/src/internal/skill-file-policy.d.ts +10 -0
  25. package/packages/adapter-deepagents/dist/src/internal/skill-file-policy.js +1 -0
  26. package/packages/adapter-deepagents/dist/src/internal/stream-events.js +1 -1
  27. package/packages/adapter-deepagents/dist/src/internal/tool-repeat-visibility.d.ts +4 -0
  28. package/packages/adapter-deepagents/dist/src/internal/tool-repeat-visibility.js +1 -0
  29. package/packages/adapter-deepagents/dist/src/internal/trace-projection.d.ts +1 -1
  30. package/packages/adapter-deepagents/dist/src/internal/trace-projection.js +1 -1
  31. package/packages/adapter-deepagents/dist/src/memory.js +1 -1
  32. package/packages/adapter-deepagents/dist/src/model-providers.d.ts +4 -0
  33. package/packages/adapter-deepagents/dist/src/model-providers.js +1 -0
  34. package/packages/adapter-deepagents/dist/src/retry-policy.js +1 -1
  35. package/packages/adapter-deepagents/dist/src/types.d.ts +7 -1
  36. package/packages/adapter-deepagents/package.json +1 -0
  37. package/packages/adapter-langgraph/dist/src/graph.js +1 -1
  38. package/packages/adapter-langgraph/dist/src/runtime.js +1 -1
  39. package/packages/adapter-langgraph/dist/src/skill-providers.js +1 -1
  40. package/packages/cli/dist/src/args.d.ts +6 -3
  41. package/packages/cli/dist/src/args.js +1 -1
  42. package/packages/cli/dist/src/cli.js +1 -1
  43. package/packages/cli/dist/src/event-view.d.ts +9 -0
  44. package/packages/cli/dist/src/event-view.js +1 -0
  45. package/packages/cli/dist/src/index.d.ts +3 -0
  46. package/packages/cli/dist/src/index.js +1 -1
  47. package/packages/cli/dist/src/langgraph-env.d.ts +5 -0
  48. package/packages/cli/dist/src/langgraph-env.js +1 -0
  49. package/packages/cli/dist/src/langgraph-official.d.ts +2 -0
  50. package/packages/cli/dist/src/langgraph-official.js +1 -1
  51. package/packages/cli/dist/src/memory/lifecycle.d.ts +2 -0
  52. package/packages/cli/dist/src/memory/lifecycle.js +1 -0
  53. package/packages/cli/dist/src/memory/providers.d.ts +3 -0
  54. package/packages/cli/dist/src/memory/providers.js +1 -0
  55. package/packages/cli/dist/src/output.js +1 -1
  56. package/packages/cli/dist/src/server.d.ts +2 -0
  57. package/packages/cli/dist/src/server.js +1 -1
  58. package/packages/cli/package.json +2 -0
  59. package/packages/core/dist/evaluations/index.d.ts +18 -0
  60. package/packages/core/dist/evaluations/index.js +1 -0
  61. package/packages/core/dist/execution-contract.js +1 -1
  62. package/packages/core/dist/index.d.ts +3 -0
  63. package/packages/core/dist/index.js +1 -1
  64. package/packages/core/dist/memory-plugins/maintenance.js +1 -1
  65. package/packages/core/dist/memory-plugins/shared.js +1 -1
  66. package/packages/core/dist/memory-plugins.js +1 -1
  67. package/packages/core/dist/recovery/tool-call.d.ts +15 -0
  68. package/packages/core/dist/recovery/tool-call.js +1 -1
  69. package/packages/core/dist/runtime/completion.js +1 -1
  70. package/packages/core/dist/runtime/direct-tool-call.js +1 -1
  71. package/packages/core/dist/runtime/events.d.ts +77 -20
  72. package/packages/core/dist/runtime/memory.js +1 -1
  73. package/packages/core/dist/runtime/persistence/artifacts.js +1 -1
  74. package/packages/core/dist/runtime/persistence/inspection.js +1 -1
  75. package/packages/core/dist/runtime/persistence/queue.js +1 -1
  76. package/packages/core/dist/runtime/persistence/stores.js +1 -1
  77. package/packages/core/dist/runtime/progress-narration.d.ts +33 -0
  78. package/packages/core/dist/runtime/progress-narration.js +1 -0
  79. package/packages/core/dist/runtime/tool-gateway.d.ts +5 -0
  80. package/packages/core/dist/runtime.d.ts +2 -1
  81. package/packages/core/dist/runtime.js +1 -1
  82. package/packages/core/dist/spec-driven/config.d.ts +4 -0
  83. package/packages/core/dist/spec-driven/config.js +1 -0
  84. package/packages/core/dist/spec-driven/events.d.ts +11 -0
  85. package/packages/core/dist/spec-driven/events.js +1 -0
  86. package/packages/core/dist/spec-driven/index.d.ts +4 -0
  87. package/packages/core/dist/spec-driven/index.js +1 -0
  88. package/packages/core/dist/spec-driven/lifecycle.d.ts +11 -0
  89. package/packages/core/dist/spec-driven/lifecycle.js +1 -0
  90. package/packages/core/dist/spec-driven/types.d.ts +38 -0
  91. package/packages/core/dist/spec-driven/types.js +1 -0
  92. package/packages/core/dist/trace.d.ts +1 -1
  93. package/packages/core/dist/trace.js +1 -1
  94. package/packages/core/dist/types.d.ts +15 -1
  95. package/packages/core/dist/workflows/index.js +1 -1
  96. package/packages/core/dist/workflows/runtime.js +1 -1
  97. package/packages/core/dist/workspace/types.d.ts +9 -0
  98. package/packages/governance/dist/src/skill-candidates.js +1 -1
  99. package/packages/memory/dist/src/langmem-service.js +1 -1
  100. package/packages/memory/dist/src/maintenance.js +1 -1
  101. package/packages/memory/dist/src/policy.js +1 -1
  102. package/packages/memory/dist/src/provider.js +1 -1
  103. package/packages/memory/dist/src/store.js +1 -1
  104. package/packages/protocols/dist/src/http-server.js +1 -1
  105. package/packages/protocols/dist/src/openai-compatible.js +1 -1
  106. package/packages/protocols/dist/src/openai-payload.js +1 -1
  107. package/packages/protocols/dist/src/openai-stream.js +1 -1
  108. package/packages/tool-gateway/dist/src/argument-guard.d.ts +2 -1
  109. package/packages/tool-gateway/dist/src/argument-guard.js +1 -1
  110. package/packages/tool-gateway/dist/src/in-memory.js +1 -1
  111. package/packages/tool-gateway/dist/src/module-loader.js +1 -1
  112. package/packages/tool-gateway/dist/src/schema-validation.js +1 -1
  113. package/packages/tool-gateway/dist/src/types.d.ts +3 -0
  114. package/packages/tool-gateway/package.json +1 -1
  115. package/packages/workspace-yaml/dist/discovery.js +1 -1
  116. package/packages/workspace-yaml/dist/documents.js +1 -1
  117. package/packages/workspace-yaml/dist/evaluations.d.ts +9 -0
  118. package/packages/workspace-yaml/dist/evaluations.js +1 -0
  119. package/packages/workspace-yaml/dist/loader.js +1 -1
  120. package/packages/workspace-yaml/dist/workflows.js +1 -1
package/README.md CHANGED
@@ -1,32 +1,212 @@
1
1
  # stable-harness
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/stable-harness)](https://www.npmjs.com/package/stable-harness)
4
+ [![license](https://img.shields.io/npm/l/stable-harness)](https://www.npmjs.com/package/stable-harness)
5
+
3
6
  Stable runtime and operator control plane for agent applications.
4
7
 
5
- `stable-harness` is not an agent execution framework. It loads a YAML-defined
6
- workspace, maps that workspace onto an upstream agent framework, and owns the
7
- production runtime surfaces around execution: requests, sessions, approvals,
8
- events, artifacts, memory lifecycle, recovery, governance, and protocol access.
8
+ `stable-harness` lets a team keep its chosen agent framework while adding the
9
+ production surfaces that real workspaces need: YAML inventory, runtime requests,
10
+ sessions, event traces, artifacts, memory lifecycle, governance hooks, recovery,
11
+ tool repair, and protocol access.
12
+
13
+ It is not another agent execution framework. Upstream frameworks own execution
14
+ semantics. Stable Harness owns the runtime boundary around them.
15
+
16
+ ## Why Use It
17
+
18
+ Agent frameworks are good at deciding what an agent should do next. Production
19
+ applications also need a stable layer that can be inspected, governed, resumed,
20
+ replayed, and called through predictable APIs.
9
21
 
10
- The first backend target is DeepAgents. The adapter contract is intentionally
11
- thin: upstream frameworks own execution semantics; stable-harness owns runtime
12
- assembly and lifecycle.
22
+ Stable Harness gives you that layer without rewriting the backend:
23
+
24
+ - define agents, tools, models, memory, workflows, and protocol exposure in YAML
25
+ - run the same workspace through CLI, SDK, HTTP, and OpenAI-compatible clients
26
+ - keep DeepAgents and LangGraph behavior upstream-native through thin adapters
27
+ - validate and repair tool calls at the runtime gateway before execution
28
+ - observe upstream tool, planning, delegation, progress, memory, and artifact events
29
+ - keep downstream product logic in the workspace, not inside the framework
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install stable-harness
35
+ ```
36
+
37
+ Stable Harness currently targets Node.js `>=24 <25`.
13
38
 
14
39
  ## First Run
15
40
 
41
+ Clone the repo when developing the framework itself:
42
+
16
43
  ```bash
44
+ git clone git@github.com:botbotgo/stable-harness.git
45
+ cd stable-harness
17
46
  npm install
18
47
  npm run build
19
48
  npm run check:rules
20
- npm run test
49
+ npm test
21
50
  npm run example:minimal
22
51
  ```
23
52
 
53
+ Run an existing Stable Harness workspace:
54
+
55
+ ```bash
56
+ stable-harness -w ./examples/minimal-deepagents "hello stable harness"
57
+ ```
58
+
59
+ Inspect the workspace without running an agent:
60
+
61
+ ```bash
62
+ stable-harness -w ./examples/minimal-deepagents
63
+ stable-harness agent render orchestra -w ./examples/minimal-deepagents
64
+ stable-harness workflow render review-shell -w ./examples/minimal-deepagents
65
+ ```
66
+
67
+ Start the OpenAI-compatible facade:
68
+
69
+ ```bash
70
+ stable-harness start -w ./examples/minimal-deepagents --port 8642
71
+ ```
72
+
73
+ Then point compatible clients at:
74
+
75
+ ```text
76
+ http://127.0.0.1:8642/v1
77
+ ```
78
+
79
+ ## Embed In An App
80
+
81
+ ```ts
82
+ import { createStableHarnessRuntime } from "stable-harness";
83
+
84
+ const runtime = await createStableHarnessRuntime("/path/to/workspace");
85
+
86
+ const response = await runtime.request({
87
+ input: "Review the current release evidence.",
88
+ agentId: "orchestra",
89
+ });
90
+
91
+ console.log(response.output);
92
+ ```
93
+
94
+ The runtime also exposes `subscribe`, `inspect`, `getRun`, `listRequests`,
95
+ `listSessions`, `inspectRequest`, `cancel`, and `stop` so applications can build
96
+ operator workflows around the same execution surface.
97
+
98
+ ## Workspace Shape
99
+
100
+ A workspace is a folder with Kubernetes-style YAML documents:
101
+
102
+ ```text
103
+ config/
104
+ runtime/workspace.yaml
105
+ agents/orchestra.yaml
106
+ catalogs/models.yaml
107
+ catalogs/tools.yaml
108
+ workflows/review-shell.yaml
109
+ resources/
110
+ tools/
111
+ skills/
112
+ ```
113
+
114
+ Minimal runtime:
115
+
116
+ ```yaml
117
+ apiVersion: stable-harness.dev/v1
118
+ kind: Runtime
119
+ metadata:
120
+ name: app-runtime
121
+ spec:
122
+ routing:
123
+ defaultAgentId: orchestra
124
+ protocols:
125
+ inProcess: true
126
+ openaiCompatible:
127
+ bearerToken: ${env:STABLE_HARNESS_API_KEY}
128
+ ```
129
+
130
+ Minimal agent:
131
+
132
+ ```yaml
133
+ apiVersion: stable-harness.dev/v1
134
+ kind: Agent
135
+ metadata:
136
+ name: orchestra
137
+ spec:
138
+ backend: deepagents
139
+ modelRef: local-dev
140
+ systemPrompt: You are a concise workspace agent.
141
+ tools:
142
+ - shell
143
+ subagents:
144
+ - reviewer
145
+ ```
146
+
147
+ ## Runtime Boundary
148
+
149
+ ```mermaid
150
+ flowchart LR
151
+ Client["CLI / SDK / HTTP / OpenAI-compatible client"]
152
+ Runtime["Stable Harness runtime"]
153
+ Inventory["YAML workspace inventory"]
154
+ Gateway["Tool gateway + repair policy"]
155
+ Adapter["Thin backend adapter"]
156
+ Backend["DeepAgents / LangGraph / future backend"]
157
+ Ops["Events / runs / memory / approvals / artifacts"]
158
+
159
+ Client --> Runtime
160
+ Inventory --> Runtime
161
+ Runtime --> Gateway
162
+ Runtime --> Adapter
163
+ Adapter --> Backend
164
+ Runtime --> Ops
165
+ ```
166
+
167
+ Stable Harness owns lifecycle, governance, observability, persistence, recovery,
168
+ protocol access, and tool-gateway policy. It does not infer routing from user
169
+ keywords, synthesize upstream planning calls, or rebuild backend-native agent
170
+ semantics.
171
+
172
+ ## Current Backends
173
+
174
+ | Backend | Status | Boundary |
175
+ | --- | --- | --- |
176
+ | DeepAgents | Primary adapter | Upstream execution, skills, planning, delegation, and built-ins are passed through; Stable Harness observes and governs the runtime edge. |
177
+ | LangGraph | Runtime and workflow adapter | Stable Harness can compile explicit workflow topology and expose LangGraph-compatible protocol surfaces. |
178
+ | Custom adapters | Supported through SDK | Implement `RuntimeAdapter` and declare the backend in workspace YAML. |
179
+
180
+ ## Tool Reliability
181
+
182
+ Stable Harness uses `@botbotgo/better-call` at the tool-gateway boundary. The
183
+ default CLI path configures repair mode for registered tools, so malformed or
184
+ near-miss tool calls can be repaired before execution while agent inventory,
185
+ schema validation, semantic validators, and governance policy still define what
186
+ is allowed.
187
+
188
+ This is constrained repair, not silent magic:
189
+
190
+ - unknown or unauthorized tools stay blocked
191
+ - semantic validators remain authoritative
192
+ - upstream built-ins stay upstream-owned
193
+ - repaired calls are observable through runtime events and traces
194
+
195
+ ## Protocols
196
+
197
+ - OpenAI-compatible facade: [docs/protocols/openai-compatible.md](docs/protocols/openai-compatible.md)
198
+ - LangGraph-compatible facade: [docs/protocols/langgraph-compatible.md](docs/protocols/langgraph-compatible.md)
199
+ - HTTP runtime protocol: [docs/protocols/http-runtime.md](docs/protocols/http-runtime.md)
200
+
24
201
  ## Product Boundary
25
202
 
26
- See:
203
+ Read these before adding public runtime behavior:
27
204
 
28
205
  - [Product boundary](docs/product-boundary.md)
29
206
  - [Compatibility matrix](docs/compatibility-matrix.md)
30
207
  - [Implementation blueprint](docs/implementation-blueprint.md)
31
208
  - [Engineering rules](docs/engineering-rules.md)
32
209
  - [Adapter contract](docs/adapter-contract.md)
210
+
211
+ The short rule: pass through upstream execution semantics first, then add only
212
+ small, typed, replaceable runtime capabilities around them.
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{createDeepAgentsAdapter as t}from"@stable-harness/adapter-deepagents";import{createStableHarnessRuntime as o,projectEvent as e,projectRuntimeTrace as a}from"@stable-harness/core";import{createModuleToolGateway as s}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as r}from"@stable-harness/workspace-yaml";import{createAgentHarness as n,request as c,stop as l}from"./compat/agent-harness.js";import{formatCompatDelta as i}from"./runtime/compat/presentation.js";const p=function(t){let o,e,a,s=process.cwd(),r=!1,n=!1,c=!1,l=Number(process.env.STABLE_HARNESS_CLI_TIMEOUT_MS??3e5);const i=[];for(let p=0;p<t.length;p+=1)"-w"===t[p]||"--workspace"===t[p]?s=t[++p]??s:"--agent"===t[p]?o=t[++p]:"--tool"===t[p]?e=t[++p]:"--tool-args-json"===t[p]?a=m(t[++p]??"{}"):"--trace"===t[p]?r=!0:"--trace-json"===t[p]?n=!0:"--native"===t[p]?c=!0:"--timeout-ms"===t[p]?l=Number(t[++p]??l):i.push(t[p]);return{workspaceRoot:s,agentId:o,toolId:e,toolArgs:a,trace:r,traceJson:n,native:c,timeoutMs:l,prompt:i.join(" ")||"hello"}}(process.argv.slice(2));function u(t){return"tool.started"===t.type?[`agent:${t.agentId} Running tool ${t.toolId}`]:[]}function m(t){try{return JSON.parse(t)}catch(t){throw new Error(`Invalid --tool-args-json value: ${t instanceof Error?t.message:String(t)}`)}}p.toolId||p.native?await async function(n){const c=setTimeout(()=>{process.stderr.write(`botbotgo native request timed out after ${n.timeoutMs}ms\n`),process.exit(124)},n.timeoutMs);try{const c=await r(n.workspaceRoot),l=await s({tools:c.tools.values()}),i=o({workspace:c,toolGateway:l,adapters:[t()]});n.trace?i.subscribe(t=>{const o=e(t);var a;o&&console.log(`trace:${o.agentId}:${o.label}${a=o.detail,"string"==typeof a?.toolId?`:${a.toolId}`:""}`);for(const o of u(t))console.log(o)}):i.subscribe(t=>{for(const o of u(t))console.log(o)});const p=await i.request({agentId:n.agentId,input:n.prompt,toolCall:n.toolId?{toolId:n.toolId,args:n.toolArgs}:void 0});!function(t,o,e){if(!e.trace&&!e.traceJson)return;const s=t.getRun(o),r=s?a(s):[];e.traceJson&&console.log(JSON.stringify({trace:r}))}(i,p.requestId,n),console.log(p.output),process.exitCode="completed"===p.state?0:1}finally{clearTimeout(c)}}(p):await async function(t){const o=await n(t.workspaceRoot);try{const e=await c(o,{agentId:t.agentId,input:t.prompt,dataListener(t){for(const o of i(t))console.log(o)}});console.log(e.output),process.exitCode="completed"===e.state?0:1}finally{await l(o)}}(p);
2
+ import{createDeepAgentsAdapter as t}from"@stable-harness/adapter-deepagents";import{createStableHarnessRuntime as o,projectEvent as e,projectRuntimeTrace as a}from"@stable-harness/core";import{createModuleToolGateway as r}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as s}from"@stable-harness/workspace-yaml";import{createAgentHarness as n,request as i,stop as c}from"./compat/agent-harness.js";import{formatCompatDelta as l}from"./runtime/compat/presentation.js";const p=function parseArgs(t){let o,e,a,r=process.cwd(),s=!1,n=!1,i=!1,c=!1,l=!1,p=Number(process.env.STABLE_HARNESS_CLI_TIMEOUT_MS??3e5);const g=[];for(let m=0;m<t.length;m+=1)"-h"===t[m]||"--help"===t[m]?l=!0:"-w"===t[m]||"--workspace"===t[m]?r=t[++m]??r:"--agent"===t[m]?o=t[++m]:"--tool"===t[m]?e=t[++m]:"--tool-args-json"===t[m]?a=parseJsonArg(t[++m]??"{}"):"--trace"===t[m]?s=!0:"--trace-json"===t[m]?n=!0:"--progress"===t[m]?i=!0:"--native"===t[m]?c=!0:"--timeout-ms"===t[m]?p=Number(t[++m]??p):g.push(t[m]);return{workspaceRoot:r,agentId:o,toolId:e,toolArgs:a,trace:s,traceJson:n,progress:i,native:c,help:l,timeoutMs:p,prompt:g.join(" ")||"hello"}}(process.argv.slice(2));function formatNativeCompatEvent(t){return"runtime.tool.direct.started"===t.type?[`agent:${t.agentId} Running tool ${t.toolId}`]:[]}function parseJsonArg(t){try{return JSON.parse(t)}catch(t){throw new Error(`Invalid --tool-args-json value: ${t instanceof Error?t.message:String(t)}`)}}p.help?console.log(function legacyHelpText(){return["Usage:"," botbotgo -w <workspace> [--agent <id>] [prompt]"," botbotgo --native -w <workspace> [--agent <id>] [prompt]"," botbotgo --native -w <workspace> --tool <id> --tool-args-json <json>","","Options:"," -w, --workspace <path> Workspace root."," --agent <id> Select an agent."," --native Use the native stable runtime path."," --tool <id> Invoke an explicit registered tool on the native path."," --tool-args-json <json> Tool arguments for --tool."," --trace Print trace lines."," --trace-json Print trace JSON."," --progress Print native progress narration."," --timeout-ms <ms> Request timeout."," -h, --help Show this help."].join("\n")}()):p.toolId||p.native?await async function runNative(n){const i=setTimeout(()=>{process.stderr.write(`botbotgo native request timed out after ${n.timeoutMs}ms\n`),process.exit(124)},n.timeoutMs);try{const i=await s(n.workspaceRoot),c=await r({tools:i.tools.values()}),l=o({workspace:i,toolGateway:c,adapters:[t()],progressNarration:n.progress?{enabled:!0,style:"cli"}:void 0});n.trace?l.subscribe(t=>{const o=e(t);o&&console.log(`trace:${o.agentId}:${o.label}${function formatTraceDetail(t){return"string"==typeof t?.toolId?`:${t.toolId}`:""}(o.detail)}`);for(const o of formatNativeCompatEvent(t))console.log(o)}):l.subscribe(t=>{for(const o of formatNativeCompatEvent(t))console.log(o)}),n.progress&&l.subscribe(t=>{"runtime.progress.narration"===t.type&&console.log(`progress:${t.agentId}:${t.message}`)});const p=await l.request({agentId:n.agentId,input:n.prompt,toolCall:n.toolId?{toolId:n.toolId,args:n.toolArgs}:void 0});!function printNativeTrace(t,o,e){if(!e.trace&&!e.traceJson)return;const r=t.getRun(o),s=r?a(r):[];e.traceJson&&console.log(JSON.stringify({trace:s}))}(l,p.requestId,n),console.log(p.output),process.exitCode="completed"===p.state?0:1}finally{clearTimeout(i)}}(p):await async function runCompat(t){const o=await n(t.workspaceRoot);try{const e=await i(o,{agentId:t.agentId,input:t.prompt,dataListener(t){for(const o of l(t))console.log(o)}});console.log(e.output),process.exitCode="completed"===e.state?0:1}finally{await c(o)}}(p);
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{runAgentHarnessCompatAgent as t}from"../runtime/compat/agent-harness-compat-runner.js";import{loadWorkspace as n}from"../workspace/compile.js";export{loadWorkspace}from"../workspace/compile.js";export async function createAgentHarness(t){return function(t){const n=new Set;return{workspace:t,async request(r){const s=e();return o(t,r,s,n)},async*streamEvents(r){const s=e();yield{type:"event",event:{eventType:"request.created",requestId:s}};const a=function(){const e=[],t=[];let n=!1;return{push(n){const o=t.shift();o?o({value:n,done:!1}):e.push(n)},close(){for(n=!0;t.length;)t.shift()({value:void 0,done:!0})},[Symbol.asyncIterator]:()=>({next:async()=>{const o=e.shift();return o?{value:o,done:!1}:n?{value:void 0,done:!0}:new Promise(e=>t.push(e))}})}}(),c=o(t,r,s,n,e=>{"tool.start"===e.type?a.push({type:"event",event:{payload:{event:"on_tool_start",run_type:"tool",name:e.toolName}}}):a.push({requestId:s,...e,type:"tool-result"})}).finally(()=>a.close());for await(const e of a)yield e;await c},cancel(e){n.add(e)},async stop(){n.clear()}}}(await n(t))}export async function request(e,t){return e.request(t)}export async function cancelRequest(e,t){t.reason,e.cancel(t.requestId)}export async function stop(e){await e.stop()}async function o(e,n,o,r,s){const a=n.agentId??"orchestra",c=e.agents.get(a);if(!c)throw new Error(`Unknown agent: ${a}`);return t({workspace:e,agent:c,request:{...n,dataListener:s??n.dataListener},requestId:o,cancelled:r})}
1
+ import{randomUUID as e}from"node:crypto";import{runAgentHarnessCompatAgent as t}from"../runtime/compat/agent-harness-compat-runner.js";import{loadWorkspace as n}from"../workspace/compile.js";export{loadWorkspace}from"../workspace/compile.js";export async function createAgentHarness(t){return function createRuntime(t){const n=new Set;return{workspace:t,async request(o){const r=e();return runRequest(t,o,r,n)},async*streamEvents(o){const r=e();yield{type:"event",event:{eventType:"request.created",requestId:r}};const s=function createAsyncQueue(){const e=[],t=[];let n=!1;return{push(n){const o=t.shift();o?o({value:n,done:!1}):e.push(n)},close(){for(n=!0;t.length;)t.shift()({value:void 0,done:!0})},[Symbol.asyncIterator]:()=>({next:async()=>{const o=e.shift();return o?{value:o,done:!1}:n?{value:void 0,done:!0}:new Promise(e=>t.push(e))}})}}(),a=runRequest(t,o,r,n,e=>{"agent.tool.start"===e.type?s.push({type:"event",event:{payload:{event:"on_tool_start",run_type:"tool",name:e.toolName}}}):s.push({requestId:r,...e,type:"tool-result"})}).finally(()=>s.close());for await(const e of s)yield e;await a},cancel(e){n.add(e)},async stop(){n.clear()}}}(await n(t))}export async function request(e,t){return e.request(t)}export async function cancelRequest(e,t){t.reason,e.cancel(t.requestId)}export async function stop(e){await e.stop()}async function runRequest(e,n,o,r,s){const a=n.agentId??"orchestra",c=e.agents.get(a);if(!c)throw new Error(`Unknown agent: ${a}`);return t({workspace:e,agent:c,request:{...n,dataListener:s??n.dataListener},requestId:o,cancelled:r})}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@ export { createDeepAgentsMiddlewareSkillProvider, createLangGraphRuntimeAdapter,
5
5
  export type { LangGraphNodeHandler, LangGraphNodeHandlerInput, LangGraphNodeResolver, LangGraphNodeResolverInput, LangGraphSkillMiddlewareProvider, LangGraphSkillMiddlewareProviderInput, LangGraphSkillResolverProvider, LangGraphWorkflowAdapterOptions, LangGraphWorkflowState, LangGraphWorkflowTraceEntry, } from "@stable-harness/adapter-langgraph";
6
6
  export type { LangGraphRegistrySkillOutput } from "@stable-harness/adapter-langgraph";
7
7
  export { createLangMemServiceProvider } from "@stable-harness/memory";
8
- export type { CompiledWorkspace, RuntimeAdapter, RuntimeEvent, RuntimeWorkflowAdapter, RuntimeRequest, RuntimeResponse, RuntimeRunRecord, StableHarnessRuntime, WorkspaceAgent, WorkspaceModel, WorkspaceRuntimePolicy, WorkspaceTool, } from "@stable-harness/core";
8
+ export { applySpecDrivenPhaseTransition, containsRecoverableResultOutput, createSpecDrivenArtifact, createSpecDrivenArtifactEvent, createSpecDrivenPhaseEvent, createSpecDrivenWorkflowPolicy, createSpecDrivenWorkflowState, projectRuntimeTrace, } from "@stable-harness/core";
9
+ export type { CompiledWorkspace, RuntimeAdapter, RuntimeEvent, RuntimeWorkflowAdapter, RuntimeRequest, RuntimeResponse, RuntimeRunRecord, RuntimeTraceEntry, StableHarnessRuntime, SpecDrivenPhaseRecord, SpecDrivenPhaseStatus, SpecDrivenPhaseTransition, SpecDrivenWorkflowState, WorkspaceAgent, WorkspaceModel, WorkspaceRuntimePolicy, WorkspaceSpecDrivenPhase, WorkspaceSpecDrivenWorkflowPolicy, WorkspaceTool, } from "@stable-harness/core";
9
10
  export { loadWorkspaceFromYaml } from "@stable-harness/workspace-yaml";
10
11
  export { createInMemoryToolGateway, createModuleToolGateway } from "@stable-harness/tool-gateway";
11
12
  export type { ModuleToolDescriptor, ToolGateway, ToolGatewayContext, ToolGatewayInvokeRequest, ToolGatewayInvokeResult, ToolGatewayTool, } from "@stable-harness/tool-gateway";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{createDeepAgentsAdapter as e}from"@stable-harness/adapter-deepagents";import{createLangGraphRuntimeAdapter as r,createLangGraphWorkflowAdapter as t,createRegistrySkillResolverProvider as a}from"@stable-harness/adapter-langgraph";import{createStableHarnessRuntime as o}from"@stable-harness/core";import{createModuleToolGateway as n}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as s}from"@stable-harness/workspace-yaml";export{createDeepAgentsAdapter,createDeepAgentsMemoryMaintenanceTarget}from"@stable-harness/adapter-deepagents";export{createDeepAgentsMiddlewareSkillProvider,createLangGraphRuntimeAdapter,createLangGraphWorkflowAdapter,createRegistrySkillResolverProvider}from"@stable-harness/adapter-langgraph";export{createLangMemServiceProvider}from"@stable-harness/memory";export{loadWorkspaceFromYaml}from"@stable-harness/workspace-yaml";export{createInMemoryToolGateway,createModuleToolGateway}from"@stable-harness/tool-gateway";export function createStableHarnessRuntime(e){return"string"==typeof e?createStableRuntime({workspaceRoot:e}):"workspaceRoot"in e?createStableRuntime(e):o(e)}export async function createStableRuntime(e){const r=await s(e.workspaceRoot),t=e.toolGateway??await n({tools:r.tools.values()});return o({workspace:r,toolGateway:t,adapters:e.adapters??i(r,e),workflowAdapters:e.workflowAdapters??l(r,e)})}export async function requestStableRuntime(e,r){return e.request(r)}function i(t,a){const o={deepagents:({policy:r})=>e(r.config?{config:r.config}:{}),langgraph:({policy:e})=>r({...c(e.config),name:e.name}),...a.adapterFactories},n=function(e){const r=e.runtime.adapters?.filter(e=>!1!==e.enabled);return r&&r.length>0?r:[...new Set([...e.agents.values()].map(e=>e.backend))].map(e=>({name:e}))}(t);return n.map(e=>{const r=o[e.name];if(r)return r({policy:e,workspace:t});throw new Error(`Unsupported runtime adapter: ${e.name}`)})}function l(e,r){const a={langgraph:({name:e,options:r})=>t({...c(r),name:e}),...r.workflowAdapterFactories};return[...new Set([...e.workflows.values()].map(e=>e.adapter??"").filter(Boolean))].map(t=>{const o=a[t];return o?.({name:t,workspace:e,options:p(r,t)})}).filter(e=>Boolean(e))}function p(e,r){return e.workflowAdapterOptions?.[r]??{}}function c(e){return d(e)?{...e,...void 0!==m(e)?{skillProvider:m(e)}:{}}:{}}function m(e){if(!1===e.skillProvider)return!1;const r=function(e){return d(e.skills)?e.skills:d(e.skillProvider)?e.skillProvider:void 0}(e);if(!r)return;const t=u(r.provider)??u(r.name)??"registry-resolver";if(["none","disabled","false"].includes(t))return!1;if("registry-resolver"!==t)throw new Error(`Unsupported LangGraph skill provider: ${t}`);return a({..."boolean"==typeof r.includeContent?{includeContent:r.includeContent}:{},..."number"==typeof r.maxBytes&&Number.isFinite(r.maxBytes)?{maxBytes:r.maxBytes}:{}})}function u(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function d(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
1
+ import{createDeepAgentsAdapter as e}from"@stable-harness/adapter-deepagents";import{createLangGraphRuntimeAdapter as r,createLangGraphWorkflowAdapter as a,createRegistrySkillResolverProvider as t}from"@stable-harness/adapter-langgraph";import{createStableHarnessRuntime as o}from"@stable-harness/core";import{createModuleToolGateway as n}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as s}from"@stable-harness/workspace-yaml";export{createDeepAgentsAdapter,createDeepAgentsMemoryMaintenanceTarget}from"@stable-harness/adapter-deepagents";export{createDeepAgentsMiddlewareSkillProvider,createLangGraphRuntimeAdapter,createLangGraphWorkflowAdapter,createRegistrySkillResolverProvider}from"@stable-harness/adapter-langgraph";export{createLangMemServiceProvider}from"@stable-harness/memory";export{applySpecDrivenPhaseTransition,containsRecoverableResultOutput,createSpecDrivenArtifact,createSpecDrivenArtifactEvent,createSpecDrivenPhaseEvent,createSpecDrivenWorkflowPolicy,createSpecDrivenWorkflowState,projectRuntimeTrace}from"@stable-harness/core";export{loadWorkspaceFromYaml}from"@stable-harness/workspace-yaml";export{createInMemoryToolGateway,createModuleToolGateway}from"@stable-harness/tool-gateway";export function createStableHarnessRuntime(e){return"string"==typeof e?createStableRuntime({workspaceRoot:e}):"workspaceRoot"in e?createStableRuntime(e):o(e)}export async function createStableRuntime(e){const r=await s(e.workspaceRoot),a=e.toolGateway??await n({tools:r.tools.values()});return o({workspace:r,toolGateway:a,adapters:e.adapters??createRuntimeAdapters(r,e),workflowAdapters:e.workflowAdapters??createWorkflowAdapters(r,e)})}export async function requestStableRuntime(e,r){return e.request(r)}function createRuntimeAdapters(a,t){const o={deepagents:({policy:r})=>e(r.config?{config:r.config}:{}),langgraph:({policy:e})=>r({...readLangGraphOptions(e.config),name:e.name}),...t.adapterFactories},n=function runtimeAdapterPolicies(e){const r=e.runtime.adapters?.filter(e=>!1!==e.enabled);return r&&r.length>0?r:[...new Set([...e.agents.values()].map(e=>e.backend))].map(e=>({name:e}))}(a);return n.map(e=>{const r=o[e.name];if(r)return r({policy:e,workspace:a});throw new Error(`Unsupported runtime adapter: ${e.name}`)})}function createWorkflowAdapters(e,r){const t={langgraph:({name:e,options:r})=>a({...readLangGraphOptions(r),name:e}),...r.workflowAdapterFactories};return[...new Set([...e.workflows.values()].map(e=>e.adapter??"").filter(Boolean))].map(a=>{const o=t[a];return o?.({name:a,workspace:e,options:readWorkflowAdapterOptions(r,a)})}).filter(e=>Boolean(e))}function readWorkflowAdapterOptions(e,r){return e.workflowAdapterOptions?.[r]??{}}function readLangGraphOptions(e){return isRecord(e)?{...e,...void 0!==readLangGraphSkillProvider(e)?{skillProvider:readLangGraphSkillProvider(e)}:{}}:{}}function readLangGraphSkillProvider(e){if(!1===e.skillProvider)return!1;const r=function readSkillProviderConfig(e){return isRecord(e.skills)?e.skills:isRecord(e.skillProvider)?e.skillProvider:void 0}(e);if(!r)return;const a=readString(r.provider)??readString(r.name)??"registry-resolver";if(["none","disabled","false"].includes(a))return!1;if("registry-resolver"!==a)throw new Error(`Unsupported LangGraph skill provider: ${a}`);return t({..."boolean"==typeof r.includeContent?{includeContent:r.includeContent}:{},..."number"==typeof r.maxBytes&&Number.isFinite(r.maxBytes)?{maxBytes:r.maxBytes}:{}})}function readString(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
@@ -1 +1 @@
1
- import{generateWithModel as t}from"../model/ollama.js";import{parseToolCall as e}from"./json.js";import{buildAgentPrompt as n,buildRoutingPrompt as r,buildRoutingRepairPrompt as a,buildToolSelectionPrompt as o}from"./prompts.js";import{loadAgentTools as s}from"./tool-registry.js";export async function runAgentHarnessCompatAgent(o){const m=await async function(e){if(0===e.agent.subagentRefs.length)return[e.agent];const n=l(e.workspace,e.agent),o=e.request.input??"",s=await t(n,r(e.workspace,e.agent,o)),u=await async function(e,n,r,o){let s=o,u=I(s);for(let o=0;o<3&&!i(e,r,u);o+=1)s=await t(n,a({workspace:e.workspace,agent:e.agent,userInput:r,previousResponse:s,issue:c(e,r,u)})),u=I(s);return i(e,r,u)?u:function(t,e){if(d(t,e)<=1)return[];const n=String(t.deepAgentConfig.systemPrompt??"").split(/\r?\n/u).map(t=>t.trim());for(const r of n){if(!/delegation tree|路由|委托/u.test(r)||!/must|必须|include|包含/u.test(r))continue;const n=t.subagentRefs.filter(t=>{return new RegExp(`\\b${e=t,e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}\\b`,"u").test(r);var e});if(n.length>=d(t,e))return n}return[]}(e.agent,r)}(e,n,o,s);return u.map(t=>e.workspace.agents.get(t)).filter(t=>Boolean(t))}(o),y=m[0]??o.agent,b=[];!function(t,e,n,r){if(e.length>1&&t.request.dataListener?.({type:"delegation.plan",requestId:t.requestId,agentId:t.agent.id,agentIds:e.map(t=>t.id)}),n.id===t.agent.id)return;const a={agentId:t.agent.id,toolName:"task",output:n.id};r.push(a),t.request.dataListener?.({type:"tool.result",requestId:t.requestId,...a}),t.request.dataListener?.({type:"delegation.start",requestId:t.requestId,agentId:n.id})}(o,m,y,b);const N=l(o.workspace,y),R=await s(o.workspace,y.toolRefs),$=[],j=new Set;if(y.toolRefs.length>0){const t=await u(o,y,R,$);t&&R.has(t.name)&&(f(o,b,$,await g(o,y,R.get(t.name),t.arguments)),j.add(k(t.name,t.arguments)),await w(o,y,R,j,b,$))}for(let r=0;r<4;r+=1){if(o.cancelled.has(o.requestId))return q(o.requestId,$.join("\n\n"),b,y.id);const r=n({workspace:o.workspace,agent:y,model:N,userInput:o.request.input??"",tools:R,observations:$}),a=await t(N,r),s=e(a);if(!s&&p(b))return q(o.requestId,a,b,y.id);const i=s??await u(o,y,R,$);if(!i||!R.has(i.name)){const t=await u(o,y,h(R,j),$);if(t&&R.has(t.name)){f(o,b,$,await g(o,y,R.get(t.name),t.arguments)),j.add(k(t.name,t.arguments));continue}return q(o.requestId,a,b,y.id)}const d=k(i.name,i.arguments);if(j.has(d)){const t=await u(o,y,h(R,j),$);if(t&&R.has(t.name)){f(o,b,$,await g(o,y,R.get(t.name),t.arguments)),j.add(k(t.name,t.arguments));continue}return q(o.requestId,$.join("\n\n"),b,y.id)}f(o,b,$,await g(o,y,R.get(i.name),i.arguments)),j.add(d),await w(o,y,R,j,b,$)}return q(o.requestId,$.join("\n\n"),b,y.id)}async function u(n,r,a,s){if(n.cancelled.has(n.requestId)||0===a.size)return;const u=l(n.workspace,r),i=await t(u,o({agent:r,userInput:n.request.input??"",tools:a,observations:s}));return e(i)}function i(t,e,n){return n.filter(e=>t.workspace.agents.has(e)).length>=d(t.agent,e)}function d(t,e){const n=e.split(/\r?\n/u).filter(t=>/^\s*\d+[.)、]/u.test(t)).length;return Math.max(1,Math.min(t.subagentRefs.length,n||1))}function c(t,e,n){const r=d(t.agent,e);return 0===n.length?"No valid available subagent id was returned.":`Only ${n.length} valid subagent ids were returned; at least ${r} are required for this multi-part request.`}function l(t,e){const n=e.modelRef??"default",r=t.models.get(n)??t.models.get("default");if(!r)throw new Error(`No model configured for agent ${e.id}`);return r}async function g(t,e,n,r){return t.request.dataListener?.({type:"tool.start",requestId:t.requestId,agentId:e.id,toolName:n.name}),await new Promise(t=>setImmediate(t)),t.cancelled.has(t.requestId)?{agentId:e.id,toolName:n.name,output:"cancelled before tool invocation",isError:!0}:async function(t,e,n,r){const a=process.cwd();try{process.chdir(t.workspace.workspaceRoot);const a=await n.invoke(r);return{agentId:e.id,toolName:n.name,output:y(a)}}catch(t){return{agentId:e.id,toolName:n.name,output:y(t),isError:!0}}finally{process.chdir(a)}}(t,e,n,r)}function f(t,e,n,r){e.push(r),t.request.dataListener?.({type:"tool.result",requestId:t.requestId,...r}),n.push(`Tool ${r.toolName} returned:\n${r.output}`)}function m(t){return"task"===t}function p(t){return t.some(t=>!t.isError&&!m(t.toolName))}async function w(t,e,n,r,a,o){if(p(a))return;const s=await u(t,e,h(n,r),o);s&&n.has(s.name)&&(f(t,a,o,await g(t,e,n.get(s.name),s.arguments)),r.add(k(s.name,s.arguments)))}function h(t,e){return new Map([...t].filter(([t])=>!m(t)&&!function(t,e){return[...e].some(e=>e.startsWith(`${t}:`))}(t,e)))}function I(t){try{const e=function(t){try{return JSON.parse(t)}catch{const e=t.indexOf("{"),n=t.lastIndexOf("}");if(e>=0&&n>e)return JSON.parse(t.slice(e,n+1));throw new Error("No JSON object found")}}(t),n=function(t){for(const e of["agentIds","routing","agents","delegations","plan"]){const n=t[e],r=Array.isArray(n)?n.filter(t=>"string"==typeof t):[];if(r.length>0)return[...new Set(r)]}return[]}(e);if(n.length>0)return n;const r=function(t){for(const e of["agentId","agent","owner","route"]){const n=t[e];if("string"==typeof n&&n.trim().length>0)return n}}(e);return r?[r]:[]}catch{return[]}}function q(t,e,n,r){return{requestId:t,state:"completed",output:e,metadata:{executedToolResults:n,routedAgentId:r}}}function y(t){return t instanceof Error?t.stack??t.message:"string"==typeof t?t:JSON.stringify(t)}function k(t,e){return`${t}:${JSON.stringify(e)}`}
1
+ import{generateWithModel as e}from"../model/ollama.js";import{parseToolCall as t}from"./json.js";import{buildAgentPrompt as n,buildRoutingPrompt as o,buildRoutingRepairPrompt as r,buildToolSelectionPrompt as a}from"./prompts.js";import{loadAgentTools as s}from"./tool-registry.js";export async function runAgentHarnessCompatAgent(a){const i=await async function resolveAgents(t){if(0===t.agent.subagentRefs.length)return[t.agent];const n=resolveModel(t.workspace,t.agent),a=t.request.input??"",s=await e(n,o(t.workspace,t.agent,a)),i=await async function repairRouting(t,n,o,a){let s=a,i=parseRouting(s);for(let a=0;a<3&&!hasEnoughRouting(t,o,i);a+=1)s=await e(n,r({workspace:t.workspace,agent:t.agent,userInput:o,previousResponse:s,issue:routingIssue(t,o,i)})),i=parseRouting(s);return hasEnoughRouting(t,o,i)?i:function readConfiguredRoutingTree(e,t){if(requiredRoutingCount(e,t)<=1)return[];const n=String(e.deepAgentConfig.systemPrompt??"").split(/\r?\n/u).map(e=>e.trim());for(const o of n){if(!/delegation tree|路由|委托/u.test(o)||!/must|必须|include|包含/u.test(o))continue;const n=e.subagentRefs.filter(e=>{return new RegExp(`\\b${t=e,t.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}\\b`,"u").test(o);var t});if(n.length>=requiredRoutingCount(e,t))return n}return[]}(t.agent,o)}(t,n,a,s);return i.map(e=>t.workspace.agents.get(e)).filter(e=>Boolean(e))}(a),u=i[0]??a.agent,l=[];!function emitRoutingEvents(e,t,n,o){if(t.length>1&&e.request.dataListener?.({type:"delegation.plan",requestId:e.requestId,agentId:e.agent.id,agentIds:t.map(e=>e.id)}),n.id===e.agent.id)return;const r={agentId:e.agent.id,toolName:"task",output:n.id};o.push(r),e.request.dataListener?.({type:"agent.tool.result",requestId:e.requestId,...r}),e.request.dataListener?.({type:"delegation.start",requestId:e.requestId,agentId:n.id})}(a,i,u,l);const d=resolveModel(a.workspace,u),c=await s(a.workspace,u.toolRefs),g=[],f=new Set;if(u.toolRefs.length>0){const e=await forceToolSelection(a,u,c,g);e&&c.has(e.name)&&(recordToolResult(a,l,g,await invokeToolWithStart(a,u,c.get(e.name),e.arguments)),f.add(toolKey(e.name,e.arguments)),await ensureEvidenceAfterPlanning(a,u,c,f,l,g))}for(let o=0;o<4;o+=1){if(a.cancelled.has(a.requestId))return completed(a.requestId,g.join("\n\n"),l,u.id);const o=n({workspace:a.workspace,agent:u,model:d,userInput:a.request.input??"",tools:c,observations:g}),r=await e(d,o),s=t(r);if(!s&&hasEvidence(l))return completed(a.requestId,r,l,u.id);const i=s??await forceToolSelection(a,u,c,g);if(!i||!c.has(i.name)){const e=await forceToolSelection(a,u,availableNextTools(c,f),g);if(e&&c.has(e.name)){recordToolResult(a,l,g,await invokeToolWithStart(a,u,c.get(e.name),e.arguments)),f.add(toolKey(e.name,e.arguments));continue}return completed(a.requestId,r,l,u.id)}const p=toolKey(i.name,i.arguments);if(f.has(p)){const e=await forceToolSelection(a,u,availableNextTools(c,f),g);if(e&&c.has(e.name)){recordToolResult(a,l,g,await invokeToolWithStart(a,u,c.get(e.name),e.arguments)),f.add(toolKey(e.name,e.arguments));continue}return completed(a.requestId,g.join("\n\n"),l,u.id)}recordToolResult(a,l,g,await invokeToolWithStart(a,u,c.get(i.name),i.arguments)),f.add(p),await ensureEvidenceAfterPlanning(a,u,c,f,l,g)}return completed(a.requestId,g.join("\n\n"),l,u.id)}async function forceToolSelection(n,o,r,s){if(n.cancelled.has(n.requestId)||0===r.size)return;const i=resolveModel(n.workspace,o),u=await e(i,a({agent:o,userInput:n.request.input??"",tools:r,observations:s}));return t(u)}function hasEnoughRouting(e,t,n){return n.filter(t=>e.workspace.agents.has(t)).length>=requiredRoutingCount(e.agent,t)}function requiredRoutingCount(e,t){const n=t.split(/\r?\n/u).filter(e=>/^\s*\d+[.)、]/u.test(e)).length;return Math.max(1,Math.min(e.subagentRefs.length,n||1))}function routingIssue(e,t,n){const o=requiredRoutingCount(e.agent,t);return 0===n.length?"No valid available subagent id was returned.":`Only ${n.length} valid subagent ids were returned; at least ${o} are required for this multi-part request.`}function resolveModel(e,t){const n=t.modelRef??"default",o=e.models.get(n)??e.models.get("default");if(!o)throw new Error(`No model configured for agent ${t.id}`);return o}async function invokeToolWithStart(e,t,n,o){return e.request.dataListener?.({type:"agent.tool.start",requestId:e.requestId,agentId:t.id,toolName:n.name}),await new Promise(e=>setImmediate(e)),e.cancelled.has(e.requestId)?{agentId:t.id,toolName:n.name,output:"cancelled before tool invocation",isError:!0}:async function invokeTool(e,t,n,o){const r=process.cwd();try{process.chdir(e.workspace.workspaceRoot);const r=await n.invoke(o);return{agentId:t.id,toolName:n.name,output:stringifyOutput(r)}}catch(e){return{agentId:t.id,toolName:n.name,output:stringifyOutput(e),isError:!0}}finally{process.chdir(r)}}(e,t,n,o)}function recordToolResult(e,t,n,o){t.push(o),e.request.dataListener?.({type:"agent.tool.result",requestId:e.requestId,...o}),n.push(`Tool ${o.toolName} returned:\n${o.output}`)}function isPlanningTool(e){return"task"===e}function hasEvidence(e){return e.some(e=>!e.isError&&!isPlanningTool(e.toolName))}async function ensureEvidenceAfterPlanning(e,t,n,o,r,a){if(hasEvidence(r))return;const s=await forceToolSelection(e,t,availableNextTools(n,o),a);s&&n.has(s.name)&&(recordToolResult(e,r,a,await invokeToolWithStart(e,t,n.get(s.name),s.arguments)),o.add(toolKey(s.name,s.arguments)))}function availableNextTools(e,t){return new Map([...e].filter(([e])=>!isPlanningTool(e)&&!function hasExecutedTool(e,t){return[...t].some(t=>t.startsWith(`${e}:`))}(e,t)))}function parseRouting(e){try{const t=function parseJsonObject(e){try{return JSON.parse(e)}catch{const t=e.indexOf("{"),n=e.lastIndexOf("}");if(t>=0&&n>t)return JSON.parse(e.slice(t,n+1));throw new Error("No JSON object found")}}(e),n=function readRoutingIds(e){for(const t of["agentIds","routing","agents","delegations","plan"]){const n=e[t],o=Array.isArray(n)?n.filter(e=>"string"==typeof e):[];if(o.length>0)return[...new Set(o)]}return[]}(t);if(n.length>0)return n;const o=function readRoutingId(e){for(const t of["agentId","agent","owner","route"]){const n=e[t];if("string"==typeof n&&n.trim().length>0)return n}}(t);return o?[o]:[]}catch{return[]}}function completed(e,t,n,o){return{requestId:e,state:"completed",output:t,metadata:{executedToolResults:n,routedAgentId:o}}}function stringifyOutput(e){return e instanceof Error?e.stack??e.message:"string"==typeof e?e:JSON.stringify(e)}function toolKey(e,t){return`${e}:${JSON.stringify(t)}`}
@@ -1 +1 @@
1
- export function parseToolCall(r){return function(r){if("string"!=typeof r)return[];const e=[];for(const o of function(t){return[t.trim(),extractJsonObject(t)].filter(t=>Boolean(t))}(r)){const r=n(t(o));r&&e.push(r)}for(const o of function(t){return t.match(/\{[^{}]*"name"\s*:\s*"[^"]+"[\s\S]*?\}\s*\}/gu)??[]}(r)){const r=n(t(o));r&&e.push(r)}return function(t){const n=new Set;return t.filter(t=>{const r=`${t.name}:${JSON.stringify(t.arguments)}`;return!n.has(r)&&(n.add(r),!0)})}(e)}(r)[0]}export function extractJsonObject(t){const n=t.indexOf("{"),r=t.lastIndexOf("}");return n>=0&&r>n?t.slice(n,r+1):void 0}function t(t){try{return JSON.parse(t)}catch{return}}function n(t){if("object"!=typeof t||null===t)return;const n=t;if("string"!=typeof n.name)return;const r="object"==typeof n.arguments&&null!==n.arguments?n.arguments:{};return{name:n.name,arguments:r}}
1
+ export function parseToolCall(n){return function parseToolCalls(n){if("string"!=typeof n)return[];const t=[];for(const e of function jsonCandidates(n){return[n.trim(),extractJsonObject(n)].filter(n=>Boolean(n))}(n)){const n=normalizeToolCall(safeParse(e));n&&t.push(n)}for(const e of function objectCandidates(n){return n.match(/\{[^{}]*"name"\s*:\s*"[^"]+"[\s\S]*?\}\s*\}/gu)??[]}(n)){const n=normalizeToolCall(safeParse(e));n&&t.push(n)}return function uniqueToolCalls(n){const t=new Set;return n.filter(n=>{const e=`${n.name}:${JSON.stringify(n.arguments)}`;return!t.has(e)&&(t.add(e),!0)})}(t)}(n)[0]}export function extractJsonObject(n){const t=n.indexOf("{"),e=n.lastIndexOf("}");return t>=0&&e>t?n.slice(t,e+1):void 0}function safeParse(n){try{return JSON.parse(n)}catch{return}}function normalizeToolCall(n){if("object"!=typeof n||null===n)return;const t=n;if("string"!=typeof t.name)return;const e="object"==typeof t.arguments&&null!==t.arguments?t.arguments:{};return{name:t.name,arguments:e}}
@@ -1 +1 @@
1
- import{readPlanTodos as t}from"@stable-harness/core";export function formatCompatDelta(o){return"delegation.start"===o.type?[`agent:${o.agentId} Starting delegated execution.`]:"delegation.plan"===o.type?[`Planned delegation tree: ${e(o).join(" -> ")}`]:"tool.start"===o.type?[`agent:${o.agentId} Running tool ${a=o.toolName,"write_todos"===a?"Call Write Todos.":String(a)}`]:"tool.result"===o.type?function(e){if("task"===e.toolName)return[`agent:${e.agentId} Delegating to ${e.output}.`];if("write_todos"===e.toolName){const o=t(e.output);return o.length>0?function(t,e){const o=e.filter(t=>"completed"===t.status).length,a=e.filter(t=>"in_progress"===t.status).length,r=e.filter(t=>"pending"===t.status).length,s=[`agent:${t} TODO Burn Down | ${o}/${e.length} done | ${a} active | ${r} pending`];for(const t of e)s.push(` [${n(t.status)}] ${t.content}`);for(const n of e.filter(t=>"completed"===t.status||"failed"===t.status))s.push(`agent:${t} TODO ${n.status}: ${n.content}`);return s}(String(e.agentId),o):[]}return[]}(o):[];var a}function e(t){return Array.isArray(t.agentIds)?t.agentIds.map(String):[]}function n(t){return"completed"===t?"x":"in_progress"===t?"~":"failed"===t?"!":" "}
1
+ import{readPlanTodos as t}from"@stable-harness/core";export function formatCompatDelta(e){return"delegation.start"===e.type?[`agent:${e.agentId} Starting delegated execution.`]:"delegation.plan"===e.type?[`Planned delegation tree: ${readAgentIds(e).join(" -> ")}`]:"agent.tool.start"===e.type?[`agent:${e.agentId} Running tool ${n=e.toolName,"write_todos"===n?"Call Write Todos.":String(n)}`]:"agent.tool.result"===e.type?function formatToolResult(e){if("task"===e.toolName)return[`agent:${e.agentId} Delegating to ${e.output}.`];if("write_todos"===e.toolName){const n=t(e.output);return n.length>0?function formatTodoBurnDown(t,e){const n=e.filter(t=>"completed"===t.status).length,o=e.filter(t=>"in_progress"===t.status).length,r=e.filter(t=>"pending"===t.status).length,a=[`agent:${t} TODO Burn Down | ${n}/${e.length} done | ${o} active | ${r} pending`];for(const t of e)a.push(` [${todoMarker(t.status)}] ${t.content}`);for(const n of e.filter(t=>"completed"===t.status||"failed"===t.status))a.push(`agent:${t} TODO ${n.status}: ${n.content}`);return a}(String(e.agentId),n):[]}return[]}(e):[];var n}function readAgentIds(t){return Array.isArray(t.agentIds)?t.agentIds.map(String):[]}function todoMarker(t){return"completed"===t?"x":"in_progress"===t?"~":"failed"===t?"!":" "}
@@ -1 +1 @@
1
- export function buildAgentPrompt(o){const t=[...o.tools.values()].map(e=>({name:e.name,description:e.description??""}));return[o.agent.deepAgentConfig.systemPrompt??"","","You are running inside stable-harness.",e(o.agent,o.tools),'If a tool is needed, return exactly one JSON object: {"name":"tool_name","arguments":{...}}.',"Before returning a final answer, re-read the tool policy excerpts and run any required follow-up evidence tool for this request.","If enough evidence is available, return the final answer only.","Available tools:",JSON.stringify(t,null,2),o.observations.length?`Tool observations:\n${o.observations.join("\n\n")}`:"",`User request:\n${o.userInput}`].filter(Boolean).join("\n")}export function buildRoutingPrompt(e,t,n){const r=t.subagentRefs.map(o=>e.agents.get(o)).filter(e=>Boolean(e)).map(e=>({id:e.id,description:e.description}));return["Choose the best subagent plan for this request.",o(t,r.map(e=>e.id)),t.deepAgentConfig.systemPrompt??"","Routing policy excerpts are authoritative workspace config.","This is a routing-only step. Tools are unavailable here.","Do not return write_todos, read_todos, task, markdown, or prose.","If a routing policy says a request must include one or more available subagents, include those subagents.",'If one owner is enough, return exactly JSON: {"agentId":"one_id","reason":"short reason"}.','If the request contains multiple specialist-owned tasks, return exactly JSON: {"agentIds":["first_id","second_id"],"reason":"short reason"}.',"For numbered or multi-part requests, check every item and include an owner for each distinct specialist-owned task.","Do not omit later tasks after selecting earlier owners.","Return only ids from Available subagents.","Available subagents:",JSON.stringify(r,null,2),`User request:\n${n}`].filter(Boolean).join("\n")}export function buildRoutingRepairPrompt(e){const t=e.agent.subagentRefs.map(o=>e.workspace.agents.get(o)).filter(e=>Boolean(e)).map(e=>({id:e.id,description:e.description}));return[e.issue??"Your previous routing response did not contain a valid complete routing plan.",o(e.agent,t.map(e=>e.id)),"This is a routing-only repair. Tools are unavailable here.","Do not return write_todos, read_todos, task, markdown, or prose.","Return JSON only.",'For one owner: {"agentId":"one_id","reason":"short reason"}.','For multiple owners: {"agentIds":["first_id","second_id"],"reason":"short reason"}.',"Use only ids from Available subagents.","Available subagents:",JSON.stringify(t,null,2),`Previous response:\n${e.previousResponse}`,`User request:\n${e.userInput}`].filter(Boolean).join("\n")}export function buildToolSelectionPrompt(o){const t=[...o.tools.values()].map(e=>({name:e.name,description:e.description??""}));return[`Agent: ${o.agent.id}`,o.agent.description?`Responsibility: ${o.agent.description}`:"",e(o.agent,o.tools),"Choose the single best evidence tool from the available tools.",'Return exactly one JSON object: {"name":"tool_name","arguments":{...}}.',"Infer arguments from the user request, tool description, and tool policy excerpts.","If the user explicitly names an available tool, choose that exact tool.","Do not use empty arguments when the tool policy or user request provides a concrete schema, URL, path, question, ticker, or command.","Prefer the most specific evidence tool for the requested artifact or operation before choosing a general search tool.","Available tools:",JSON.stringify(t,null,2),o.observations?.length?`Tool observations already gathered:\n${o.observations.join("\n\n")}`:"",`User request:\n${o.userInput}`].filter(Boolean).join("\n")}function e(e,o){const t=String(e.deepAgentConfig.systemPrompt??""),n=[...o.keys()].filter(e=>"write_todos"!==e&&"read_todos"!==e),r=t.split(/\r?\n/u).map(e=>e.trim()).filter(e=>n.some(o=>e.includes(o))).slice(0,12);return r.length>0?`Tool policy excerpts:\n${r.join("\n")}`:""}function o(e,o){const n=String(e.deepAgentConfig.systemPrompt??"").split(/\r?\n/u).map(e=>e.trim()).filter(e=>function(e,o){const t=e.toLowerCase();return o.some(o=>e.includes(o))&&/\b(route|routing|delegate|delegation|owner|specialist|include|tree|task)\b/u.test(t)}(e,o)),r=n.map(e=>({line:e,score:t(e,o)})).sort((e,o)=>o.score-e.score).map(e=>e.line).slice(0,16);return r.length>0?`Routing policy excerpts:\n${r.join("\n")}`:""}function t(e,o){const t=e.toLowerCase();return 10*o.filter(o=>e.includes(o)).length+(t.match(/\b(must|include|order|tree|requires|required|exactly|only)\b/gu)??[]).length}
1
+ export function buildAgentPrompt(e){const o=[...e.tools.values()].map(e=>({name:e.name,description:e.description??""}));return[e.agent.deepAgentConfig.systemPrompt??"","","You are running inside stable-harness.",toolPolicyExcerpt(e.agent,e.tools),'If a tool is needed, return exactly one JSON object: {"name":"tool_name","arguments":{...}}.',"Before returning a final answer, re-read the tool policy excerpts and run any required follow-up evidence tool for this request.","If enough evidence is available, return the final answer only.","Available tools:",JSON.stringify(o,null,2),e.observations.length?`Tool observations:\n${e.observations.join("\n\n")}`:"",`User request:\n${e.userInput}`].filter(Boolean).join("\n")}export function buildRoutingPrompt(e,o,t){const n=o.subagentRefs.map(o=>e.agents.get(o)).filter(e=>Boolean(e)).map(e=>({id:e.id,description:e.description}));return["Choose the best subagent plan for this request.",routingPolicyExcerpt(o,n.map(e=>e.id)),o.deepAgentConfig.systemPrompt??"","Routing policy excerpts are authoritative workspace config.","This is a routing-only step. Tools are unavailable here.","Do not return write_todos, read_todos, task, markdown, or prose.","If a routing policy says a request must include one or more available subagents, include those subagents.",'If one owner is enough, return exactly JSON: {"agentId":"one_id","reason":"short reason"}.','If the request contains multiple specialist-owned tasks, return exactly JSON: {"agentIds":["first_id","second_id"],"reason":"short reason"}.',"For numbered or multi-part requests, check every item and include an owner for each distinct specialist-owned task.","Do not omit later tasks after selecting earlier owners.","Return only ids from Available subagents.","Available subagents:",JSON.stringify(n,null,2),`User request:\n${t}`].filter(Boolean).join("\n")}export function buildRoutingRepairPrompt(e){const o=e.agent.subagentRefs.map(o=>e.workspace.agents.get(o)).filter(e=>Boolean(e)).map(e=>({id:e.id,description:e.description}));return[e.issue??"Your previous routing response did not contain a valid complete routing plan.",routingPolicyExcerpt(e.agent,o.map(e=>e.id)),"This is a routing-only repair. Tools are unavailable here.","Do not return write_todos, read_todos, task, markdown, or prose.","Return JSON only.",'For one owner: {"agentId":"one_id","reason":"short reason"}.','For multiple owners: {"agentIds":["first_id","second_id"],"reason":"short reason"}.',"Use only ids from Available subagents.","Available subagents:",JSON.stringify(o,null,2),`Previous response:\n${e.previousResponse}`,`User request:\n${e.userInput}`].filter(Boolean).join("\n")}export function buildToolSelectionPrompt(e){const o=[...e.tools.values()].map(e=>({name:e.name,description:e.description??""}));return[`Agent: ${e.agent.id}`,e.agent.description?`Responsibility: ${e.agent.description}`:"",toolPolicyExcerpt(e.agent,e.tools),"Choose the single best evidence tool from the available tools.",'Return exactly one JSON object: {"name":"tool_name","arguments":{...}}.',"Infer arguments from the user request, tool description, and tool policy excerpts.","If the user explicitly names an available tool, choose that exact tool.","Do not use empty arguments when the tool policy or user request provides a concrete schema, URL, path, question, ticker, or command.","Prefer the most specific evidence tool for the requested artifact or operation before choosing a general search tool.","Available tools:",JSON.stringify(o,null,2),e.observations?.length?`Tool observations already gathered:\n${e.observations.join("\n\n")}`:"",`User request:\n${e.userInput}`].filter(Boolean).join("\n")}function toolPolicyExcerpt(e,o){const t=String(e.deepAgentConfig.systemPrompt??""),n=[...o.keys()].filter(e=>"write_todos"!==e&&"read_todos"!==e),r=t.split(/\r?\n/u).map(e=>e.trim()).filter(e=>n.some(o=>e.includes(o))).slice(0,12);return r.length>0?`Tool policy excerpts:\n${r.join("\n")}`:""}function routingPolicyExcerpt(e,o){const t=String(e.deepAgentConfig.systemPrompt??"").split(/\r?\n/u).map(e=>e.trim()).filter(e=>function isRoutingPolicyLine(e,o){const t=e.toLowerCase();return o.some(o=>e.includes(o))&&/\b(route|routing|delegate|delegation|owner|specialist|include|tree|task)\b/u.test(t)}(e,o)),n=t.map(e=>({line:e,score:routingPolicyScore(e,o)})).sort((e,o)=>o.score-e.score).map(e=>e.line).slice(0,16);return n.length>0?`Routing policy excerpts:\n${n.join("\n")}`:""}function routingPolicyScore(e,o){const t=e.toLowerCase();return 10*o.filter(o=>e.includes(o)).length+(t.match(/\b(must|include|order|tree|requires|required|exactly|only)\b/gu)??[]).length}
@@ -1 +1 @@
1
- export async function generateWithModel(n,i){if("ollama"!==n.provider&&"openai-compatible"!==n.provider)throw new Error(`Unsupported model provider: ${n.provider}`);const o=function(t){const e=t.trim()||"http://127.0.0.1:11434/";return e.endsWith("/")?e:`${e}/`}(String(n.init.baseUrl??n.init.baseURL??"")),a="ollama"===n.provider?`${o}api/generate`:`${o}v1/chat/completions`,s=Number(n.init.timeout??12e4),c=Number(n.init.retries??2)+1;let u;for(let o=1;o<=c;o+=1)try{return await t(n,a,i,s)}catch(t){if(u=t,o===c||!e(t))throw t;await r(500*o)}throw u instanceof Error?u:new Error(String(u))}async function t(t,e,r,o){const a=new AbortController,s=setTimeout(()=>a.abort(),o),c=await function(t,e,r,o){return fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify("ollama"===t.provider?n(t,r):i(t,r)),signal:o})}(t,e,r,a.signal).finally(()=>clearTimeout(s));if(!c.ok)throw new Error(`Model request failed with HTTP ${c.status}: ${await c.text()}`);const u=await c.json();return function(t,e){if("ollama"===t)return String(e.response??"");const r=(Array.isArray(e.choices)?e.choices:[])[0];return String(r?.message?.content??"")}(t.provider,u)}function e(t){if(t instanceof DOMException&&"AbortError"===t.name)return!1;const e=t instanceof Error?t.message:String(t);return/fetch failed|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket|network/i.test(e)}function r(t){return new Promise(e=>setTimeout(e,t))}function n(t,e){return{model:t.model,prompt:e,stream:!1,think:t.init.think??!1,options:{num_ctx:t.init.numCtx,num_predict:t.init.numPredict,temperature:t.init.temperature}}}function i(t,e){return{model:t.model,messages:[{role:"user",content:e}],temperature:t.init.temperature??0}}
1
+ export async function generateWithModel(e,t){if("ollama"!==e.provider&&"openai-compatible"!==e.provider)throw new Error(`Unsupported model provider: ${e.provider}`);const r=function normalizeBaseUrl(e){const t=e.trim()||"http://127.0.0.1:11434/";return t.endsWith("/")?t:`${t}/`}(String(e.init.baseUrl??e.init.baseURL??"")),n="ollama"===e.provider?`${r}api/generate`:`${function normalizeOpenAiBaseUrl(e){return e.endsWith("/v1/")?e:`${e}v1/`}(r)}chat/completions`,o=Number(e.init.timeout??12e4),i=Number(e.init.retries??2)+1;let a;for(let r=1;r<=i;r+=1)try{return await requestModel(e,n,t,o)}catch(e){if(a=e,r===i||!isRetryableModelError(e))throw e;await delay(500*r)}throw a instanceof Error?a:new Error(String(a))}async function requestModel(e,t,r,n){const o=new AbortController,i=setTimeout(()=>o.abort(),n),a=await function fetchModel(e,t,r,n){return fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify("ollama"===e.provider?ollamaBody(e,r):openAiBody(e,r)),signal:n})}(e,t,r,o.signal).finally(()=>clearTimeout(i));if(!a.ok)throw new Error(`Model request failed with HTTP ${a.status}: ${await a.text()}`);const s=await a.json();return function parseModelResponse(e,t){if("ollama"===e)return String(t.response??"");const r=(Array.isArray(t.choices)?t.choices:[])[0];return String(r?.message?.content??"")}(e.provider,s)}function isRetryableModelError(e){if(e instanceof DOMException&&"AbortError"===e.name)return!1;const t=e instanceof Error?e.message:String(e);return/fetch failed|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket|network/i.test(t)}function delay(e){return new Promise(t=>setTimeout(t,e))}function ollamaBody(e,t){return{model:e.model,prompt:t,stream:!1,think:e.init.think??!1,options:{num_ctx:e.init.numCtx,num_predict:e.init.numPredict,temperature:e.init.temperature}}}function openAiBody(e,t){return{model:e.model,messages:[{role:"user",content:t}],temperature:e.init.temperature??0}}
@@ -1 +1 @@
1
- import{readFileSync as r,statSync as t}from"node:fs";import*as o from"node:path";import{parse as e}from"yaml";export function validateSkillMetadata(i){const a=t(i).isDirectory()?o.join(i,"SKILL.md"):i,s=r(a,"utf8").match(/^---\n([\s\S]*?)\n---/u);if(!s)throw new Error(`${a} is missing YAML front matter`);const m=e(s[1]),l=n(m.name,"name",a),f=n(m.description,"description",a),d=(c=m["allowed-tools"],Array.isArray(c)?c.filter(r=>"string"==typeof r&&r.trim().length>0):[]);var c;if(0===d.length)throw new Error(`${o.basename(i)} must declare allowed-tools`);return{name:l,description:f,allowedTools:d,path:a}}function n(r,t,o){if("string"!=typeof r||!r.trim())throw new Error(`${o} front matter requires ${t}`);return r.trim()}
1
+ import{readFileSync as r,statSync as t}from"node:fs";import*as e from"node:path";import{parse as n}from"yaml";export function validateSkillMetadata(o){const i=t(o).isDirectory()?e.join(o,"SKILL.md"):o,a=r(i,"utf8").match(/^---\n([\s\S]*?)\n---/u);if(!a)throw new Error(`${i} is missing YAML front matter`);const s=n(a[1]),m=readString(s.name,"name",i),l=readString(s.description,"description",i),d=function readStringList(r){return Array.isArray(r)?r.filter(r=>"string"==typeof r&&r.trim().length>0):[]}(s["allowed-tools"]);if(0===d.length)throw new Error(`${e.basename(o)} must declare allowed-tools`);return{name:m,description:l,allowedTools:d,path:i}}function readString(r,t,e){if("string"!=typeof r||!r.trim())throw new Error(`${e} front matter requires ${t}`);return r.trim()}
@@ -1 +1 @@
1
- import{readdir as e,readFile as t}from"node:fs/promises";import*as s from"node:path";import{parseAllDocuments as n}from"yaml";import{validateSkillMetadata as r}from"../runtime/skills/skill-metadata.js";export async function loadWorkspace(u){const y=s.resolve(u),g=await async function(e){const s=await o(e),r=[];for(const e of s){const s=await t(e,"utf8");for(const e of n(s)){const t=e.toJSON();t?.kind&&r.push({...t,metadata:t.metadata??{},spec:t.spec??{}})}}return r}(s.join(y,"config")),w=function(e){const t=new Map;for(const s of e){if("Models"===s.kind&&Array.isArray(s.spec))for(const e of s.spec){const s=i(e);t.set(String(s.name),s)}if("Model"===s.kind&&d(s.spec)){const e=i({name:s.metadata?.name,...s.spec});t.set(String(e.name),e)}}return t}(g),k=function(e,t){const n=new Map;for(const r of t.filter(e=>"Agent"===e.kind)){if(!d(r.spec))continue;const t=String(r.metadata?.name),o=d(r.spec.config)?r.spec.config:{},i=c(r.spec,o);n.set(t,{id:t,name:t,description:r.metadata?.description??"",sourcePath:s.join(e,"config","agents",`${t}.yaml`),modelRef:l(r.spec.modelRef),toolRefs:f(r.spec.tools),skillPathRefs:f(r.spec.skills).map(t=>s.join(e,"resources","skills",t)),subagentRefs:f(r.spec.subagents),memorySources:m(r.spec.memory),deepAgentConfig:{...o,responseFormat:p(o.responseFormat),systemPrompt:i}})}return n}(y,g),A=await async function(n,r){const o=new Map;for(const e of r.filter(e=>"Tool"===e.kind)){const t=e.metadata?.name;t&&o.set(t,{name:t,id:t,spec:e.spec})}return await async function(n,r){const o=await e(n,{withFileTypes:!0});for(const e of o){if(!e.isFile()||!e.name.endsWith(".mjs")||e.name.startsWith("_"))continue;const o=s.join(n,e.name),i=(await t(o,"utf8")).match(/export\s+const\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*tool/u),a=i?.[1]??s.basename(e.name,".mjs");r.set(a,{name:a,id:a,sourcePath:o})}}(s.join(n,"resources","tools"),o),o}(y,g),h=await async function(t){const n=new Map,o=s.join(t,"resources","skills");for(const t of await e(o,{withFileTypes:!0})){if(!t.isDirectory())continue;const e=s.join(o,t.name),i=r(e);n.set(i.name,{name:i.name,path:e,description:i.description,allowedTools:i.allowedTools})}return n}(y);return function(e,t){for(const n of e.values())n.skillPathRefs=n.skillPathRefs.map(e=>{const n=s.basename(e);return t.get(n)?.path??e})}(k,h),function(e,t){e.has("direct")||e.set("direct",{id:"direct",name:"direct",description:"Direct execution agent.",sourcePath:s.join(t,"config","runtime","workspace.yaml"),toolRefs:[],skillPathRefs:[],subagentRefs:[],memorySources:[],deepAgentConfig:{builtinTools:{filesystem:!1,modelExposed:!1}}})}(k,y),{workspaceRoot:y,models:w,agents:k,tools:A,skills:h,bindings:a(k)}}async function o(t){const n=await e(t,{withFileTypes:!0});return(await Promise.all(n.map(async e=>{const n=s.join(t,e.name);return e.isDirectory()?o(n):e.isFile()&&/\.ya?ml$/iu.test(e.name)?[n]:[]}))).flat().sort()}function i(e){const t=String(e.name),s={...e};return delete s.name,delete s.provider,delete s.model,{name:t,provider:String(u(e.provider)),model:String(u(e.model)),init:(n=s,Object.fromEntries(Object.entries(n).map(([e,t])=>[e,u(t)])))};var n}function a(e){const t=new Map;for(const[s,n]of e){const e=n.deepAgentConfig;t.set(s,{agentId:s,deepAgentParams:{responseFormat:e.responseFormat,systemPrompt:e.systemPrompt}})}return t}function c(e,t){return"string"==typeof e.systemPrompt?e.systemPrompt:"string"==typeof t.systemPrompt?t.systemPrompt:""}function p(e){const t={type:"object",properties:{status:{type:"string",enum:["completed","blocked","failed","refused"]},summary:{type:"array",items:{type:"string"}},findings:{type:"array",items:{type:"string"}},blockers:{type:"array",items:{type:"string"}},nextActions:{type:"array",items:{type:"string"}},report:{type:"string"}},required:["status","summary","findings","blockers","nextActions","report"]};if(!d(e))return t;const s=d(e.properties)?e.properties:{},n=Array.isArray(e.required)?e.required:[];return{...t,...e,properties:{...t.properties,...s},required:[...new Set([...t.required,...n].filter(e=>"string"==typeof e))]}}function m(e){return Array.isArray(e)?e.flatMap(e=>d(e)&&"string"==typeof e.path?[e.path]:[]):[]}function f(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.trim().length>0):[]}function l(e){return"string"==typeof e?e.replace(/^[^/]+\//u,""):void 0}function u(e){if("string"!=typeof e)return e;const t=e.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*?))?\}/gu,(e,t,s)=>process.env[t]??s??"");return/^(true|false)$/iu.test(t)?"true"===t.toLowerCase():/^-?\d+$/u.test(t)?Number(t):t}function d(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
1
+ import{readdir as e,readFile as t}from"node:fs/promises";import*as s from"node:path";import{parseAllDocuments as o}from"yaml";import{validateSkillMetadata as r}from"../runtime/skills/skill-metadata.js";export async function loadWorkspace(n){const i=s.resolve(n),a=await async function readConfigDocuments(e){const s=await listYamlFiles(e),r=[];for(const e of s){const s=await t(e,"utf8");for(const e of o(s)){const t=e.toJSON();t?.kind&&r.push({...t,metadata:t.metadata??{},spec:t.spec??{}})}}return r}(s.join(i,"config")),c=function compileModels(e){const t=new Map;for(const s of e){if("Models"===s.kind&&Array.isArray(s.spec))for(const e of s.spec){const s=compileModel(e);t.set(String(s.name),s)}if("Model"===s.kind&&isRecord(s.spec)){const e=compileModel({name:s.metadata?.name,...s.spec});t.set(String(e.name),e)}}return t}(a),m=function compileAgents(e,t){const o=new Map;for(const r of t.filter(e=>"Agent"===e.kind)){if(!isRecord(r.spec))continue;const t=String(r.metadata?.name),n=isRecord(r.spec.config)?r.spec.config:{},i=readSystemPrompt(r.spec,n);o.set(t,{id:t,name:t,description:r.metadata?.description??"",sourcePath:s.join(e,"config","agents",`${t}.yaml`),modelRef:normalizeRef(r.spec.modelRef),toolRefs:readStringList(r.spec.tools),skillPathRefs:readStringList(r.spec.skills).map(t=>s.join(e,"resources","skills",t)),subagentRefs:readStringList(r.spec.subagents),memorySources:readMemorySources(r.spec.memory),deepAgentConfig:{...n,responseFormat:normalizeResponseFormat(n.responseFormat),systemPrompt:i}})}return o}(i,a),l=await async function compileTools(o,r){const n=new Map;for(const e of r.filter(e=>"Tool"===e.kind)){const t=e.metadata?.name;t&&n.set(t,{name:t,id:t,spec:e.spec})}return await async function loadToolExports(o,r){const n=await e(o,{withFileTypes:!0});for(const e of n){if(!e.isFile()||!e.name.endsWith(".mjs")||e.name.startsWith("_"))continue;const n=s.join(o,e.name),i=(await t(n,"utf8")).match(/export\s+const\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*tool/u),a=i?.[1]??s.basename(e.name,".mjs");r.set(a,{name:a,id:a,sourcePath:n})}}(s.join(o,"resources","tools"),n),n}(i,a),p=await async function compileSkills(t){const o=new Map,n=s.join(t,"resources","skills");for(const t of await e(n,{withFileTypes:!0})){if(!t.isDirectory())continue;const e=s.join(n,t.name),i=r(e);o.set(i.name,{name:i.name,path:e,description:i.description,allowedTools:i.allowedTools})}return o}(i);return function resolveAgentSkillNames(e,t){for(const o of e.values())o.skillPathRefs=o.skillPathRefs.map(e=>{const o=s.basename(e);return t.get(o)?.path??e})}(m,p),function ensureDirectAgent(e,t){e.has("direct")||e.set("direct",{id:"direct",name:"direct",description:"Direct execution agent.",sourcePath:s.join(t,"config","runtime","workspace.yaml"),toolRefs:[],skillPathRefs:[],subagentRefs:[],memorySources:[],deepAgentConfig:{}})}(m,i),{workspaceRoot:i,models:c,agents:m,tools:l,skills:p,bindings:compileBindings(m)}}async function listYamlFiles(t){const o=await e(t,{withFileTypes:!0});return(await Promise.all(o.map(async e=>{const o=s.join(t,e.name);return e.isDirectory()?listYamlFiles(o):e.isFile()&&/\.ya?ml$/iu.test(e.name)?[o]:[]}))).flat().sort()}function compileModel(e){const t=String(e.name),s={...e};return delete s.name,delete s.provider,delete s.model,{name:t,provider:String(resolveValue(e.provider)),model:String(resolveValue(e.model)),init:(o=s,Object.fromEntries(Object.entries(o).map(([e,t])=>[e,resolveValue(t)])))};var o}function compileBindings(e){const t=new Map;for(const[s,o]of e){const e=o.deepAgentConfig;t.set(s,{agentId:s,deepAgentParams:{responseFormat:e.responseFormat,systemPrompt:e.systemPrompt}})}return t}function readSystemPrompt(e,t){return"string"==typeof e.systemPrompt?e.systemPrompt:"string"==typeof t.systemPrompt?t.systemPrompt:""}function normalizeResponseFormat(e){const t={type:"object",properties:{status:{type:"string",enum:["completed","blocked","failed","refused"]},summary:{type:"array",items:{type:"string"}},findings:{type:"array",items:{type:"string"}},blockers:{type:"array",items:{type:"string"}},nextActions:{type:"array",items:{type:"string"}},report:{type:"string"}},required:["status","summary","findings","blockers","nextActions","report"]};if(!isRecord(e))return t;const s=isRecord(e.properties)?e.properties:{},o=Array.isArray(e.required)?e.required:[];return{...t,...e,properties:{...t.properties,...s},required:[...new Set([...t.required,...o].filter(e=>"string"==typeof e))]}}function readMemorySources(e){return Array.isArray(e)?e.flatMap(e=>isRecord(e)&&"string"==typeof e.path?[e.path]:[]):[]}function readStringList(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.trim().length>0):[]}function normalizeRef(e){return"string"==typeof e?e.replace(/^[^/]+\//u,""):void 0}function resolveValue(e){if("string"!=typeof e)return e;const t=e.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*?))?\}/gu,(e,t,s)=>process.env[t]??s??"");return/^(true|false)$/iu.test(t)?"true"===t.toLowerCase():/^-?\d+$/u.test(t)?Number(t):t}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stable-harness",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "description": "Stable application runtime and operator control plane for agent workspaces.",
6
6
  "license": "MIT",
@@ -64,7 +64,7 @@
64
64
  "test:skill-mining:e2e": "node scripts/run-skill-candidate-mining-e2e.mjs",
65
65
  "prepublishOnly": "npm run build && npm run release:check-package",
66
66
  "prepack": "npm run release:minify",
67
- "release:minify": "find dist packages -type f -name '*.js' \\( -path 'dist/*' -o -path '*/dist/*' \\) -exec sh -c 'for f do ./node_modules/.bin/terser \"$f\" --compress passes=2 --mangle --module --toplevel --comments false --output \"$f\"; done' sh {} +",
67
+ "release:minify": "find dist packages -type f -name '*.js' \\( -path 'dist/*' -o -path '*/dist/*' \\) -exec sh -c 'for f do ./node_modules/.bin/terser \"$f\" --compress passes=2 --mangle keep_fnames=true --keep-fnames --keep-classnames --module --comments false --output \"$f\"; done' sh {} +",
68
68
  "release:check-package": "node scripts/release/check-npm-package.mjs",
69
69
  "release:pack": "npm run build && npm run release:check-package && npm pack --dry-run",
70
70
  "release:publish": "npm publish --access public --registry https://registry.npmjs.org/",
@@ -74,10 +74,11 @@
74
74
  "packages/*"
75
75
  ],
76
76
  "dependencies": {
77
- "@botbotgo/better-call": "^0.1.1",
77
+ "@botbotgo/better-call": "^0.1.18",
78
78
  "@langchain/core": "^1.1.43",
79
79
  "@langchain/langgraph": "^1.3.0",
80
80
  "@langchain/ollama": "^1.2.7",
81
+ "@langchain/openai": "^1.4.5",
81
82
  "@stable-harness/adapter-deepagents": "file:packages/adapter-deepagents",
82
83
  "@stable-harness/adapter-langgraph": "file:packages/adapter-langgraph",
83
84
  "@stable-harness/core": "file:packages/core",
@@ -1 +1 @@
1
- import{ChatOllama as e}from"@langchain/ollama";import{createBuiltinToolPolicyMiddleware as t,createObserverMiddleware as n,resolveFilesystemPermissions as r}from"./internal/builtin-tool-policy.js";import{buildGatewayTools as o,stringifyDeepAgentResult as s}from"./internal/gateway-tools.js";import{resolveDeepAgentsNativeMemories as i}from"./memory.js";import{buildDeepAgentRequest as a}from"./internal/messages.js";import{createDeepAgentsRetryMiddleware as m}from"./retry-policy.js";import{streamDeepAgentResult as c}from"./internal/stream-events.js";export function createDeepAgentsAdapter(e={}){return{name:"deepagents",canRun:e=>"deepagents"===e.backend,async run(i){if(i.emit({type:"adapter.event",requestId:i.requestId,sessionId:i.sessionId,agentId:i.agent.id,event:{adapter:"deepagents",phase:"handoff",modelRef:i.agent.modelRef,tools:i.agent.tools,subagents:i.agent.subagents}}),e.runner)return e.runner(i);const g=(e.createDeepAgent??await async function(){try{const e=(await async function(){return new Function("specifier","return import(specifier)")("deepagents")}()).createDeepAgent;if("function"==typeof e)return e}catch(e){throw new Error(`DeepAgents package is required for the default adapter path: ${function(e){return e instanceof Error?e.message:String(e)}(e)}`)}throw new Error("DeepAgents package does not export createDeepAgent.")}())(function(e,s){const i={...f(s),...f(e.agent.config.deepagents)},a=i.permissions??r(e,e.agent);return y({name:e.agent.id,model:i.model??d(e,e.agent),systemPrompt:e.agent.systemPrompt??k(e.agent.config.systemPrompt),backend:i.backend,checkpointer:i.checkpointer,store:i.store,middleware:l(i.middleware,n(e),t(e),m(e.workspace.runtime.retry)),responseFormat:i.responseFormat,contextSchema:i.contextSchema,interruptOn:i.interruptOn,permissions:a,tools:o(e,e.agent.id,e.agent.tools),subagents:e.agent.subagents.map(t=>{const n=e.workspace.agents.get(t),s=f(n?.config.deepagents),i=s.permissions??r(e,n);return y({name:t,description:n?.description??k(n?.config.description)??n?.id,systemPrompt:n?.systemPrompt??k(n?.config.systemPrompt),model:s.model??(n?d(e,n):void 0),middleware:s.middleware,interruptOn:s.interruptOn,permissions:i,responseFormat:s.responseFormat,tools:o(e,t,n?.tools??[]),memory:p(e,n),skills:u(e,n)})}),memory:p(e,e.agent),skills:u(e,e.agent)})}(i,e.config)),v=a(i);if(!0===i.request.metadata?.openaiStream&&g.streamEvents)return c(i,g.streamEvents(v,{version:"v2"}),s);const w=await g.invoke(v);return s(w)}}}function p(e,t){const n=g(t?.config,"memory");if(n)return n;const r=i(e.workspace).map(e=>`/memories/${e.id}.md`);return r.length>0?r:void 0}function u(e,t){const n=g(t?.config,"skills");if(n)return n;const r=(t?.skills??[]).map(t=>e.workspace.skills.get(t)?.path??t);return r.length>0?r:void 0}function l(e,t,n,r=[]){return[t,n,...r,...Array.isArray(e)?e:[]]}function d(t,n){const r=n.modelRef?t.workspace.models.get(n.modelRef):void 0;return r?function(t){return"ollama"===t.provider?new e(y({model:t.model,baseUrl:k(t.config?.baseUrl),temperature:v(t.config?.temperature),numCtx:v(t.config?.numCtx),numPredict:v(t.config?.numPredict),timeout:v(t.config?.timeout),think:"boolean"==typeof t.config?.think?t.config.think:void 0})):t.model}(r):void 0}function f(e){return b(e)?e:{}}function g(e,t){const n=b(e)?e:{},r=f(n.deepagents),o="memory"===t?["memory","memorySources"]:["skills","skillSources"];for(const e of o){const t=w(r[e]);if(t)return t}return w(n[t])}function y(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e))}function k(e){return"string"==typeof e&&e.trim()?e:void 0}function v(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function w(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e):void 0}function b(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
1
+ import{realpathSync as e}from"node:fs";import t from"node:path";import{createBuiltinToolPolicyMiddleware as r,createObserverMiddleware as n,resolveFilesystemPermissions as o}from"./internal/builtin-tool-policy.js";import{buildGatewayTools as s,createToolRepeatState as i,stringifyDeepAgentResult as a}from"./internal/gateway-tools.js";import{resolveDeepAgentsNativeMemories as l}from"./memory.js";import{buildDeepAgentRequest as d}from"./internal/messages.js";import{createRawToolCallParserMiddleware as c}from"./internal/raw-tool-call-parser.js";import{createBackendModel as p}from"./model-providers.js";import{createDeepAgentsRetryMiddleware as u}from"./retry-policy.js";import{streamDeepAgentResult as g}from"./internal/stream-events.js";export function createDeepAgentsAdapter(e={}){return{name:"deepagents",canRun:e=>"deepagents"===e.backend,async run(t){if(t.emit({type:"runtime.adapter.event",requestId:t.requestId,sessionId:t.sessionId,agentId:t.agent.id,event:{adapter:"deepagents",phase:"agent.handoff",modelRef:t.agent.modelRef,tools:t.agent.tools,subagents:t.agent.subagents}}),e.runner)return e.runner(t);const r=e.createDeepAgent?void 0:await async function loadDeepAgentsModule(){try{return await async function importOptionalPackage(e){return new Function("specifier","return import(specifier)")(e)}("deepagents")}catch(e){throw new Error(`DeepAgents package is required for the default adapter path: ${function formatError(e){return e instanceof Error?e.message:String(e)}(e)}`)}}(),n=e.createDeepAgent??function readCreateDeepAgent(e){const t=e?.createDeepAgent;if("function"==typeof t)return t;throw new Error("DeepAgents package does not export createDeepAgent.")}(r),i=n(function buildDeepAgentParams(e,t,r){const n={...readDeepAgentsConfig(t),...readDeepAgentsConfig(e.agent.config.deepagents)},i=resolveDeepAgentsSkills(e,e.agent),a=n.permissions??o(e,e.agent),l=requestScopedRepeatState(e,e.agent.id);return pruneUndefined({name:e.agent.id,model:n.model??resolveAgentModel(e,e.agent),systemPrompt:buildSystemPrompt(e,e.agent),backend:n.backend??resolveDeepAgentsBackend(e,r,i),checkpointer:n.checkpointer,store:n.store,middleware:mergeMiddleware(e,e.agent,n.middleware,l),responseFormat:n.responseFormat,contextSchema:n.contextSchema,interruptOn:n.interruptOn,generalPurposeAgent:readBoolean(n.generalPurposeAgent),taskDescription:readString(n.taskDescription),permissions:a,tools:s(e,e.agent.id,e.agent.tools,resolveAgentRepairModel(e,e.agent,n),l),subagents:e.agent.subagents.map(t=>{const r=e.workspace.agents.get(t),n=readDeepAgentsConfig(r?.config.deepagents),i=n.permissions??o(e,r),a=scopedInput(e,r),l=requestScopedRepeatState(e,t);return pruneUndefined({name:t,description:r?.description??readString(r?.config.description)??r?.id,systemPrompt:buildSystemPrompt(e,r),model:n.model??(r?resolveAgentModel(e,r):void 0),middleware:mergeMiddleware(a,r,n.middleware,l),interruptOn:n.interruptOn,generalPurposeAgent:readBoolean(n.generalPurposeAgent),taskDescription:readString(n.taskDescription),permissions:i,responseFormat:n.responseFormat,tools:s(e,t,r?.tools??[],resolveAgentRepairModel(a,r,n),l),memory:resolveDeepAgentsMemory(e,r),skills:resolveDeepAgentsSkills(e,r)})}),memory:resolveDeepAgentsMemory(e,e.agent),skills:i})}(t,e.config,r)),l=d(t),c=function buildDeepAgentInvokeConfig(e){return pruneUndefined({recursionLimit:readNumber(readDeepAgentsConfig(e.config.deepagents).recursionLimit)??readNumber(e.config.recursionLimit)})}(t.agent);if(!0===t.request.metadata?.openaiStream&&i.streamEvents)return g(t,i.streamEvents(l,{version:"v2",...c}),a);const p=await i.invoke(l,c);return a(p)}}}function buildSystemPrompt(e,t){const r=t?.systemPrompt??readString(t?.config.systemPrompt),n=function buildSkillInventoryPolicy(e,t){const r=(t?.skills??[]).map(t=>e.workspace.skills.get(t)).filter(e=>Boolean(e));if(0!==r.length)return["## Stable Harness Skill Inventory","Use only the skills listed in this workspace inventory. Do not infer or invent skill names, skill directories, or SKILL.md paths from generic examples.","Skill files are already registered with the backend. Do not read local SKILL.md files or skill directories through filesystem tools.","If none of these skills match the task, continue with the configured tools and collected evidence instead of reading an unlisted skill path.",r.map(e=>{const t=e.allowedTools.length>0?`; allowed tools: ${e.allowedTools.join(", ")}`:"",r=e.description?`: ${e.description}${t}`:t;return`- ${e.id}${r}`}).join("\n")].join("\n")}(e,t),o=function buildResponseLanguagePolicy(e){const t=isRecord(e.workspace.runtime.responseLanguage)?e.workspace.runtime.responseLanguage:{};if("matchUser"!==(readString(t.mode)??readString(t.strategy)))return;const r=function detectRequestLanguage(e){return/\p{Script=Han}/u.test(e)?"Chinese":/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(e)?"Japanese":/\p{Script=Hangul}/u.test(e)?"Korean":/\p{Script=Arabic}/u.test(e)?"Arabic":/\p{Script=Hebrew}/u.test(e)?"Hebrew":/\p{Script=Thai}/u.test(e)?"Thai":/\p{Script=Cyrillic}/u.test(e)?"Cyrillic-script language":void 0}(e.request.input);return["Stable runtime response language policy:",...r?[`- Detected request language: ${r}.`]:[],"- Match the final answer language to the original user request unless the user explicitly asks for another language.","- If tool or subagent evidence is in a different language, translate the final user-facing answer into the detected request language.","- When delegating to subagents, include the same response-language requirement in delegated instructions.","- Do not call another tool or subagent only to translate, rewrite, format, or synthesize a completed answer.",`Original user request:\n${e.request.input}`].join("\n")}(e),s=function buildResponsePresentationPolicy(e,t){const r=isRecord(e.workspace.runtime.responsePresentation)?e.workspace.runtime.responsePresentation:{};if(!0===r.enabled&&!function hasStructuredResponseFormat(e){return void 0!==e?.config.responseFormat||isRecord(e?.config.deepagents)&&void 0!==e.config.deepagents.responseFormat}(t)&&"markdown"===(readString(r.style)??"markdown"))return["Stable runtime final-answer presentation policy:","- For user-facing natural-language final answers, use GitHub-flavored Markdown.","- Prefer clear section headings, short paragraphs, and concise bullets over dense prose.","- Use tables only when they make comparison or planning details easier to scan.","- For detailed investigations, plans, or reports, include assumptions, findings, recommendations, and concrete next steps.","- Preserve exact plain text, JSON, code, or other structured output when the user or response format asks for it.","- Do not end with generic follow-up offers; deliver the requested answer directly."].join("\n")}(e,t);return[r,n,o,s].filter(Boolean).join("\n\n")||void 0}function resolveDeepAgentsMemory(e,t){const r=readDeepAgentsStringArray(t?.config,"memory");if(r)return r;const n=l(e.workspace).map(e=>`/memories/${e.id}.md`);return n.length>0?n:void 0}function resolveDeepAgentsSkills(r,n){const o=readDeepAgentsStringArray(n?.config,"skills");if(o)return o;const s=[...new Set((n?.skills??[]).map(e=>r.workspace.skills.get(e)?.path).filter(e=>"string"==typeof e&&e.trim().length>0).map(n=>function backendSkillSourcePath(r,n){const o=t.dirname(t.dirname(n)),s=t.relative(r,o);return!s||s.startsWith("..")||t.isAbsolute(s)?""===s?"/":function canonicalPath(t){try{return e.native(t)}catch{return t}}(o):`/${s.split(t.sep).join("/")}`}(r.workspace.root,n)))];return s.length>0?s:void 0}function resolveDeepAgentsBackend(e,t,r){if(t?.FilesystemBackend&&r&&0!==r.length)return()=>new t.FilesystemBackend({rootDir:e.workspace.root})}function mergeMiddleware(e,t,o,s=i(e.workspace.runtime.toolGateway)){const a=Array.isArray(o)?o:[],l=scopedInput(e,t),d=new Set;return[n(l,{observedToolIds:d,repeatState:s}),r(l,{repeatState:s}),...u(e.workspace.runtime.retry),...a,c(l)]}function requestScopedRepeatState(e,t){const r=`deepagents.repeat.${t}`,n=e.requestState?.get(r);if(n)return n;const o=i(e.workspace.runtime.toolGateway);return e.requestState&&o&&e.requestState.set(r,o),o}function scopedInput(e,t){return t?{...e,agent:t}:e}function resolveAgentModel(e,t){const r=t.modelRef?e.workspace.models.get(t.modelRef):void 0;return r?p(r):void 0}function resolveAgentRepairModel(e,t,r){const n=r.model;if(isRepairModel(n))return n;if(!t)return;const o=resolveAgentModel(e,t);return isRepairModel(o)?o:void 0}function readDeepAgentsConfig(e){return isRecord(e)?e:{}}function readDeepAgentsStringArray(e,t){const r=isRecord(e)?e:{},n=readDeepAgentsConfig(r.deepagents),o="memory"===t?["memory","memorySources"]:["skills","skillSources"];for(const e of o){const t=readStringArray(n[e]);if(t)return t}return readStringArray(r[t])}function pruneUndefined(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e))}function readString(e){return"string"==typeof e&&e.trim()?e:void 0}function readNumber(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function readBoolean(e){return"boolean"==typeof e?e:void 0}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e):void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function isRepairModel(e){return"object"==typeof e&&null!==e&&"invoke"in e&&"function"==typeof e.invoke}
@@ -0,0 +1,4 @@
1
+ export declare function normalizeArgsRecord(args: unknown): Record<string, unknown>;
2
+ export declare function normalizeWriteTodosArgs(args: unknown): Record<string, unknown>;
3
+ export declare function normalizeFilesystemArgs(toolId: string, args: unknown, workspaceRoot: string): Record<string, unknown>;
4
+ export declare function shallowEqualRecord(left: Record<string, unknown>, right: Record<string, unknown>): boolean;
@@ -0,0 +1 @@
1
+ import r from"node:path";export function normalizeArgsRecord(r){if(isRecord(r))return r;if("string"!=typeof r||!r.trim())return{};try{const t=JSON.parse(r);return isRecord(t)?t:{}}catch{return{}}}export function normalizeWriteTodosArgs(r){if(Array.isArray(r))return{todos:r.map(normalizeTodoItem)};if("string"==typeof r&&r.trim())try{const t=JSON.parse(r);if(Array.isArray(t))return{todos:t.map(normalizeTodoItem)}}catch{return{todos:[{content:r.trim()}]}}const t=normalizeArgsRecord(r),e=readTodoList(t);return e?{...t,todos:e.map(normalizeTodoItem)}:t}export function normalizeFilesystemArgs(r,t,e){const n={...normalizeArgsRecord(t)};return normalizePathField(n,e,"path"),normalizePathField(n,e,"file_path"),normalizePathField(n,e,"pattern","glob"===r),n}export function shallowEqualRecord(r,t){const e=Object.keys(r),n=Object.keys(t);return e.length===n.length&&e.every(e=>r[e]===t[e])}function readTodoList(r){const t=r.todos??r.items??r.tasks??r.plan;if(Array.isArray(t))return t;if("string"==typeof t&&t.trim())try{const r=JSON.parse(t);return Array.isArray(r)?r:isRecord(r)?readTodoList(r):void 0}catch{return}}function normalizeTodoItem(r){if("string"==typeof r&&r.trim())return{content:r.trim()};if(!isRecord(r))return r;const t=readString(r.content)??readString(r.description)??readString(r.task)??readString(r.title)??readString(r.name),e=function normalizeTodoStatus(r){if(!r)return;const t=r.toLowerCase().replaceAll("-","_").replaceAll(" ","_");return"todo"===t||"not_started"===t?"pending":"doing"===t||"in_progress"===t?"in_progress":"done"===t||"complete"===t?"completed":t}(readString(r.status)??readString(r.state));return{...t?{content:t}:{},...e?{status:e}:{}}}function readString(r){return"string"==typeof r&&r.trim()?r.trim():void 0}function normalizePathField(t,e,n,o=!0){const i=o?readString(t[n]):void 0;i&&(t[n]=function workspaceBackendPath(t,e){if(!e.startsWith("/"))return e;if(function isPathInside(t,e){const n=r.relative(t,e);return""===n||!!n&&!n.startsWith("..")&&!r.isAbsolute(n)}(t,e)){const n=r.relative(t,e).split(r.sep).filter(Boolean).join("/");return n?`/${n}`:"/"}return e}(e,i))}function isRecord(r){return"object"==typeof r&&null!==r&&!Array.isArray(r)}
@@ -1,8 +1,10 @@
1
1
  import { ToolMessage } from "@langchain/core/messages";
2
- import type { RuntimeAdapter } from "@stable-harness/core";
3
- import type { WorkspaceAgent } from "@stable-harness/core";
2
+ import type { RuntimeAdapter, WorkspaceAgent } from "@stable-harness/core";
3
+ import { type ToolRepeatState } from "./gateway-tools.js";
4
4
  type AdapterRunInput = Parameters<RuntimeAdapter["run"]>[0];
5
- export declare function createBuiltinToolPolicyMiddleware(input: AdapterRunInput): {
5
+ export declare function createBuiltinToolPolicyMiddleware(input: AdapterRunInput, options?: {
6
+ repeatState?: ToolRepeatState;
7
+ }): {
6
8
  name: string;
7
9
  wrapModelCall(request: {
8
10
  tools?: Array<{
@@ -16,7 +18,10 @@ export declare function validateFilesystemBuiltinCall(input: AdapterRunInput, to
16
18
  id?: string;
17
19
  };
18
20
  }): ToolMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>> | undefined;
19
- export declare function createObserverMiddleware(input: AdapterRunInput): {
21
+ export declare function createObserverMiddleware(input: AdapterRunInput, options?: {
22
+ observedToolIds?: Set<string>;
23
+ repeatState?: ToolRepeatState;
24
+ }): {
20
25
  name: string;
21
26
  wrapToolCall(request: {
22
27
  toolCall?: {