stable-harness 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +1 -1
- package/docs/guides/index.md +2 -0
- package/docs/guides/operator-runbook.md +6 -0
- package/docs/guides/quality-gates.md +80 -0
- package/package.json +3 -3
- package/packages/adapter-deepagents/dist/src/index.d.ts +1 -0
- package/packages/adapter-deepagents/dist/src/index.js +1 -1
- package/packages/adapter-deepagents/dist/src/internal/builtin-args.d.ts +1 -0
- package/packages/adapter-deepagents/dist/src/internal/builtin-args.js +1 -1
- package/packages/adapter-deepagents/dist/src/internal/builtin-call-repair.d.ts +9 -0
- package/packages/adapter-deepagents/dist/src/internal/builtin-call-repair.js +1 -0
- package/packages/adapter-deepagents/dist/src/internal/builtin-tool-policy.js +1 -1
- package/packages/adapter-deepagents/dist/src/internal/gateway/tool-evidence.d.ts +10 -0
- package/packages/adapter-deepagents/dist/src/internal/gateway/tool-evidence.js +1 -0
- package/packages/adapter-deepagents/dist/src/internal/gateway-tools.js +1 -1
- package/packages/adapter-deepagents/package.json +1 -1
- package/packages/adapter-langgraph/dist/src/skill-providers.js +1 -1
- package/packages/cli/dist/src/cli.js +1 -1
- package/packages/core/dist/execution-contract.js +1 -1
- package/packages/core/dist/index.d.ts +2 -0
- package/packages/core/dist/index.js +1 -1
- package/packages/core/dist/quality/event-evidence.d.ts +5 -0
- package/packages/core/dist/quality/event-evidence.js +1 -0
- package/packages/core/dist/quality/execution-review.d.ts +2 -0
- package/packages/core/dist/quality/execution-review.js +1 -0
- package/packages/core/dist/quality/index.d.ts +7 -0
- package/packages/core/dist/quality/index.js +1 -0
- package/packages/core/dist/quality/llm-review.d.ts +7 -0
- package/packages/core/dist/quality/llm-review.js +1 -0
- package/packages/core/dist/quality/planning-review.d.ts +2 -0
- package/packages/core/dist/quality/planning-review.js +1 -0
- package/packages/core/dist/quality/profile.d.ts +3 -0
- package/packages/core/dist/quality/profile.js +1 -0
- package/packages/core/dist/quality/recovery-policy.d.ts +11 -0
- package/packages/core/dist/quality/recovery-policy.js +1 -0
- package/packages/core/dist/quality/runtime.d.ts +13 -0
- package/packages/core/dist/quality/runtime.js +1 -0
- package/packages/core/dist/quality/types.d.ts +47 -0
- package/packages/core/dist/quality/types.js +1 -0
- package/packages/core/dist/runtime/direct-tool-call.js +1 -1
- package/packages/core/dist/runtime/events.d.ts +30 -0
- package/packages/core/dist/runtime/selection-repair.d.ts +14 -0
- package/packages/core/dist/runtime/selection-repair.js +1 -0
- package/packages/core/dist/runtime/tool-gateway.d.ts +2 -0
- package/packages/core/dist/runtime.d.ts +2 -0
- package/packages/core/dist/runtime.js +1 -1
- package/packages/core/dist/workflows/runtime.js +1 -1
- package/packages/core/dist/workspace/types.d.ts +9 -0
- package/packages/core/package.json +1 -0
- package/packages/tool-gateway/dist/src/module-loader.js +1 -1
- package/packages/tool-gateway/dist/src/types.d.ts +2 -0
- package/packages/tool-gateway/package.json +1 -1
- package/packages/workspace-yaml/dist/documents.js +1 -1
- package/packages/workspace-yaml/dist/loader.js +1 -1
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createDeepAgentsAdapter as
|
|
1
|
+
import{createBackendModel as e,createDeepAgentsAdapter as r}from"@stable-harness/adapter-deepagents";import{createLangGraphRuntimeAdapter as t,createLangGraphWorkflowAdapter as a,createRegistrySkillResolverProvider as o}from"@stable-harness/adapter-langgraph";import{createStableHarnessRuntime as n}from"@stable-harness/core";import{createModuleToolGateway as i}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):n(e)}export async function createStableRuntime(e){const r=await s(e.workspaceRoot),t=e.toolGateway??await i({tools:r.tools.values()});return n({workspace:r,toolGateway:t,qualityReviewModel:createQualityReviewModel(r),adapters:e.adapters??createRuntimeAdapters(r,e),workflowAdapters:e.workflowAdapters??createWorkflowAdapters(r,e)})}function createQualityReviewModel(r){const t=function readQualityModelRef(e){const r=isRecord(e)?e:{};return readString((isRecord(r.reviewer)?r.reviewer:r).modelRef)}(r.runtime.quality),a=t?r.models.get(t):void 0,o=a?e(a):void 0;return function isQualityReviewModel(e){return isRecord(e)&&"function"==typeof e.invoke}(o)?o:void 0}export async function requestStableRuntime(e,r){return e.request(r)}function createRuntimeAdapters(e,a){const o={deepagents:({policy:e})=>r(e.config?{config:e.config}:{}),langgraph:({policy:e})=>t({...readLangGraphOptions(e.config),name:e.name}),...a.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}))}(e);return n.map(r=>{const t=o[r.name];if(t)return t({policy:r,workspace:e});throw new Error(`Unsupported runtime adapter: ${r.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 t=readString(r.provider)??readString(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 o({..."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)}
|
package/dist/tools.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ export declare function tool(input: {
|
|
|
2
2
|
name?: string;
|
|
3
3
|
description?: string;
|
|
4
4
|
schema?: unknown;
|
|
5
|
-
func?: (args: unknown) => unknown | Promise<unknown>;
|
|
6
|
-
invoke?: (args: unknown) => unknown | Promise<unknown>;
|
|
5
|
+
func?: (args: unknown, context?: unknown) => unknown | Promise<unknown>;
|
|
6
|
+
invoke?: (args: unknown, context?: unknown) => unknown | Promise<unknown>;
|
|
7
7
|
}): {
|
|
8
8
|
name: string | undefined;
|
|
9
9
|
description: string | undefined;
|
|
10
10
|
schema: unknown;
|
|
11
|
-
invoke(args: unknown): Promise<unknown>;
|
|
11
|
+
invoke(args: unknown, context?: unknown): Promise<unknown>;
|
|
12
12
|
};
|
package/dist/tools.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function tool(n){return{name:n.name,description:n.description,schema:n.schema,async invoke(o){if(n.invoke)return n.invoke(o);if(n.func)return n.func(o);throw new Error(`Tool ${n.name??"unknown"} has no function`)}}}
|
|
1
|
+
export function tool(n){return{name:n.name,description:n.description,schema:n.schema,async invoke(o,e){if(n.invoke)return n.invoke(o,e);if(n.func)return n.func(o,e);throw new Error(`Tool ${n.name??"unknown"} has no function`)}}}
|
package/docs/guides/index.md
CHANGED
|
@@ -12,6 +12,8 @@ embed it, operate it, or explain why it exists.
|
|
|
12
12
|
workflows, memory, and protocol exposure in YAML.
|
|
13
13
|
- [Integration guide](integration-guide.md): embed the runtime inside an app,
|
|
14
14
|
run it through CLI, or expose it through HTTP/OpenAI-compatible clients.
|
|
15
|
+
- [Quality gates](quality-gates.md): enable plan review, execution evidence
|
|
16
|
+
review, and configured recovery loops without replacing upstream planning.
|
|
15
17
|
- [Operator runbook](operator-runbook.md): validate a workspace, inspect
|
|
16
18
|
events, run smoke tests, and keep the runtime operable.
|
|
17
19
|
|
|
@@ -20,6 +20,12 @@ npm run release:pack
|
|
|
20
20
|
npm run release:smoke
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
Stable Harness publishes compiled npm artifacts only. The package must not
|
|
24
|
+
include TypeScript sources, source maps, source-map content markers, tests,
|
|
25
|
+
scripts, examples, benchmarks, or development-only source directories. The
|
|
26
|
+
release package may include compiled JavaScript, declaration files, package
|
|
27
|
+
metadata, docs, README, NOTICE, and LICENSE.
|
|
28
|
+
|
|
23
29
|
`release:smoke` installs the packed package into an isolated temporary project,
|
|
24
30
|
initializes a workspace, and runs a CLI tool call. It catches missing published
|
|
25
31
|
dependencies that monorepo tests can hide.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Quality Gates
|
|
2
|
+
|
|
3
|
+
Stable Harness can supervise agent work without replacing the backend agent
|
|
4
|
+
framework. Quality gates are runtime control-plane checks over plan evidence,
|
|
5
|
+
execution evidence, blockers, and recovery loops.
|
|
6
|
+
|
|
7
|
+
The normal user-facing configuration is intentionally small:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
runtime:
|
|
11
|
+
quality: balanced
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Profiles:
|
|
15
|
+
|
|
16
|
+
- `fast`: keep latency low. Run lightweight final-output checks and blocker
|
|
17
|
+
handling only when explicitly configured.
|
|
18
|
+
- `balanced`: require upstream planning evidence for agents with declared
|
|
19
|
+
tools, skills, or subagents; review final execution evidence; allow limited
|
|
20
|
+
recovery.
|
|
21
|
+
- `strict`: use the balanced checks and also require successful tool or
|
|
22
|
+
delegated-task evidence before final synthesis.
|
|
23
|
+
|
|
24
|
+
Advanced configuration can override individual pieces:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
runtime:
|
|
28
|
+
quality:
|
|
29
|
+
profile: balanced
|
|
30
|
+
reviewer:
|
|
31
|
+
modelRef: local-dev
|
|
32
|
+
prompts:
|
|
33
|
+
planning: |
|
|
34
|
+
Review whether the plan is executable, complete, and grounded in the
|
|
35
|
+
declared tools, skills, and subagents. Return only the JSON verdict.
|
|
36
|
+
execution: |
|
|
37
|
+
Review whether the final answer is supported by executed evidence and
|
|
38
|
+
whether the agent should continue with ReAct. Return only the JSON
|
|
39
|
+
verdict.
|
|
40
|
+
executionReview:
|
|
41
|
+
requireToolEvidence: true
|
|
42
|
+
recovery:
|
|
43
|
+
maxLoops: 2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Runtime Boundary
|
|
47
|
+
|
|
48
|
+
Quality gates do not create a second planner. Planning still belongs to the
|
|
49
|
+
upstream backend, such as DeepAgents. Stable Harness only observes upstream
|
|
50
|
+
planning events, reviews generic evidence quality, and feeds structured gaps
|
|
51
|
+
back into the same upstream agent loop.
|
|
52
|
+
|
|
53
|
+
When `reviewer.modelRef` is configured, Stable Harness uses that LLM as the
|
|
54
|
+
primary reviewer. The model must return JSON:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"verdict": "pass | revise_plan | continue_react | blocked",
|
|
59
|
+
"issues": [
|
|
60
|
+
{ "code": "missing_evidence", "message": "Explain the gap.", "recoverable": true }
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The built-in deterministic reviewers remain as fallback and cover:
|
|
66
|
+
|
|
67
|
+
- missing upstream planning evidence
|
|
68
|
+
- empty final answers
|
|
69
|
+
- missing successful tool evidence when required
|
|
70
|
+
- control blockers such as approval blocks, invalid input, failed argument
|
|
71
|
+
repair, or repeated-tool limits
|
|
72
|
+
|
|
73
|
+
If plan review fails, the runtime asks the agent to revise the plan using the
|
|
74
|
+
declared inventory. If execution review fails, the runtime asks the agent to
|
|
75
|
+
continue from the current state with ReAct: inspect the evidence gap, choose the
|
|
76
|
+
next declared tool or subagent action, execute it, and synthesize from evidence.
|
|
77
|
+
|
|
78
|
+
BetterCall remains focused on individual tool-call reliability. Quality gates
|
|
79
|
+
consume tool and repair diagnostics as evidence, but they live in Stable Harness
|
|
80
|
+
because they govern the run lifecycle rather than a single tool call.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stable-harness",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Stable application runtime and operator control plane for agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"benchmark:tool-guard:matrix": "node scripts/benchmarks/tool-argument-guard-matrix.mjs",
|
|
63
63
|
"test:langmem:sqlite:e2e": "node scripts/run-langmem-sqlite-e2e.mjs",
|
|
64
64
|
"validate:workspace": "node scripts/validate-workspace.mjs",
|
|
65
|
-
"test": "rm -rf dist/test && tsc -b test/tsconfig.json && node --test dist/test/*.test.js dist/test/adapter/*.test.js dist/test/compat/*.test.js dist/test/memory/*.test.js dist/test/protocol/*.test.js dist/test/retry/*.test.js dist/test/runtime/*.test.js dist/test/sdk/*.test.js dist/test/workspace/*.test.js",
|
|
65
|
+
"test": "rm -rf dist/test && tsc -b test/tsconfig.json && node --test dist/test/*.test.js dist/test/adapter/*.test.js dist/test/adapter/*/*.test.js dist/test/compat/*.test.js dist/test/memory/*.test.js dist/test/protocol/*.test.js dist/test/retry/*.test.js dist/test/runtime/*.test.js dist/test/runtime/*/*.test.js dist/test/sdk/*.test.js dist/test/workspace/*.test.js",
|
|
66
66
|
"test:langmem:maintenance:e2e": "node scripts/run-langmem-maintenance-e2e.mjs",
|
|
67
67
|
"test:skill-mining:e2e": "node scripts/run-skill-candidate-mining-e2e.mjs",
|
|
68
68
|
"prepublishOnly": "npm run build && npm run release:check-package",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"packages/*"
|
|
79
79
|
],
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@botbotgo/better-call": "^0.1.
|
|
81
|
+
"@botbotgo/better-call": "^0.1.58",
|
|
82
82
|
"@langchain/core": "^1.1.43",
|
|
83
83
|
"@langchain/langgraph": "^1.3.0",
|
|
84
84
|
"@langchain/langgraph-api": "^1.2.1",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { createDeepAgentsAdapter } from "./adapter.js";
|
|
2
2
|
export { createDeepAgentsMemoryMaintenanceTarget, resolveDeepAgentsNativeMemories } from "./memory.js";
|
|
3
|
+
export { createBackendModel } from "./model-providers.js";
|
|
3
4
|
export type { DeepAgentFactory, DeepAgentsAdapterRunner } from "./types.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{createDeepAgentsAdapter}from"./adapter.js";export{createDeepAgentsMemoryMaintenanceTarget,resolveDeepAgentsNativeMemories}from"./memory.js";
|
|
1
|
+
export{createDeepAgentsAdapter}from"./adapter.js";export{createDeepAgentsMemoryMaintenanceTarget,resolveDeepAgentsNativeMemories}from"./memory.js";export{createBackendModel}from"./model-providers.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function normalizeArgsRecord(args: unknown): Record<string, unknown>;
|
|
2
2
|
export declare function normalizeWriteTodosArgs(args: unknown): Record<string, unknown>;
|
|
3
3
|
export declare function normalizeFilesystemArgs(toolId: string, args: unknown, workspaceRoot: string): Record<string, unknown>;
|
|
4
|
+
export declare function normalizeExecuteArgs(args: unknown, workspaceRoot: string): Record<string, unknown>;
|
|
4
5
|
export declare function shallowEqualRecord(left: Record<string, unknown>, right: Record<string, unknown>): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import t from"node:path";export function normalizeArgsRecord(t){if(isRecord(t))return t;if("string"!=typeof t||!t.trim())return{};try{const r=JSON.parse(t);return isRecord(r)?r:{}}catch{return{}}}export function normalizeWriteTodosArgs(t){if(Array.isArray(t))return{todos:t.map(normalizeTodoItem)};if("string"==typeof t&&t.trim())try{const r=JSON.parse(t);if(Array.isArray(r))return{todos:r.map(normalizeTodoItem)}}catch{return{todos:[{content:t.trim()}]}}const r=normalizeArgsRecord(t),e=readTodoList(r);return e?{...r,todos:e.map(normalizeTodoItem)}:r}export function normalizeFilesystemArgs(t,r,e){const i={...normalizeArgsRecord(r)};return copyFirstStringAlias(i,"path",function filesystemPathAliases(t){return"grep"===t?["file","filename","filepath","filePath","directory","dir"]:["file_path","file","filename","filepath","filePath","target","directory","dir"]}(t)),copyFirstStringAlias(i,"pattern",function filesystemPatternAliases(t){return"glob"!==t?[]:["query","regex","search"]}(t)),copyFirstStringAlias(i,"content","write_file"===t?["text","body"]:[]),copyFirstStringAlias(i,"old_string","edit_file"===t?["oldText","old","replace","from"]:[]),copyFirstStringAlias(i,"new_string","edit_file"===t?["newText","new","with","to"]:[]),normalizePathField(i,e,"path"),normalizePathField(i,e,"file_path"),normalizePathField(i,e,"pattern","glob"===t),i}export function normalizeExecuteArgs(t,r){const e={...normalizeArgsRecord(t)};return copyFirstStringAlias(e,"command",["cmd","shell","input"]),normalizePathField(e,r,"cwd"),e}export function shallowEqualRecord(t,r){const e=Object.keys(t),i=Object.keys(r);return e.length===i.length&&e.every(e=>t[e]===r[e])}function readTodoList(t){const r=t.todos??t.items??t.tasks??t.plan;if(Array.isArray(r))return r;if(isRecord(r))return readTodoList(r)??[r];if("string"==typeof r&&r.trim())try{const t=JSON.parse(r);return Array.isArray(t)?t:isRecord(t)?readTodoList(t):void 0}catch{return}}function normalizeTodoItem(t){if("string"==typeof t&&t.trim())return{content:t.trim()};if(!isRecord(t))return t;const r=readString(t.content)??readString(t.description)??readString(t.task)??readString(t.step)??readString(t.gap)??readString(t.evidenceGap)??readString(t.evidence_gap)??readString(t.action)??readString(t.title)??readString(t.name),e=function normalizeTodoStatus(t){if(!t)return;const r=t.toLowerCase().replaceAll("-","_").replaceAll(" ","_");return"todo"===r||"not_started"===r||"planned"===r||"blocked"===r?"pending":"doing"===r||"in_progress"===r||"partial"===r?"in_progress":"done"===r||"complete"===r||"filled"===r?"completed":r}(readString(t.status)??readString(t.state));return{...r?{content:r}:{},...e?{status:e}:{}}}function readString(t){return"string"==typeof t&&t.trim()?t.trim():void 0}function copyFirstStringAlias(t,r,e){if(!readString(t[r]))for(const i of e){const e=readString(t[i]);if(e)return void(t[r]=e)}}function normalizePathField(r,e,i,n=!0){const o=n?readString(r[i]):void 0;o&&(r[i]=function workspaceBackendPath(r,e){if(!e.startsWith("/"))return e;if(function isPathInside(r,e){const i=t.relative(r,e);return""===i||!!i&&!i.startsWith("..")&&!t.isAbsolute(i)}(r,e)){const i=t.relative(r,e).split(t.sep).filter(Boolean).join("/");return i?`/${i}`:"/"}return e}(e,o))}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{repairCallSelection as t}from"@botbotgo/better-call";import{normalizeArgsRecord as e,normalizeExecuteArgs as r,normalizeFilesystemArgs as o,normalizeWriteTodosArgs as i,shallowEqualRecord as s}from"./builtin-args.js";const a=new Set(["ls","read_file","write_file","edit_file","glob","grep"]);export async function repairBuiltinToolRequest(e){const r=normalizeBuiltinArgs(e.toolId,e.request.toolCall?.args,e.workspaceRoot),o=function builtinCandidate(t){const e=n[t];return e?{id:t,description:`DeepAgents builtin tool ${t}`,schema:e}:void 0}(e.toolId);if(!o)return updateRequestArgs(e.request,e.request.toolCall?.args,r);const i=await t({call:{id:e.toolId,args:r},candidates:[o],mode:"repair"}),s=i.ok?normalizeBuiltinArgs(e.toolId,i.args,e.workspaceRoot):r;return updateRequestArgs(e.request,e.request.toolCall?.args,s)}function normalizeBuiltinArgs(t,s,n){return"write_todos"===t?i(s):"read_todos"===t||"task"===t?e(s):"execute"===t?r(s,n):a.has(t)?o(t,s,n):e(s)}function updateRequestArgs(t,e,r){return function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}(e)&&s(e,r)?t:{...t,toolCall:{...t.toolCall,args:r}}}const n={write_todos:objectSchema({todos:{type:"array",items:{type:"object",additionalProperties:!0}}}),read_todos:objectSchema({}),task:objectSchema({subagent_type:{type:"string"},subagentType:{type:"string"},description:{type:"string"},task:{type:"string"}}),execute:objectSchema({command:{type:"string"},cwd:{type:"string"},timeoutMs:{type:"number"}}),ls:objectSchema({path:{type:"string"}}),read_file:objectSchema({path:{type:"string"},file_path:{type:"string"}}),write_file:objectSchema({path:{type:"string"},file_path:{type:"string"},content:{type:"string"}}),edit_file:objectSchema({path:{type:"string"},file_path:{type:"string"},old_string:{type:"string"},new_string:{type:"string"}}),glob:objectSchema({path:{type:"string"},pattern:{type:"string"}}),grep:objectSchema({path:{type:"string"},pattern:{type:"string"}})};function objectSchema(t){return{type:"object",properties:t,additionalProperties:!0}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{realpathSync as e}from"node:fs";import{repairCallSelection as t}from"@botbotgo/better-call";import{ToolMessage as o}from"@langchain/core/messages";import{normalizeArgsRecord as n
|
|
1
|
+
import{realpathSync as e}from"node:fs";import{repairCallSelection as t}from"@botbotgo/better-call";import{ToolMessage as o}from"@langchain/core/messages";import{normalizeArgsRecord as n}from"./builtin-args.js";import{repairBuiltinToolRequest as r}from"./builtin-call-repair.js";import{afterToolInvoke as i,beforeToolInvoke as a,createToolRepeatState as s,stringifyDeepAgentResult as l,toolControlProjection as u}from"./gateway-tools.js";import{observedToolEvidence as c,recordObservedToolEvidence as d}from"./gateway/tool-evidence.js";import{validateSkillFileBuiltinCall as g}from"./skill-file-policy.js";import{filterRepeatLimitedTools as p}from"./tool-repeat-visibility.js";import{traceProjectionForBuiltinTool as f}from"./trace-projection.js";const m=new Set(["ls","read_file","write_file","edit_file","glob","grep"]),h=new Set(["write_todos","read_todos","task","execute",...m]);export function createBuiltinToolPolicyMiddleware(e,t={}){return{name:"StableHarnessBuiltinToolPolicy",async wrapModelCall(o,n){const r=Array.isArray(o.tools)?p(o.tools.filter(t=>!function hasHiddenBuiltins(e){return isFilesystemDisabled(e)||!isTaskVisible(e)}(e)||isModelVisibleBuiltin(e,t.name)),t.repeatState):o.tools,i=function normalizeToolChoice(e,t,o){return"required"===t?o&&o.length>0?t:"auto":function isForcedHiddenTool(e,t){return"string"==typeof t?.function?.name&&!isModelVisibleBuiltin(e,t.function.name)}(e,t)?"auto":t}(e,o.toolChoice,r);return n({...o,tools:r,toolChoice:i})}}}export function validateFilesystemBuiltinCall(e,t,n){if(isFilesystemDisabled(e)&&m.has(t))return new o({tool_call_id:n.toolCall?.id??`stable-harness-${t}-policy`,name:t,content:`Filesystem builtin tool '${t}' is disabled for this agent. Do not retry filesystem tools; use the agent's registered workspace tools and already collected evidence instead.`})}export function createObserverMiddleware(e,u={}){const d=u.repeatState??s(e.workspace.runtime.toolGateway);return{name:"StableHarnessObserver",async wrapToolCall(s,p){const f=s.toolCall?.name;if(!f||!h.has(f))return p(s);const m=await r({toolId:f,request:s,workspaceRoot:e.workspace.root});emitToolEvent(e,f,"agent.tool.start",m.toolCall?.args);const y="task"===f?await async function repairTaskCall(e,r){const i=function allowedTaskTypes(e){const t=readConfigRecord(e.agent.config,"deepagents");if(!0===t?.generalPurposeAgent)return;const o=readConfigRecord(e.agent.config,"builtinTools")?.modelExposed;return!1===o?[]:Array.isArray(o)?o.includes("task")?e.agent.subagents:[]:0===e.agent.subagents.length?[]:void 0}(e);if(void 0===i)return{request:r};const a=n(r.toolCall?.args),s=function readTaskSubagentType(e){const t=isRecord(e)?e:{};return readString(t.subagent_type)??readString(t.subagentType)}(a);if(s&&i.includes(s))return{request:r};const l=await t({call:{id:s,args:a},candidates:taskCallCandidates(e,i),mode:"repair"});if(l.ok){const e={...a,...l.args,subagent_type:l.candidateId};return{request:{...r,toolCall:{...r.toolCall,args:e}}}}const u=s?`: ${s}`:"",c=i.length>0?i.join(", "):"none";return{request:r,blocked:new o({tool_call_id:r.toolCall?.id??"stable-harness-task-policy",name:"task",status:"error",content:[`Task delegation target is not in the workspace inventory${u}. Allowed task targets: ${c}.`,"Retry with an allowed target only when that target semantically owns the original user request.","Do not substitute another specialist just to continue the same evidence need; synthesize from already collected evidence when no allowed target is a semantic match."].join(" ")})}}(e,m):{request:m},b=y.request,v=y.blocked;if(v)return emitToolEvent(e,f,"agent.tool.result",b.toolCall?.args,{output:v.content}),v;const w=validateFilesystemBuiltinCall(e,f,b);if(w)return emitToolEvent(e,f,"agent.tool.result",b.toolCall?.args,{output:w.content}),w;const k=g(e,f,b);if(k)return emitToolEvent(e,f,"agent.tool.result",b.toolCall?.args,{output:k.content}),k;try{const t=d?a(f,b.toolCall?.args,d):void 0;if(t)return emitToolEvent(e,f,"agent.tool.result",b.toolCall?.args,{output:t.eventOutput}),builtinToolMessage(s,f,t.modelOutput);const o=await p(b),n=function observedToolOutput(e,t,o){return"write_todos"===e?JSON.stringify({status:"recorded",args:t.toolCall?.args}):l(o)}(f,b,o),r=d?i(f,b.toolCall?.args,n,o,d):{};return emitToolEvent(e,f,"agent.tool.result",b.toolCall?.args,{output:r.eventOutput??n}),u.observedToolIds?.add(f),void 0===r.modelOutput?o:builtinToolMessage(s,f,r.modelOutput)}catch(t){const n=function recoverableBuiltinToolError(e,t,n,r){const i=formatError(r);if("task"===t&&/repeat limit reached for tool/iu.test(i)){const t=function formatObservedEvidence(e){const t=c(e);if(0===t.length)return"";const o=t.map(e=>[`Agent: ${e.agentId}`,`Tool: ${e.toolId}`,e.output].join("\n")).join("\n\n---\n\n");return o.length>12e3?`${o.slice(0,12e3)}\n[truncated]`:o}(e);return new o({tool_call_id:n.toolCall?.id??"stable-harness-task-repeat-limit",name:"task",content:JSON.stringify({status:"delegated_task_repeat_limit",finalizationRequired:!0,instruction:"The delegated agent reached a configured tool repeat limit. Stop delegating this evidence need and do not send a synthesis task to another subagent for the same need. Finalize only from observedEvidence and other evidence that is already visible in this run. If the visible evidence is insufficient for a requested claim, report an explicit blocker or evidence gap instead of estimating, inventing, or using generic knowledge.",...t?{observedEvidence:t}:{},error:previewError(i)})})}if(/Received tool input did not match expected schema|Invalid input:/iu.test(i))return new o({tool_call_id:n.toolCall?.id??`stable-harness-${t}-argument-error`,name:t,status:"error",content:JSON.stringify({status:"tool_argument_error",toolId:t,instruction:"The upstream builtin tool rejected these arguments. Fix the tool arguments according to the tool schema, or choose a more appropriate available tool.",error:previewError(i)})})}(e,f,s,t);if(n)return emitToolEvent(e,f,"agent.tool.result",m.toolCall?.args,{output:n.content}),n;throw emitToolEvent(e,f,"agent.tool.result",m.toolCall?.args,{error:formatError(t)}),t}}}}function builtinToolMessage(e,t,n){return new o({tool_call_id:e.toolCall?.id??`stable-harness-${t}-repeat-guard`,name:t,content:n})}export function resolveFilesystemPermissions(e,t){const o=readConfigRecord(t?.config,"builtinTools"),n=[];if(n.push(...function skillReadPermissions(e,t){const o=[...new Set((t?.skills??[]).flatMap(t=>function skillReadPaths(e,t){return t?[...new Set([t,canonicalPath(t),backendSkillPath(e,t)])].flatMap(e=>function skillReadPathCandidates(e){const t=e.replace(/\/+$/u,""),o=t.endsWith("/SKILL.md")?t.slice(0,-9):t,n=function parentPath(e){const t=e.lastIndexOf("/");return t>0?e.slice(0,t):void 0}(o);return[t,o,`${o}/**`,...n?[n,`${n}/**`]:[]]}(e)):[]}(e.workspace.root,e.workspace.skills.get(t)?.path)).filter(e=>e.startsWith("/")))];return o.length>0?[{operations:["read"],paths:o,mode:"allow"}]:[]}(e,t)),!1!==o?.filesystem){if(deepagentsMemoryWritable(e))return n.length>0?n:void 0}else n.push({operations:["read"],paths:["/memories/**"],mode:"allow"}),n.push({operations:["read","write"],paths:["/**"],mode:"deny"});return deepagentsMemoryWritable(e)||n.push({operations:["write"],paths:["/memories/**"],mode:"deny"}),n.length>0?n:void 0}function emitToolEvent(e,t,o,n,r={}){"string"==typeof r.output&&d(e,e.agent.id,t,r.output),e.emit({type:"runtime.adapter.event",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,event:{adapter:"deepagents",phase:o,toolId:t,..."agent.tool.start"===o?{args:n}:{},...r,..."string"==typeof r.output?u(r.output):{},...f(t,o,n)}})}function backendSkillPath(e,t){const o=function pathRelative(e,t){const o=e.split("/").filter(Boolean),n=t.split("/").filter(Boolean);if(!(n.length<o.length)){for(let e=0;e<o.length;e+=1)if(o[e]!==n[e])return;return n.slice(o.length).join("/")}}(e,t);return void 0===o?t:`/${o.split("/").filter(Boolean).join("/")}`}function canonicalPath(t){try{return e.native(t)}catch{return t}}function deepagentsMemoryWritable(e){const t=readConfigRecord(e.workspace.runtime.memory,"deepagentsMem");return!1!==t?.write}function taskCallCandidates(e,t){return t.map(t=>({id:t,description:e.workspace.agents.get(t)?.description,schema:{type:"object",properties:{subagent_type:{type:"string"},subagentType:{type:"string"},description:{type:"string"}},required:["description"],additionalProperties:!0}}))}function isFilesystemDisabled(e){const t=readConfigRecord(e.agent.config,"builtinTools");return!1===t?.filesystem}function isTaskVisible(e){const t=readConfigRecord(e.agent.config,"builtinTools")?.modelExposed;return!1!==t&&(!Array.isArray(t)||t.includes("task"))}function isModelVisibleBuiltin(e,t){return(!isFilesystemDisabled(e)||!function isFilesystemTool(e){return"string"==typeof e&&m.has(e)}(t))&&("task"!==t||isTaskVisible(e))}function readConfigRecord(e,t){const o=isRecord(e)?e:{};return isRecord(o[t])?o[t]:void 0}function readString(e){return"string"==typeof e&&e.trim()?e:void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function formatError(e){return e instanceof Error?e.message:String(e)}function previewError(e){const t=e.replace(/\s+/gu," ").trim();return t.length>800?`${t.slice(0,797)}...`:t}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RuntimeAdapter } from "@stable-harness/core";
|
|
2
|
+
export type ObservedToolEvidence = {
|
|
3
|
+
agentId: string;
|
|
4
|
+
toolId: string;
|
|
5
|
+
output: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function observedToolEvidence(input: Parameters<RuntimeAdapter["run"]>[0]): ObservedToolEvidence[];
|
|
8
|
+
export declare function recordObservedToolEvidence(input: Parameters<RuntimeAdapter["run"]>[0], agentId: string, toolId: string, output: string): void;
|
|
9
|
+
export declare function isControlOnlyOutput(output: string): boolean;
|
|
10
|
+
export declare function isSuccessfulEvidenceOutput(output: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e="deepagents.observedToolEvidence";export function observedToolEvidence(t){const r=t.requestState?.get(e);return Array.isArray(r)?r:[]}export function recordObservedToolEvidence(t,r,o,n){if(!t.requestState||!isSuccessfulEvidenceOutput(n)||isControlOnlyOutput(n))return;const s=observedToolEvidence(t);t.requestState.set(e,[...s,{agentId:r,toolId:o,output:n}].slice(-12))}export function isControlOnlyOutput(e){return/"status"\s*:\s*"(?:duplicate_tool_call|repeated_tool_call_limit|tool_argument_error|delegated_task_repeat_limit|invalid_input|blocked|failed|error|plan_required|dependency_required)"|Status:\s*(?:invalid_input|blocked|failed|error|plan_required|dependency_required)\b|Filesystem builtin tool .* is disabled/iu.test(e)}export function isSuccessfulEvidenceOutput(e){const t=function parseJsonRecord(e){try{const t=JSON.parse(e);return function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}(t)?t:void 0}catch{return}}(e),r="string"==typeof t?.status?t.status:function readTextStatus(e){return String(e).match(/^Status:\s*([A-Za-z0-9_-]+)/imu)?.[1]}(e);return!r||/^(?:completed|success|ok|recorded)$/iu.test(r)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ToolMessage as
|
|
1
|
+
import{ToolMessage as e}from"@langchain/core/messages";import{tool as t}from"@langchain/core/tools";import{isSuccessfulEvidenceOutput as o,observedToolEvidence as r,recordObservedToolEvidence as n}from"./gateway/tool-evidence.js";export function buildGatewayTools(o,n,s,a,i=createToolRepeatState(o.workspace.runtime.toolGateway)){return o.toolGateway?s.flatMap(s=>{const l=o.toolGateway?.get(s);if(!l)return[];const u=o.workspace.tools.get(s),c=u?.schema??l.schema;return[t(async t=>async function invokeGuardedGatewayTool(t){emitToolResult(t.input,t.agentId,t.toolId,void 0);const o=function missingRequiredPlanContent(e,t){const o=readRecord(e.agent.config.executionContract);if(!0!==o?.requiresPlan)return"";const n=readStringArray(o.planEvidenceTools);if(0===n.length||n.includes(t))return"";const s=new Set(r(e).map(e=>e.toolId));return n.some(e=>s.has(e))?"":["Status: plan_required",`Evidence tool: ${t}`,`Blocker: execution contract requires a planning checkpoint from one of: ${n.join(", ")} before evidence tools run.`,"Instruction: call the planning tool first, then retry this atomic evidence tool with repaired arguments."].join("\n")}(t.input,t.toolId);if(o)return emitToolResult(t.input,t.agentId,t.toolId,o),o;const n=function missingToolDependencyContent(e,t){const o=readRecord(e.agent.config.executionContract),n=readStringArray(readRecord(o.toolDependencies)[t]);if(0===n.length)return"";const s=new Set(r(e).map(e=>e.toolId)),a=n.filter(e=>!s.has(e));return 0===a.length?"":["Status: dependency_required",`Evidence tool: ${t}`,`Blocker: this atomic evidence tool requires completed dependency evidence from: ${a.join(", ")}.`,"Instruction: complete the dependency tool first, evaluate it, then retry this atomic evidence tool."].join("\n")}(t.input,t.toolId);if(n)return emitToolResult(t.input,t.agentId,t.toolId,n),n;const s=t.repeatState?beforeToolInvoke(t.toolId,t.args,t.repeatState):void 0;if(s)return emitToolResult(t.input,t.agentId,t.toolId,s.eventOutput),s.modelOutput;const a=await async function invokeGatewayTool(t,o,r,n,s){try{return await t.toolGateway.invoke({toolId:r,args:n,repairModel:s,context:{workspaceRoot:t.workspace.root,requestId:t.requestId,sessionId:t.sessionId,agentId:o,requestInput:t.request.input,observedEvidence:formatObservedEvidenceForToolContext(t)}})}catch(o){if(function isToolArgumentValidationError(e){return e instanceof Error&&"ToolArgumentValidationError"===e.name&&"string"==typeof e.toolId&&Array.isArray(e.issues)}(o))return new e({tool_call_id:`stable-harness-${r}-argument-guard`,name:r,status:"error",content:formatToolArgumentError(o)});if(t.workspace.runtime.retry?.tools?.enabled)throw o;return new e({tool_call_id:`stable-harness-${r}-execution-error`,name:r,status:"error",content:JSON.stringify({error:"tool_execution_failed",toolId:r,message:formatError(o),retry:"Use the error as evidence, adjust the tool arguments if possible, or return a final answer with the blocker."})})}}(t.input,t.agentId,t.toolId,t.args,t.repairModel),i=a instanceof e?String(a.content):stringifyDeepAgentResult(a.output),l=t.repeatState?afterToolInvoke(t.toolId,t.args,i,a,t.repeatState):{};return emitToolResult(t.input,t.agentId,t.toolId,l.eventOutput??i),void 0!==l.modelOutput?l.modelOutput:a instanceof e?a:i}({input:o,agentId:n,toolId:s,args:t,repairModel:a,repeatState:i}),{name:s,description:buildToolDescription(u?.description??l.description??s,c,o.workspace.runtime.toolGateway,s),schema:{type:"object",additionalProperties:!0}})]}):[]}export function createToolRepeatState(e){if(function repeatGuardEnabled(e){return!0===repeatGuardConfig(e).enabled}(e))return{successfulCalls:new Map,duplicateCallCounts:new Map,latestSuccessfulOutputByTool:new Map,successfulToolCounts:new Map,toolCallCounts:new Map,maxDuplicateCallsPerTool:readPositiveInteger(repeatGuardConfig(e).maxDuplicateCallsPerTool)??3,maxCallsPerTool:readPositiveInteger(repeatGuardConfig(e).maxCallsPerTool),maxSuccessfulCallsPerTool:readPositiveInteger(repeatGuardConfig(e).maxSuccessfulCallsPerTool),maxCallsByTool:readPositiveIntegerMap(repeatGuardConfig(e).maxCallsByTool),maxSuccessfulCallsByTool:readPositiveIntegerMap(repeatGuardConfig(e).maxSuccessfulCallsByTool)}}function repeatGuardConfig(e){return isRecord(e)&&isRecord(e.repeatGuard)?e.repeatGuard:{}}function readPositiveInteger(e){return"number"==typeof e&&Number.isInteger(e)&&e>0?e:void 0}function readPositiveIntegerMap(e){return isRecord(e)?new Map(Object.entries(e).map(([e,t])=>[e,readPositiveInteger(t)]).filter(e=>void 0!==e[1])):new Map}export function beforeToolInvoke(e,t,o){const r=o.toolCallCounts.get(e)??0;o.toolCallCounts.set(e,r+1);const n=o.maxCallsByTool.get(e)??o.maxCallsPerTool;if(void 0!==n&&r>=n){const t=repeatedToolCallLimitContent(e,o.latestSuccessfulOutputByTool.get(e));return{eventOutput:t,modelOutput:t}}const s=o.maxSuccessfulCallsByTool.get(e)??o.maxSuccessfulCallsPerTool;if(void 0!==s&&(o.successfulToolCounts.get(e)??0)>=s){const t=repeatedToolCallLimitContent(e,o.latestSuccessfulOutputByTool.get(e));return{eventOutput:t,modelOutput:t}}const a=stableToolCallKey(e,t),i=o.successfulCalls.get(a);if(void 0!==i){const t=o.duplicateCallCounts.get(a)??0;if(o.duplicateCallCounts.set(a,t+1),void 0!==o.maxDuplicateCallsPerTool&&t>=o.maxDuplicateCallsPerTool){const t=repeatedToolCallLimitContent(e);return{eventOutput:t,modelOutput:t}}const r=function duplicateToolCallContent(e,t){return JSON.stringify({status:"duplicate_tool_call",toolId:e,instruction:"This agent already completed an equivalent tool call. Use the prior evidence instead of calling the tool again.",previousOutput:t})}(e,i);return{eventOutput:r,modelOutput:i}}}export function isToolRepeatLimitReached(e,t){if(!t)return!1;const o=t.maxCallsByTool.get(e)??t.maxCallsPerTool;if(void 0!==o&&(t.toolCallCounts.get(e)??0)>=o)return!0;const r=t.maxSuccessfulCallsByTool.get(e)??t.maxSuccessfulCallsPerTool;return void 0!==r&&(t.successfulToolCounts.get(e)??0)>=r}export function afterToolInvoke(t,r,n,s,a){return s instanceof e&&"error"===s.status?{}:o(n)?(a.successfulCalls.set(stableToolCallKey(t,r),n),a.latestSuccessfulOutputByTool.set(t,n),a.successfulToolCounts.set(t,(a.successfulToolCounts.get(t)??0)+1),{}):{}}function emitToolResult(e,t,o,r){void 0!==r&&n(e,t,o,r),e.emit({type:"runtime.adapter.event",requestId:e.requestId,sessionId:e.sessionId,agentId:t,event:void 0===r?{adapter:"deepagents",phase:"agent.tool.start",toolId:o}:{adapter:"deepagents",phase:"agent.tool.result",toolId:o,output:previewToolOutput(r),...toolControlProjection(r)}})}function repeatedToolCallLimitContent(e,t){return JSON.stringify({status:"repeated_tool_call_limit",toolId:e,instruction:"This tool reached the configured repeat limit for this request. Do not call this tool or a substitute tool for the same evidence need again. Use previousOutput and the collected evidence to produce the final answer now, or report the remaining gap explicitly.",...void 0!==t?{previousOutput:t}:{}})}export function stringifyDeepAgentResult(t){if(t instanceof e)return function stringifyToolMessageContent(e){return"string"==typeof e?e:JSON.stringify(e)}(t.content);if("string"==typeof t)return t;if(isRecord(t)){const e=t.structuredResponse??t.structured_response;if(void 0!==e)return"string"==typeof e?e:JSON.stringify(e);const o=(Array.isArray(t.messages)?t.messages:[]).at(-1);if(isRecord(o)&&"string"==typeof o.content)return o.content;const r=(isRecord(t.update)&&Array.isArray(t.update.messages)?t.update.messages:[]).at(-1);if(isRecord(r)&&isRecord(r.kwargs)&&"string"==typeof r.kwargs.content)return r.kwargs.content;if(isRecord(r)&&"string"==typeof r.content)return r.content}return JSON.stringify(t)}function buildToolDescription(e,t,o,r){const n=function toolRepeatPolicyDescription(e,t){const o=repeatGuardConfig(e),r=readPositiveIntegerMap(o.maxSuccessfulCallsByTool).get(t)??readPositiveInteger(o.maxSuccessfulCallsPerTool);return void 0===r?"":`Stable runtime repeat policy: call this tool at most ${r} successful time(s) for this request. If more detail is needed, include the dimensions in the first call and synthesize after the result returns.`}(o,r),s=n?`${e}\n\n${n}`:e;return t?`${s}\n\nStable tool input schema:\n${previewToolOutput(JSON.stringify(t))}`:s}function previewToolOutput(e){const t=e.replace(/\s+/gu," ").trim();return t.length>500?`${t.slice(0,497)}...`:t}export function toolControlProjection(e){const t=function parseJsonRecord(e){try{const t=JSON.parse(e);return isRecord(t)?t:void 0}catch{return}}(e);if("string"==typeof t?.status)return{controlStatus:t.status};const o=function readTextStatus(e){return String(e).match(/^Status:\s*([A-Za-z0-9_-]+)/imu)?.[1]}(e);return o?{controlStatus:o}:"string"==typeof t?.error?{controlStatus:t.error}:e.startsWith("Task delegation target is not in the workspace inventory")?{controlStatus:"task_inventory_blocked"}:{}}function stableToolCallKey(e,t){return`${e}:${stableJson(t)}`}function stableJson(e){return Array.isArray(e)?`[${e.map(stableJson).join(",")}]`:isRecord(e)?`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${stableJson(e[t])}`).join(",")}}`:JSON.stringify(e)}function formatObservedEvidenceForToolContext(e){const t=r(e).map(e=>`Tool: ${e.toolId}\n${e.output}`).join("\n\n---\n\n");return t.length>12e3?`${t.slice(0,12e3)}\n[truncated]`:t}function formatToolArgumentError(e){return JSON.stringify({error:"tool_argument_validation_failed",toolId:e.toolId,issues:e.issues,retry:"Call the same tool again with arguments that satisfy the reported schema and semantic issues."})}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function readRecord(e){return isRecord(e)?e:{}}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}function formatError(e){return e instanceof Error?e.message:String(e)}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"main": "dist/src/index.js",
|
|
11
11
|
"types": "dist/src/index.d.ts",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@botbotgo/better-call": "^0.1.
|
|
13
|
+
"@botbotgo/better-call": "^0.1.58",
|
|
14
14
|
"@langchain/core": "^1.1.43",
|
|
15
15
|
"@langchain/ollama": "^1.2.7",
|
|
16
16
|
"@langchain/openai": "^1.4.5",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as e}from"node:fs/promises";import r from"node:path";export function createRegistrySkillResolverProvider(e={}){return{name:"registry-resolver",resolve:r=>async function resolveRegistrySkill(e,r){const
|
|
1
|
+
import{readFile as e}from"node:fs/promises";import r from"node:path";import{repairRuntimeSelection as t}from"@stable-harness/core";export function createRegistrySkillResolverProvider(e={}){return{name:"registry-resolver",resolve:r=>async function resolveRegistrySkill(e,r){const o=await async function resolveWorkspaceSkill(e){const r=e.workspace.skills.get(e.id);if(r)return r;const o=await t({id:e.id,candidates:[...e.workspace.skills.values()].map(e=>({id:e.id,description:e.description}))});return o.ok?e.workspace.skills.get(o.id):void 0}(e);if(!o)throw new Error(`LangGraph skill resolver cannot find skill ${e.id}`);return{id:o.id,path:o.path,...o.description?{description:o.description}:{},allowedTools:o.allowedTools,...!1===r.includeContent?{}:{content:await readSkillContent(o,r.maxBytes)}}}(r,e)}}export function createDeepAgentsMiddlewareSkillProvider(e={}){return{name:"deepagents-middleware",async createMiddleware(r){const t=await async function loadDeepAgentsSkillsModule(e){return e.importDeepAgents?e.importDeepAgents():import("deepagents")}(e),o=t.createSkillsMiddleware;if("function"!=typeof o)throw new Error("deepagents does not export createSkillsMiddleware");return o({backend:e.backend??createDeepAgentsFilesystemBackend(t,r.workspace.root),sources:e.sources??deriveDeepAgentsSkillSources(r)})}}}export function resolveSkillProvider(e){if(!1!==e.skillProvider)return e.skillProvider??createRegistrySkillResolverProvider()}async function readSkillContent(r,t=1048576){const o=await e(r.path,"utf8");if(Buffer.byteLength(o,"utf8")>t)throw new Error(`Skill ${r.id} exceeds registry resolver size limit of ${t} bytes`);return o}function createDeepAgentsFilesystemBackend(e,r){if("function"!=typeof e.FilesystemBackend)throw new Error("deepagents does not export FilesystemBackend");return new e.FilesystemBackend({rootDir:r})}function deriveDeepAgentsSkillSources(e){const t=new Set([...(o=e.agent,o?.skills??[]),...skillIdsFromWorkflow(e)]);var o;const i=new Set;for(const o of t){const t=e.workspace.skills.get(o);t&&i.add(toPosixPath(r.relative(e.workspace.root,r.dirname(r.dirname(t.path)))))}return[...i].filter(e=>e&&!e.startsWith(".."))}function skillIdsFromWorkflow(e){return e.workflow.nodes.map(e=>e.use.match(/^skills\.([^./][^.]*)$/u)?.[1]).filter(e=>Boolean(e))}function toPosixPath(e){return e.split(r.sep).join("/")}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{realpathSync as e}from"node:fs";import{fileURLToPath as t}from"node:url";import{createDeepAgentsAdapter as r}from"@stable-harness/adapter-deepagents";import{createLangGraphWorkflowAdapter as
|
|
2
|
+
import{realpathSync as e}from"node:fs";import{fileURLToPath as t}from"node:url";import{createBackendModel as o,createDeepAgentsAdapter as r}from"@stable-harness/adapter-deepagents";import{createLangGraphWorkflowAdapter as s}from"@stable-harness/adapter-langgraph";import{createStableHarnessRuntime as i}from"@stable-harness/core";import{projectEvent as a,projectRuntimeTrace as n}from"@stable-harness/core";import{createModuleToolGateway as u}from"@stable-harness/tool-gateway";import{loadWorkspaceFromYaml as l}from"@stable-harness/workspace-yaml";import{helpText as d,parseArgs as c}from"./args.js";import{formatCliRuntimeEvent as p,readCliEventViewConfig as f,shouldEnableCliProgressNarration as m}from"./event-view.js";import{initWorkspace as w}from"./init.js";import{ensureCliMemoryServices as g}from"./memory/lifecycle.js";import{createCliMemoryProviders as v}from"./memory/providers.js";import{formatDetail as y,inspectWorkflow as k,renderAgent as I,renderWorkflow as R,workspaceStatus as h}from"./output.js";import{serveProtocol as q,stopProtocol as b}from"./server.js";export async function runCli(e=process.argv.slice(2)){const t=c(e);if(t.help)return void process.stdout.write(d());const o=setTimeout(()=>{process.stderr.write(`stable-harness request timed out after ${t.timeoutMs}ms\n`),process.exit(124)},t.timeoutMs),s=t.workspaceRoot;try{if("init"===t.command)return void process.stdout.write(await w(t.prompt||s));const e=await l(s);if(t.workflowRenderId)return void process.stdout.write(R(e,t.workflowRenderId));if(t.workflowInspectId)return void process.stdout.write(k(e,t.workflowInspectId));if(t.agentRenderId)return void process.stdout.write(I(e,t.agentRenderId));if("stop"===t.command)return clearTimeout(o),void await b(e,t);const d=await u({tools:e.tools.values(),options:{betterCall:{mode:"repair"}}});await g(e);const c=v(e),C=f(e.runtime);let M;if(M=i({workspace:e,toolGateway:d,memoryProviders:c,adapters:[r()],workflowAdapters:[createCliWorkflowAdapter(d,()=>M)],progressNarration:m(C,e.runtime)?{enabled:!0,style:"cli"}:void 0,qualityReviewModel:createQualityReviewModel(e)}),t.serveOpenAi)return clearTimeout(o),void await q(M,t);if(!t.shouldRunRequest)return void process.stdout.write(h(e,s));t.trace&&M.subscribe(e=>{const t=a(e);t&&process.stdout.write(`trace:${t.agentId}:${t.label}${y(t.detail)}\n`)}),M.subscribe(e=>{const t=p(e,C);t&&process.stdout.write(`${t}\n`)});const j=await M.request({input:t.prompt,agentId:t.agentId,sessionId:t.sessionId,toolCall:t.toolId?{toolId:t.toolId,args:t.toolArgs}:void 0,workflow:t.workflowRunId?{workflowId:t.workflowRunId,input:t.prompt}:void 0});if(t.trace||t.traceJson){const e=M.getRun(j.requestId),o=e?n(e):[];t.traceJson&&process.stdout.write(`${JSON.stringify({trace:o})}\n`)}process.stdout.write(`${j.output}\n`)}finally{clearTimeout(o)}}function createQualityReviewModel(e){const t=function readQualityModelRef(e){const t=isRecord(e)?e:{},o=isRecord(t.reviewer)?t.reviewer:t;return"string"==typeof o.modelRef&&o.modelRef.trim()?o.modelRef.trim():void 0}(e.runtime.quality),r=t?e.models.get(t):void 0,s=r?o(r):void 0;return function isQualityReviewModel(e){return isRecord(e)&&"function"==typeof e.invoke}(s)?s:void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function createCliWorkflowAdapter(e,t){return s({nodeResolvers:{tools:async({id:t,node:o,request:r,requestId:s,sessionId:i,state:a,workspace:n})=>{return(await e.invoke({toolId:t,args:(u=o.config,l=r.input,d=a.outputs,!0===u?.inputFromState?{...u,requestInput:l,outputs:d}:u&&"requiredInput"in u?u.requiredInput:u&&("args"in u||"cwd"in u||"timeoutMs"in u)?u:"object"==typeof l&&null!==l?l:{}),context:{workspaceRoot:n.root,requestId:s,sessionId:i,agentId:`workflow:${o.id}`}})).output;var u,l,d},agents:async({id:e,node:o,request:r,sessionId:s,state:i})=>{var a,n,u,l;return(await t().request({input:(a=e,n=r.input,u=i.outputs,l=o.config,[`Workflow node agents.${a}: synthesize the workflow evidence into the requested final output.`,`Original request: ${"string"==typeof n?n:JSON.stringify(n)}`,"Requirements:","- Produce the final answer now; do not ask follow-up questions.","- Match the original request language unless workflow config explicitly says otherwise.","- Use only the workflow outputs as evidence; call out uncertainty directly.",...l?[`Workflow node config: ${JSON.stringify(l)}`]:[],"Prior workflow outputs:",JSON.stringify(u)].join("\n")),agentId:e,sessionId:s,metadata:r.metadata})).output}}})}(function isCliEntrypoint(){const o=process.argv[1];if(!o)return!1;try{return e(t(import.meta.url))===e(o)}catch{return!1}})()&&runCli().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function assertExecutionContract(e){(function contractDisabled(e){const t=isRecord(e?.executionContract)?e.executionContract:void 0;return!1===t?.enabled})(e.metadata)||(function assertRequiredPlan(e){const t=readExecutionContract(e.agent);if(!t.requiresPlan)return;const n=readStringArray(t.planEvidenceTools);if(0===n.length)throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"invalid_plan_contract"}),new Error("Execution contract requires plan evidence tools when requiresPlan is enabled");const r=e.store.getRun(e.requestId),o=new Set((r?.events??[]).flatMap(readEvidenceToolId));if(!n.some(e=>o.has(e)))throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"missing_plan",missingEvidenceTools:n}),new Error(`Execution contract requires a planning checkpoint from one of: ${n.join(", ")}`)}(e),function assertRequiredEvidenceTools(e){const t=function readRequiredEvidenceTools(e){return readStringArray(readExecutionContract(e).requiredEvidenceTools)}(e.agent);if(0===t.length)return;const n=e.store.getRun(e.requestId),r=new Set((n?.events??[]).flatMap(readEvidenceToolId)),o=t.filter(e=>!r.has(e));if(0!==o.length)throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"missing_required_evidence",missingEvidenceTools:o}),new Error(`Execution contract missing required evidence tools: ${o.join(", ")}`)}(e))}function readExecutionContract(e){return isRecord(e.config.executionContract)?e.config.executionContract:{}}function readEvidenceToolId(e){return"runtime.tool.direct.completed"===e.type?[e.toolId]:"runtime.adapter.event"===e.type&&isRecord(e.event)
|
|
1
|
+
export function assertExecutionContract(e){(function contractDisabled(e){const t=isRecord(e?.executionContract)?e.executionContract:void 0;return!1===t?.enabled})(e.metadata)||(function assertRequiredPlan(e){const t=readExecutionContract(e.agent);if(!t.requiresPlan)return;const n=readStringArray(t.planEvidenceTools);if(0===n.length)throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"invalid_plan_contract"}),new Error("Execution contract requires plan evidence tools when requiresPlan is enabled");const r=e.store.getRun(e.requestId),o=new Set((r?.events??[]).flatMap(readEvidenceToolId));if(!n.some(e=>o.has(e)))throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"missing_plan",missingEvidenceTools:n}),new Error(`Execution contract requires a planning checkpoint from one of: ${n.join(", ")}`)}(e),function assertRequiredEvidenceTools(e){const t=function readRequiredEvidenceTools(e){return readStringArray(readExecutionContract(e).requiredEvidenceTools)}(e.agent);if(0===t.length)return;const n=e.store.getRun(e.requestId),r=new Set((n?.events??[]).flatMap(readEvidenceToolId)),o=t.filter(e=>!r.has(e));if(0!==o.length)throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"missing_required_evidence",missingEvidenceTools:o}),new Error(`Execution contract missing required evidence tools: ${o.join(", ")}`)}(e),function assertToolDependencies(e){const t=readExecutionContract(e.agent),n=isRecord(t.toolDependencies)?t.toolDependencies:{},r=Object.entries(n).map(([e,t])=>[e,readStringArray(t)]).filter(e=>e[1].length>0);if(0===r.length)return;const o=e.store.getRun(e.requestId),i=new Set((o?.events??[]).flatMap(readEvidenceToolId)),s=r.filter(([e])=>i.has(e)).flatMap(([e,t])=>t.filter(e=>!i.has(e)).map(t=>`${e} requires ${t}`));if(0!==s.length)throw e.emit({type:"runtime.execution.contract.failed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,reason:"missing_tool_dependency",missingEvidenceTools:s}),new Error(`Execution contract missing required tool dependencies: ${s.join(", ")}`)}(e))}function readExecutionContract(e){return isRecord(e.config.executionContract)?e.config.executionContract:{}}function readEvidenceToolId(e){return"runtime.tool.direct.completed"===e.type?[e.toolId]:"runtime.adapter.event"===e.type&&isRecord(e.event)?"agent.tool.result"!==e.event.phase||"string"!=typeof e.event.toolId?[]:function isSuccessfulEvidenceEvent(e){const t=function readString(e){return"string"==typeof e&&e.length>0?e:void 0}(e.controlStatus)??function readOutputStatus(e){if("string"!=typeof e)return;const t=function parseJsonRecord(e){try{const t=JSON.parse(e);return isRecord(t)?t:void 0}catch{return}}(e);return"string"==typeof t?.status?t.status:e.match(/^Status:\s*([A-Za-z0-9_-]+)/imu)?.[1]}(e.output);return!t||/^(?:completed|success|ok|recorded)$/iu.test(t)}(e.event)?[e.event.toolId]:[]:[]}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}
|
|
@@ -6,9 +6,11 @@ export * from "./runtime/persistence/inspection.js";
|
|
|
6
6
|
export * from "./memory-plugins.js";
|
|
7
7
|
export * from "./runtime/persistence/queue.js";
|
|
8
8
|
export * from "./runtime.js";
|
|
9
|
+
export * from "./runtime/selection-repair.js";
|
|
9
10
|
export * from "./runtime/persistence/stores.js";
|
|
10
11
|
export * from "./trace.js";
|
|
11
12
|
export * from "./types.js";
|
|
12
13
|
export * from "./evaluations/index.js";
|
|
14
|
+
export * from "./quality/index.js";
|
|
13
15
|
export * from "./spec-driven/index.js";
|
|
14
16
|
export * from "./workflows/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./runtime/persistence/artifacts.js";export*from"./boundary-scan.js";export*from"./execution-contract.js";export*from"./recovery/tool-call.js";export*from"./runtime/persistence/inspection.js";export*from"./memory-plugins.js";export*from"./runtime/persistence/queue.js";export*from"./runtime.js";export*from"./runtime/persistence/stores.js";export*from"./trace.js";export*from"./types.js";export*from"./evaluations/index.js";export*from"./spec-driven/index.js";export*from"./workflows/index.js";
|
|
1
|
+
export*from"./runtime/persistence/artifacts.js";export*from"./boundary-scan.js";export*from"./execution-contract.js";export*from"./recovery/tool-call.js";export*from"./runtime/persistence/inspection.js";export*from"./memory-plugins.js";export*from"./runtime/persistence/queue.js";export*from"./runtime.js";export*from"./runtime/selection-repair.js";export*from"./runtime/persistence/stores.js";export*from"./trace.js";export*from"./types.js";export*from"./evaluations/index.js";export*from"./quality/index.js";export*from"./spec-driven/index.js";export*from"./workflows/index.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RuntimeEvent } from "../types.js";
|
|
2
|
+
export declare function hasPlanningEvidence(events: RuntimeEvent[]): boolean;
|
|
3
|
+
export declare function successfulEvidenceToolIds(events: RuntimeEvent[]): string[];
|
|
4
|
+
export declare function successfulEvidenceOutputs(events: RuntimeEvent[]): string[];
|
|
5
|
+
export declare function controlBlockers(events: RuntimeEvent[]): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function hasPlanningEvidence(t){return t.some(t=>{const e=readAdapterEvent(t);return!!e&&("plan"===e.traceType||String(e.traceLabel??"").startsWith("plan.")||"write_todos"===e.toolId&&String(e.phase??"").startsWith("agent.tool."))})}export function successfulEvidenceToolIds(t){const e=t.flatMap(t=>{if("runtime.tool.direct.completed"===t.type)return[t.toolId];const e=readAdapterEvent(t);return e&&"agent.tool.result"===e.phase&&"string"==typeof e.toolId&&isSuccessfulEvidenceEvent(e)?[e.toolId]:[]});return[...new Set(e)]}export function successfulEvidenceOutputs(t){return t.flatMap(t=>{if("runtime.tool.direct.completed"===t.type)return stringifyEvidence(t.output);const e=readAdapterEvent(t);return e&&"agent.tool.result"===e.phase&&"string"==typeof e.toolId?!isSuccessfulEvidenceEvent(e)||function isPlanningTool(t){return/^(?:write_todos|read_todos)$/u.test(t)}(e.toolId)?[]:stringifyEvidence(e.output):[]})}export function controlBlockers(t){return t.flatMap(t=>{const e=readAdapterEvent(t),r=readString(e?.controlStatus)??readOutputStatus(e?.output);return r&&function isBlockerStatus(t){return/^(?:blocked|approval_required|schema_repair_failed|tool_argument_error|invalid_input)$/iu.test(t)}(r)?[`${readString(e?.toolId)??"tool"}:${r}`]:[]})}function stringifyEvidence(t){return"string"==typeof t?t.trim()?[t]:[]:null==t?[]:[JSON.stringify(t)]}function readAdapterEvent(t){return"runtime.adapter.event"===t.type&&isRecord(t.event)?t.event:void 0}function isSuccessfulEvidenceEvent(t){const e=readString(t.controlStatus)??readOutputStatus(t.output);return!e||/^(?:completed|success|ok|recorded)$/iu.test(e)}function readOutputStatus(t){if("string"!=typeof t)return;const e=function parseJsonRecord(t){try{const e=JSON.parse(t);return isRecord(e)?e:void 0}catch{return}}(t);return"string"==typeof e?.status?e.status:t.match(/^Status:\s*([A-Za-z0-9_-]+)/imu)?.[1]}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function readString(t){return"string"==typeof t&&t.trim()?t:void 0}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{controlBlockers as e,successfulEvidenceOutputs as n,successfulEvidenceToolIds as t}from"./event-evidence.js";const r=/(?<![\w.])(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?[%kKmMbBtTxX]?(?!\w)/gu;export function reviewExecutionEvidence(e,n){if(!n.enabled||!n.executionReview.enabled)return{verdict:"pass",issues:[]};const t=[...blockerIssues(e,n),...emptyFinalIssues(e,n),...toolEvidenceIssues(e,n),...ungroundedNumberIssues(e,n)];return 0===t.length?{verdict:"pass",issues:[]}:{verdict:t.some(e=>!e.recoverable)?"blocked":"continue_react",issues:t}}function ungroundedNumberIssues(e,t){if(!t.executionReview.rejectUngroundedNumbers||!e.output?.text.trim())return[];const r=numberSet(n(e.events).join("\n"));if(0===r.size)return[];const s=[...numberSet(e.output.text)].filter(e=>!function isSupportedNumber(e,n){if(n.has(e))return!0;const t=Number.parseFloat(e);if(!Number.isFinite(t))return!1;for(const e of n){const n=Number.parseFloat(e);if(Number.isFinite(n)&&Math.abs(n-t)<=roundingTolerance(t))return!0}return!1}(e,r));return 0===s.length?[]:[{code:"ungrounded_numeric_claim",message:`Final answer contains numeric claims not found in successful tool evidence: ${s.slice(0,12).join(", ")}`,recoverable:!1}]}function numberSet(e){const n=new Set;for(const t of e.matchAll(r)){const e=normalizeNumber(t[0]);e&&n.add(e)}return n}function normalizeNumber(e){const n=e.replace(/,/gu,"").replace(/^\+/u,"").replace(/[%kKmMbBtTxX]$/u,"").trim();if(n){if(/^\d+$/u.test(n)){const e=Number.parseInt(n,10);if(e>=1&&e<=20)return;return String(e)}return/^\d+\.\d+$/u.test(n)?n.replace(/0+$/u,"").replace(/\.$/u,""):void 0}}function roundingTolerance(e){return Math.abs(e)>=1e3?1:Math.abs(e)>=100?.1:Math.abs(e)>=10?.05:.005}function blockerIssues(n,t){return t.executionReview.stopOnBlocker?e(n.events).map(e=>({code:"control_blocker",message:`Execution produced a control blocker: ${e}`,recoverable:!1})):[]}function emptyFinalIssues(e,n){return!n.executionReview.rejectEmptyFinal||e.output?.text.trim()?[]:[{code:"empty_final_answer",message:"The final answer is empty.",recoverable:!0}]}function toolEvidenceIssues(e,n){return!n.executionReview.requireToolEvidence||t(e.events).length>0?[]:[{code:"missing_tool_evidence",message:"No successful tool or delegated-task evidence was observed.",recoverable:!0}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./types.js";export*from"./profile.js";export*from"./planning-review.js";export*from"./execution-review.js";export*from"./llm-review.js";export*from"./recovery-policy.js";export*from"./runtime.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { QualityPolicy, QualityReviewInput, QualityReviewModel, QualityReviewResult } from "./types.js";
|
|
2
|
+
export declare function reviewWithLlm(input: {
|
|
3
|
+
phase: "planning" | "execution";
|
|
4
|
+
review: QualityReviewInput;
|
|
5
|
+
policy: QualityPolicy;
|
|
6
|
+
model?: QualityReviewModel;
|
|
7
|
+
}): Promise<QualityReviewResult | undefined>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export async function reviewWithLlm(e){if(!e.model||"llm"!==e.policy.reviewer?.mode)return;const t="planning"===e.phase?e.policy.reviewer.planningPrompt??function defaultPlanningPrompt(){return["You are Stable Harness planning quality review.","Review only generic agent-work quality: goal coverage, executable steps, and declared inventory fit.",'Do not invent a domain workflow. Return only JSON: {"verdict":"pass|revise_plan|blocked","issues":[{"code":"...","message":"...","recoverable":true}]}'].join("\n")}():e.policy.reviewer.executionPrompt??function defaultExecutionPrompt(){return["You are Stable Harness execution evidence review.","Review only whether execution evidence supports the final answer and whether unresolved blockers or gaps remain.",'Do not answer the user request. Return only JSON: {"verdict":"pass|continue_react|blocked","issues":[{"code":"...","message":"...","recoverable":true}]}'].join("\n")}();return function parseReviewResult(e){const t=function readResponseText(e){if("string"==typeof e)return e;if(!isRecord(e))return;if("string"==typeof e.content)return e.content;const t=isRecord(e.message)?e.message:void 0;return"string"==typeof t?.content?t.content:void 0}(e),n="string"==typeof t?function parseJsonRecord(e){const t=e.trim().replace(/^```(?:json)?\s*/iu,"").replace(/\s*```$/u,"");try{const e=JSON.parse(t);return isRecord(e)?e:void 0}catch{return}}(t):isRecord(e)?e:void 0,r=function readVerdict(e){return"pass"===e||"revise_plan"===e||"continue_react"===e||"blocked"===e?e:void 0}(n?.verdict);if(n&&r)return{verdict:r,issues:readIssues(n.issues)}}(await e.model.invoke([{role:"system",content:t},{role:"user",content:JSON.stringify(buildReviewContext(e.review),null,2)}]))}function buildReviewContext(e){return{userRequest:e.request.input,agent:{id:e.agent.id,tools:e.agent.tools,skills:e.agent.skills??[],subagents:e.agent.subagents},finalOutput:e.output?.text,events:e.events.slice(-30).map(projectEvent)}}function projectEvent(e){return"runtime.adapter.event"===e.type?{type:e.type,event:e.event}:{...e}}function readIssues(e){return Array.isArray(e)?e.flatMap(e=>isRecord(e)&&"string"==typeof e.code&&"string"==typeof e.message?[{code:e.code,message:e.message,recoverable:!1!==e.recoverable}]:[]):[]}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{hasPlanningEvidence as e}from"./event-evidence.js";export function reviewPlanningEvidence(n,s){return s.enabled&&s.planningReview.enabled&&s.planningReview.requirePlan?e(n.events)?{verdict:"pass",issues:[]}:{verdict:"revise_plan",issues:[{code:"missing_plan_evidence",message:"No upstream planning evidence was observed before the agent produced a result.",recoverable:!0}]}:{verdict:"pass",issues:[]}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function resolveQualityPolicy(e,n){const r=n.config.quality??e.quality,o=function readProfile(e){const n="string"==typeof e?e:isRecord(e)?e.profile:void 0;return"fast"===n||"balanced"===n||"strict"===n?n:"off"}(r),i=function profileDefaults(e,n){const r=n.tools.length>0||n.subagents.length>0||(n.skills?.length??0)>0;return"off"===e?function disabledPolicy(e){return{enabled:!1,profile:e,reviewer:{mode:"deterministic"},planningReview:{enabled:!1,requirePlan:!1},executionReview:{enabled:!1,requireToolEvidence:!1,rejectEmptyFinal:!1,stopOnBlocker:!1,rejectUngroundedNumbers:!1},recovery:{enabled:!1,maxLoops:0}}}(e):{enabled:!0,profile:e,planningReview:{enabled:"fast"!==e,requirePlan:"fast"!==e&&r},executionReview:{enabled:!0,requireToolEvidence:"strict"===e&&r,rejectEmptyFinal:!0,stopOnBlocker:!0,rejectUngroundedNumbers:"strict"===e},recovery:{enabled:"fast"!==e,maxLoops:"strict"===e?3:2}}}(o,n),t=isRecord(r)?r:{},a=function readReviewer(e){const n=isRecord(e.reviewer)?e.reviewer:e,r="llm"===n.mode||"string"==typeof n.modelRef?"llm":"deterministic",o=isRecord(n.prompts)?n.prompts:{},i=readString(n.modelRef);return{mode:r,...i?{modelRef:i}:{},...readString(n.planningPrompt)??readString(o.planning)?{planningPrompt:readString(n.planningPrompt)??readString(o.planning)}:{},...readString(n.executionPrompt)??readString(o.execution)?{executionPrompt:readString(n.executionPrompt)??readString(o.execution)}:{}}}(t);return{enabled:i.enabled,profile:o,...a?{reviewer:a}:{},planningReview:{enabled:readBoolean(t.planningReview,"enabled")??i.planningReview.enabled,requirePlan:readBoolean(t.planningReview,"requirePlan")??i.planningReview.requirePlan},executionReview:{enabled:readBoolean(t.executionReview,"enabled")??i.executionReview.enabled,requireToolEvidence:readBoolean(t.executionReview,"requireToolEvidence")??i.executionReview.requireToolEvidence,rejectEmptyFinal:readBoolean(t.executionReview,"rejectEmptyFinal")??i.executionReview.rejectEmptyFinal,stopOnBlocker:readBoolean(t.executionReview,"stopOnBlocker")??i.executionReview.stopOnBlocker,rejectUngroundedNumbers:readBoolean(t.executionReview,"rejectUngroundedNumbers")??i.executionReview.rejectUngroundedNumbers},recovery:{enabled:readBoolean(t.recovery,"enabled")??i.recovery.enabled,maxLoops:readPositiveInteger(t.recovery,"maxLoops")??i.recovery.maxLoops}}}function readString(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function readBoolean(e,n){const r=isRecord(e)?e:{};return"boolean"==typeof r[n]?r[n]:void 0}function readPositiveInteger(e,n){const r=(isRecord(e)?e:{})[n];return"number"==typeof r&&Number.isInteger(r)&&r>0?r:void 0}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RuntimeRequest } from "../types.js";
|
|
2
|
+
import type { QualityPolicy, QualityReviewResult } from "./types.js";
|
|
3
|
+
export declare function buildQualityRecoveryRequest(input: {
|
|
4
|
+
request: RuntimeRequest;
|
|
5
|
+
result: QualityReviewResult;
|
|
6
|
+
phase: "planning" | "execution";
|
|
7
|
+
policy: QualityPolicy;
|
|
8
|
+
availableToolIds: string[];
|
|
9
|
+
availableSubagentIds: string[];
|
|
10
|
+
observedEvidence?: string[];
|
|
11
|
+
}): RuntimeRequest | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function buildQualityRecoveryRequest(e){if(!e.policy.recovery.enabled||"pass"===e.result.verdict||"blocked"===e.result.verdict)return;const n="planning"===e.phase?function planningLines(e){return["Stable runtime quality review: the previous plan was not ready to execute.",...issueLines(e.result),inventoryLine("Available configured tools",e.availableToolIds),inventoryLine("Available configured subagents",e.availableSubagentIds),"Revise the plan using only the declared inventory, then continue through the backend's normal planning and tool-calling flow.","Do not invent tools, subagents, or a separate planning language."].filter(e=>e.length>0)}(e):function executionLines(e){return["Stable runtime quality review: the execution evidence is not sufficient for a final answer.",...issueLines(e.result),inventoryLine("Available configured tools",e.availableToolIds),inventoryLine("Available configured subagents",e.availableSubagentIds),...(n=e.observedEvidence??[],0===n.length?[]:["Completed observed evidence available for synthesis:",...n.slice(-8).map((e,n)=>`Evidence ${n+1}: ${function compact(e){const n=e.replace(/\s+/gu," ").trim();return n.length>900?`${n.slice(0,897)}...`:n}(e)}`)]),"Continue from the current state using ReAct: inspect the evidence gap, then either synthesize directly from completed observed evidence or choose one remaining declared tool/subagent action.","Do not call tools that already produced completed evidence or a repeat-limit control result for the same evidence need.","Preserve blockers explicitly instead of converting control states into unsupported factual answers."].filter(e=>e.length>0);var n}(e);return{...e.request,input:[e.request.input,"",...n].join("\n"),metadata:{...e.request.metadata,stableHarnessRecovery:`quality_${e.phase}`}}}function issueLines(e){return e.issues.map(e=>`- ${e.code}: ${e.message}`)}function inventoryLine(e,n){return n.length>0?`${e}: ${n.join(", ")}`:""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RuntimeMemoryContext, RuntimeOutput, RuntimeRequest } from "../types.js";
|
|
2
|
+
import type { QualityPolicy, QualityReviewInput, QualityReviewModel } from "./types.js";
|
|
3
|
+
export type QualityRuntimeInput = QualityReviewInput & {
|
|
4
|
+
requestId: string;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
emit: (event: import("../types.js").RuntimeEvent) => void;
|
|
7
|
+
getEvents: () => import("../types.js").RuntimeEvent[];
|
|
8
|
+
runAdapter: (request: RuntimeRequest) => Promise<RuntimeOutput>;
|
|
9
|
+
reviewModel?: QualityReviewModel;
|
|
10
|
+
memory?: RuntimeMemoryContext;
|
|
11
|
+
pluginMemories: RuntimeMemoryContext[];
|
|
12
|
+
};
|
|
13
|
+
export declare function recoverQualityReview(input: QualityRuntimeInput, request: RuntimeRequest, result: RuntimeOutput, policy: QualityPolicy): Promise<RuntimeOutput>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{successfulEvidenceOutputs as e}from"./event-evidence.js";import{buildQualityRecoveryRequest as i}from"./recovery-policy.js";import{reviewExecutionEvidence as t}from"./execution-review.js";import{reviewWithLlm as n}from"./llm-review.js";import{reviewPlanningEvidence as r}from"./planning-review.js";export async function recoverQualityReview(e,i,t,n){if(!n.enabled)return t;let r=i,s=t;for(let i=0;i<n.recovery.maxLoops+1;i+=1){const t=await emitPlanningReview(e,r,s,n);if("blocked"===t.verdict)return qualityFailureOutput("planning",t);const u=buildQualityRecovery(e,r,t,"planning",n,i);if(u){r=u,s=await e.runAdapter(r);continue}const a=await emitExecutionReview(e,r,s,n),o=buildQualityRecovery(e,r,a,"execution",n,i);if(!o)return"pass"===a.verdict?s:qualityFailureOutput("execution",a);r=o,s=await e.runAdapter(r)}return qualityFailureOutput("execution",{verdict:"blocked",issues:[{code:"quality_recovery_exhausted",message:`Quality recovery exceeded maxLoops=${n.recovery.maxLoops}.`,recoverable:!1}]})}function emitPlanningReview(e,i,t,n){return emitReview(e,"planning",r,i,t,n)}function emitExecutionReview(e,i,n,r){return emitReview(e,"execution",t,i,n,r)}async function emitReview(e,i,t,r,s,u){const a={...reviewInputFor(e,r),output:s},o="planning"===i?u.planningReview.enabled:u.executionReview.enabled;if(!o)return t(a,u);const c=t(a,u),l=await n({phase:i,review:a,policy:u,model:e.reviewModel}),d="pass"===c.verdict?l??c:c;return o&&function emitReviewEvent(e,i,t){"planning"!==i?e.emit({type:"runtime.quality.execution.reviewed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,verdict:t.verdict,issues:t.issues}):e.emit({type:"runtime.quality.planning.reviewed",requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,verdict:t.verdict,issues:t.issues})}(e,i,d),d}function buildQualityRecovery(t,n,r,s,u,a){if(a>=u.recovery.maxLoops)return;const o=i({request:n,result:r,phase:s,policy:u,availableToolIds:t.agent.tools,availableSubagentIds:t.agent.subagents,observedEvidence:"execution"===s?e(t.getEvents()):[]});return o&&t.emit({type:"runtime.quality.recovery.started",requestId:t.requestId,sessionId:t.sessionId,agentId:t.agent.id,phase:s,attempt:a+1,verdict:r.verdict}),o}function reviewInputFor(e,i){return{workspace:e.workspace,agent:e.agent,request:i,events:e.getEvents()}}function qualityFailureOutput(e,i){return{text:[`Stable runtime quality review blocked final delivery during ${e}.`,"",...i.issues.length>0?i.issues.map(e=>`- ${e.code}: ${e.message}`):["- quality_review_failed: Quality review did not pass."]].join("\n")}}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CompiledWorkspace, RuntimeEvent, RuntimeOutput, RuntimeRequest, WorkspaceAgent } from "../types.js";
|
|
2
|
+
export type QualityProfile = "off" | "fast" | "balanced" | "strict";
|
|
3
|
+
export type QualityReviewVerdict = "pass" | "revise_plan" | "continue_react" | "blocked";
|
|
4
|
+
export type QualityReviewIssue = {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
recoverable: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type QualityReviewResult = {
|
|
10
|
+
verdict: QualityReviewVerdict;
|
|
11
|
+
issues: QualityReviewIssue[];
|
|
12
|
+
};
|
|
13
|
+
export type QualityPolicy = {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
profile: QualityProfile;
|
|
16
|
+
reviewer?: {
|
|
17
|
+
mode: "deterministic" | "llm";
|
|
18
|
+
modelRef?: string;
|
|
19
|
+
planningPrompt?: string;
|
|
20
|
+
executionPrompt?: string;
|
|
21
|
+
};
|
|
22
|
+
planningReview: {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
requirePlan: boolean;
|
|
25
|
+
};
|
|
26
|
+
executionReview: {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
requireToolEvidence: boolean;
|
|
29
|
+
rejectEmptyFinal: boolean;
|
|
30
|
+
stopOnBlocker: boolean;
|
|
31
|
+
rejectUngroundedNumbers: boolean;
|
|
32
|
+
};
|
|
33
|
+
recovery: {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
maxLoops: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export type QualityReviewInput = {
|
|
39
|
+
workspace: CompiledWorkspace;
|
|
40
|
+
agent: WorkspaceAgent;
|
|
41
|
+
request: RuntimeRequest;
|
|
42
|
+
events: RuntimeEvent[];
|
|
43
|
+
output?: RuntimeOutput;
|
|
44
|
+
};
|
|
45
|
+
export type QualityReviewModel = {
|
|
46
|
+
invoke(input: unknown): Promise<unknown> | unknown;
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export async function runDirectToolCall(
|
|
1
|
+
export async function runDirectToolCall(t){const o=t.request.toolCall;if(!o)throw new Error("Direct tool call request is missing");if(!t.gateway)throw new Error("Runtime tool gateway is not configured");const e=await async function resolveDirectToolCall(t){if(t.agent.tools.includes(t.toolId)&&t.gateway.get(t.toolId))return{toolId:t.toolId,args:t.args};const o=await(t.gateway.repairToolCall?.({toolId:t.toolId,args:t.args,allowedToolIds:t.agent.tools,context:{workspaceRoot:t.workspace.root,requestId:t.requestId,sessionId:t.sessionId,agentId:t.agent.id,requestInput:t.request.input}}));if(o&&t.agent.tools.includes(o.toolId)&&t.gateway.get(o.toolId))return o;if(!t.agent.tools.includes(t.toolId))throw new Error(`Tool ${t.toolId} is not assigned to agent ${t.agent.id}`);throw new Error(`Tool is not registered: ${t.toolId}`)}({gateway:t.gateway,workspace:t.workspace,requestId:t.requestId,sessionId:t.sessionId,agent:t.agent,request:t.request,toolId:o.toolId,args:o.args});t.emit({type:"runtime.tool.direct.started",requestId:t.requestId,sessionId:t.sessionId,agentId:t.agent.id,toolId:e.toolId});const s=await t.gateway.invoke({toolId:e.toolId,args:e.args,context:{workspaceRoot:t.workspace.root,requestId:t.requestId,sessionId:t.sessionId,agentId:t.agent.id,requestInput:t.request.input}});return t.emit({type:"runtime.tool.direct.completed",requestId:t.requestId,sessionId:t.sessionId,agentId:t.agent.id,toolId:s.toolId,output:s.output}),{text:(r=s.output,"string"==typeof r?r:JSON.stringify(r)),metadata:{toolCall:{toolId:s.toolId}}};var r}
|
|
@@ -192,6 +192,36 @@ export type RuntimeEvent = {
|
|
|
192
192
|
sourceEventIds?: string[];
|
|
193
193
|
model?: string;
|
|
194
194
|
style?: string;
|
|
195
|
+
} | {
|
|
196
|
+
type: "runtime.quality.planning.reviewed";
|
|
197
|
+
requestId: string;
|
|
198
|
+
sessionId: string;
|
|
199
|
+
agentId: string;
|
|
200
|
+
verdict: string;
|
|
201
|
+
issues: Array<{
|
|
202
|
+
code: string;
|
|
203
|
+
message: string;
|
|
204
|
+
recoverable: boolean;
|
|
205
|
+
}>;
|
|
206
|
+
} | {
|
|
207
|
+
type: "runtime.quality.execution.reviewed";
|
|
208
|
+
requestId: string;
|
|
209
|
+
sessionId: string;
|
|
210
|
+
agentId: string;
|
|
211
|
+
verdict: string;
|
|
212
|
+
issues: Array<{
|
|
213
|
+
code: string;
|
|
214
|
+
message: string;
|
|
215
|
+
recoverable: boolean;
|
|
216
|
+
}>;
|
|
217
|
+
} | {
|
|
218
|
+
type: "runtime.quality.recovery.started";
|
|
219
|
+
requestId: string;
|
|
220
|
+
sessionId: string;
|
|
221
|
+
agentId: string;
|
|
222
|
+
phase: "planning" | "execution";
|
|
223
|
+
attempt: number;
|
|
224
|
+
verdict: string;
|
|
195
225
|
} | {
|
|
196
226
|
type: "runtime.adapter.event";
|
|
197
227
|
requestId: string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CallCandidate, CallSelectionDiagnostics } from "@botbotgo/better-call";
|
|
2
|
+
export type RuntimeSelectionRepairResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
id: string;
|
|
5
|
+
diagnostics?: CallSelectionDiagnostics;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
reason: string;
|
|
9
|
+
diagnostics?: CallSelectionDiagnostics;
|
|
10
|
+
};
|
|
11
|
+
export declare function repairRuntimeSelection(input: {
|
|
12
|
+
id: string;
|
|
13
|
+
candidates: CallCandidate[];
|
|
14
|
+
}): Promise<RuntimeSelectionRepairResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{repairCallSelection as a}from"@botbotgo/better-call";export async function repairRuntimeSelection(i){const t=await a({rawIntent:i.id,candidates:i.candidates,mode:"repair"});return t.ok?{ok:!0,id:t.candidateId,diagnostics:t.diagnostics}:{ok:!1,reason:t.reason,diagnostics:t.diagnostics}}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApprovalQueue } from "@stable-harness/governance";
|
|
2
2
|
import type { MemoryProvider, RuntimeMemoryStore } from "@stable-harness/memory";
|
|
3
|
+
import type { QualityReviewModel } from "./quality/index.js";
|
|
3
4
|
import type { CompiledWorkspace, RuntimeCapabilityModule, RuntimeToolGateway, RuntimeAdapter, RuntimeStore, RuntimeProgressNarrationOptions, RuntimeWorkflowAdapter, StableHarnessRuntime } from "./types.js";
|
|
4
5
|
type RuntimeFactoryInput = {
|
|
5
6
|
workspace: CompiledWorkspace;
|
|
@@ -11,6 +12,7 @@ type RuntimeFactoryInput = {
|
|
|
11
12
|
toolGateway?: RuntimeToolGateway;
|
|
12
13
|
store?: RuntimeStore;
|
|
13
14
|
progressNarration?: RuntimeProgressNarrationOptions | false;
|
|
15
|
+
qualityReviewModel?: QualityReviewModel;
|
|
14
16
|
capabilities?: RuntimeCapabilityModule[];
|
|
15
17
|
};
|
|
16
18
|
export declare function createStableHarnessRuntime(input: RuntimeFactoryInput): StableHarnessRuntime;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{assertExecutionContract as t}from"./execution-contract.js";import{projectRequestInspection as r,projectRequestSummary as s,projectSessionSummaries as o}from"./runtime/persistence/inspection.js";import{assertNoRawToolCallOutput as a,assertNoToolExecutionErrorOutput as n,buildAdapterErrorRecoveryPrompt as i,buildEvidenceSynthesisOutput as u,buildExecutionContractRecoveryRequest as c,buildResultRecoveryRequest as l,containsRawToolCallOutput as p,isRecoverableAdapterError as d,rawToolCallFailureMessage as m,toolCallRecoveryEnabled as w}from"./recovery/tool-call.js";import{
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{assertExecutionContract as t}from"./execution-contract.js";import{projectRequestInspection as r,projectRequestSummary as s,projectSessionSummaries as o}from"./runtime/persistence/inspection.js";import{assertNoRawToolCallOutput as a,assertNoToolExecutionErrorOutput as n,buildAdapterErrorRecoveryPrompt as i,buildEvidenceSynthesisOutput as u,buildExecutionContractRecoveryRequest as c,buildResultRecoveryRequest as l,containsRawToolCallOutput as p,isRecoverableAdapterError as d,rawToolCallFailureMessage as m,toolCallRecoveryEnabled as w}from"./recovery/tool-call.js";import{recoverQualityReview as f,resolveQualityPolicy as y}from"./quality/index.js";import{completeRun as R,failRun as g}from"./runtime/completion.js";import{runDirectToolCall as I}from"./runtime/direct-tool-call.js";import{createRuntimeCapabilityRegistry as q,normalizeAdapterResult as k}from"./runtime/capabilities.js";import{createMemoryRuntimeCapability as v}from"./runtime/memory.js";import{createInMemoryRuntimeStore as b}from"./runtime/persistence/stores.js";import{createProgressNarrationCapability as A}from"./runtime/progress-narration.js";import{repairRuntimeSelection as C}from"./runtime/selection-repair.js";import{runWorkflowRequest as x}from"./workflows/runtime.js";export function createStableHarnessRuntime(t){const a=new Set,n=t.store??b(),u=q([v(t),A({options:t.progressNarration,policy:t.workspace.runtime}),...t.capabilities??[]]),emitBase=e=>{n.appendEvent(e);for(const t of a)t(e)},emit=e=>{emitBase(e),u.emitSideEffects(e,emitBase)};return{request:async r=>async function runRuntimeRequest(t){const{agent:r,adapter:s}=await async function resolveExecution(e,t){const r=t.agentId?await async function resolveRequestedAgentId(e,t){if(e.agents.has(t))return t;const r=await C({id:t,candidates:[...e.agents.values()].map(e=>({id:e.id,description:e.description}))});return r.ok?r.id:t}(e.workspace,t.agentId):function resolveRoutedAgentId(e,t){for(const r of e.runtime.routes??[])if(routeMatches(r,t))return r.agentId;return e.runtime.defaultAgentId}(e.workspace,t.input),s=e.workspace.agents.get(r);if(!s)throw new Error(`Agent ${r} is not defined in the workspace`);if(t.toolCall||t.workflow)return{agent:s,adapter:void 0};const o=e.adapters.find(e=>e.canRun(s));if(!o)throw new Error(`No runtime adapter can run backend ${s.backend} for agent ${s.id}`);return{agent:s,adapter:o}}(t.input,t.request),o=t.request.requestId??e(),a=t.request.sessionId??e();t.store.createRun(function createRunRecord(e,t,r,s){return{requestId:t,sessionId:r,agentId:s.id,input:e.input,state:"running",parentRunId:e.parentRunId,metadata:e.metadata,artifacts:[],startedAt:(new Date).toISOString(),events:[]}}(t.request,o,a,r)),t.emit({type:"runtime.request.started",requestId:o,sessionId:a,agentId:r.id,input:t.request.input});try{if(t.request.workflow){const e=await x({workspace:t.input.workspace,adapters:t.input.workflowAdapters??[],toolGateway:t.input.toolGateway,request:{input:t.request.input,...t.request.workflow},requestId:o,sessionId:a,agentId:r.id,emit:t.emit});return R({store:t.store,emit:t.emit,requestId:o,sessionId:a,agent:r,result:e})}if(t.request.toolCall){const e=await I({gateway:t.input.toolGateway,workspace:t.input.workspace,emit:t.emit,request:t.request,requestId:o,sessionId:a,agent:r});return R({store:t.store,emit:t.emit,requestId:o,sessionId:a,agent:r,result:e})}return await async function runAdapterRequest(e){if(!e.adapter)throw new Error(`No runtime adapter can run backend ${e.agent.backend} for agent ${e.agent.id}`);const t=e.adapter,r=await e.capabilities.beforeAdapterRun(createCapabilityContext(e)),s=r.memory,o=r.pluginMemories??[],a=e.input.workspace.runtime,n=y(e.input.workspace.runtime,e.agent),u=new Map;let l;try{l=await runAdapterOnce(e,t,e.request,s,o,u)}catch(r){if(!d(r,a))throw r;l=await runAdapterOnce(e,t,i(e.request,r,a),s,o,u)}l=await recoverAdapterResultOutput(e,t,e.request,l,s,o,a,u),l=await f(createQualityRuntimeInput(e,s,o,u),e.request,l,n),await e.capabilities.beforeAdapterResultContract({...createCapabilityContext(e),result:l});try{assertRequestExecutionContract(e)}catch(r){const i=c({request:e.request,events:e.store.getRun(e.requestId)?.events??[],policy:a});if(!i)throw r;l=await runAdapterOnce(e,t,i,s,o,u),l=await recoverAdapterResultOutput(e,t,i,l,s,o,a,u),l=await f(createQualityRuntimeInput(e,s,o,u),i,l,n),assertRequestExecutionContract(e)}const p=R({store:e.store,emit:e.emit,requestId:e.requestId,sessionId:e.sessionId,agent:e.agent,result:l});return await e.capabilities.afterAdapterResponse({...createCapabilityContext(e),result:l,response:p}),p}({...t,adapter:s,requestId:o,sessionId:a,agent:r})}catch(e){return g({store:t.store,emit:t.emit,requestId:o,sessionId:a,agent:r,error:e})}}({input:t,capabilities:u,store:n,emit:emit,request:r}),subscribe:e=>(a.add(e),()=>a.delete(e)),inspect(){return{workspaceRoot:t.workspace.root,agents:[...t.workspace.agents.keys()].sort(),workflows:[...t.workspace.workflows.keys()].sort(),...t.workspace.runtime.specDrivenWorkflow?{specDrivenWorkflow:(e=t.workspace.runtime.specDrivenWorkflow,{enabled:e.enabled,artifactsDir:e.artifactsDir,...e.constitution?{constitution:e.constitution}:{},phases:e.phases.map(e=>e.id)})}:{},evaluations:[...t.workspace.evaluations?.keys()??[]].sort(),...t.workspace.runtime.workflowRouting?.defaultWorkflowId?{defaultWorkflowId:t.workspace.runtime.workflowRouting.defaultWorkflowId}:{},workflowRoutes:(t.workspace.runtime.workflowRouting?.routes??[]).map(e=>e.id).sort(),models:[...t.workspace.models.keys()].sort(),tools:[...t.workspace.tools.keys()].sort(),runs:n.listRuns()};var e},getRuntimePolicy:()=>t.workspace.runtime,getWorkflow:e=>t.workspace.workflows.get(e),getRun:e=>n.getRun(e),listRequests:e=>n.listRuns(e).map(s),listSessions:()=>o(n.listRuns()),inspectRequest(e){const s=n.getRun(e);return s?r(t.workspace,s):void 0},cancel(e,t){const r=n.getRun(e);r&&"running"===r.state&&(n.updateRun(e,{state:"cancelled",completedAt:(new Date).toISOString()}),emit({type:"runtime.request.cancelled",requestId:e,sessionId:r.sessionId,agentId:r.agentId,reason:t}))},async stop(){await u.stop(),a.clear()}}}function createCapabilityContext(e){return{workspace:e.input.workspace,store:e.store,emit:e.emit,request:e.request,requestId:e.requestId,sessionId:e.sessionId,agent:e.agent}}function createQualityRuntimeInput(e,t,r,s){return{workspace:e.input.workspace,agent:e.agent,request:e.request,requestId:e.requestId,sessionId:e.sessionId,events:e.store.getRun(e.requestId)?.events??[],emit:e.emit,getEvents:()=>e.store.getRun(e.requestId)?.events??[],runAdapter:o=>runAdapterOnce(e,e.adapter,o,t,r,s),reviewModel:e.input.qualityReviewModel,memory:t,pluginMemories:r}}async function recoverAdapterResultOutput(e,t,r,s,o,i,c,d){let f=r;const y=function resultRecoveryAttempts(e){const t="object"!=typeof e||null===e||Array.isArray(e)?void 0:e.recovery,r="object"!=typeof t||null===t||Array.isArray(t)?void 0:t.toolCall,s="object"!=typeof r||null===r||Array.isArray(r)?void 0:r.maxResultRecoveryAttempts;return"number"==typeof s&&Number.isInteger(s)&&s>0?s:3}(c);let R=0;for(let r=0;r<y;r+=1){const r=e.store.getRun(e.requestId)?.events??[],a=l({request:f,output:s.text,events:r.slice(R),availableToolIds:e.agent.tools,policy:c});if(!a)break;f=a,R=e.store.getRun(e.requestId)?.events.length??0,s=await runAdapterOnce(e,t,a,o,i,d)}if(w(c)){let t=!1;p(s.text,c)&&function rawToolCallFailureReturnsMessage(e){return"message"===("object"!=typeof e?.toolCallRecovery||null===e.toolCallRecovery||Array.isArray(e.toolCallRecovery)?{}:e.toolCallRecovery).onFailure}(r.metadata)&&(s={...s,text:m(),metadata:{...s.metadata,toolCallRecovery:{failed:!0,reason:"raw_tool_call_output"}}});const o=u({request:r,output:s.text,events:e.store.getRun(e.requestId)?.events??[],policy:c});o&&(t=!0,s={...s,text:o,metadata:{...s.metadata,toolCallRecovery:{synthesized:!0,reason:"raw_tool_call_output_with_evidence"}}}),t||(a(s.text,c),n(s.text,c))}return s}function assertRequestExecutionContract(e){t({store:e.store,emit:e.emit,requestId:e.requestId,sessionId:e.sessionId,agent:e.agent,metadata:e.request.metadata})}async function runAdapterOnce(e,t,r,s,o,a){return k(await t.run({workspace:e.input.workspace,agent:e.agent,request:r,requestId:e.requestId,sessionId:e.sessionId,memory:s,pluginMemories:o,toolGateway:e.input.toolGateway,requestState:a,emit:e.emit}))}function routeMatches(e,t){if(e.pattern)try{if(new RegExp(e.pattern,"iu").test(t))return!0}catch{return!1}const r=t.toLowerCase();return(e.keywords??[]).some(e=>r.includes(e.toLowerCase()))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export async function runWorkflowRequest(
|
|
1
|
+
import{repairRuntimeSelection as e}from"../runtime/selection-repair.js";export async function runWorkflowRequest(o){const r=await async function resolveWorkflow(o,r){const t=r.routeId?await async function resolveWorkflowRouteId(o,r){const t=o.runtime.workflowRouting?.routes??[];if(t.some(e=>e.id===r))return r;const n=await e({id:r,candidates:t.map(e=>({id:e.id,description:e.description,metadata:e.metadata}))});return n.ok?n.id:r}(o,r.routeId):void 0,n=t?o.runtime.workflowRouting?.routes?.find(e=>e.id===t):void 0;if(r.routeId&&!n)throw new Error(`Workflow route is not defined: ${r.routeId}`);const i=(r.workflowId?await async function resolveWorkflowId(o,r){if(o.workflows.has(r))return r;const t=await e({id:r,candidates:[...o.workflows.values()].map(e=>({id:e.id,description:e.description}))});return t.ok?t.id:r}(o,r.workflowId):void 0)??n?.workflowId??o.runtime.workflowRouting?.defaultWorkflowId;if(!i)throw new Error("Workflow request requires workflowId, routeId, or runtime.workflowRouting.defaultWorkflowId");const s=o.workflows.get(i);if(!s)throw new Error(`Workflow is not defined: ${i}`);return s}(o.workspace,o.request),t=function resolveWorkflowAdapter(e,o){const r=o?e.find(e=>e.name===o):e[0];if(!r)throw new Error("No workflow adapter is configured"+(o?` for ${o}`:""));return r}(o.adapters,r.adapter);o.emit({type:"runtime.workflow.started",requestId:o.requestId,sessionId:o.sessionId,agentId:o.agentId,adapter:t.name,workflowId:r.id});const n=await t.run({workspace:o.workspace,workflow:r,request:o.request,requestId:o.requestId,sessionId:o.sessionId,toolGateway:o.toolGateway,emit:e=>o.emit({type:"runtime.adapter.event",requestId:o.requestId,sessionId:o.sessionId,agentId:o.agentId,event:e})});return o.emit({type:"runtime.workflow.completed",requestId:o.requestId,sessionId:o.sessionId,agentId:o.agentId,adapter:t.name,workflowId:r.id}),"string"==typeof n?{text:n}:n}
|
|
@@ -49,6 +49,7 @@ export type WorkspaceAgent = {
|
|
|
49
49
|
};
|
|
50
50
|
export type WorkspaceRuntimePolicy = {
|
|
51
51
|
defaultAgentId: string;
|
|
52
|
+
routes?: WorkspaceAgentRoute[];
|
|
52
53
|
workspaceId?: string;
|
|
53
54
|
profile?: string;
|
|
54
55
|
workspaceValidation?: WorkspaceValidationPolicy;
|
|
@@ -63,9 +64,17 @@ export type WorkspaceRuntimePolicy = {
|
|
|
63
64
|
protocols?: Record<string, unknown>;
|
|
64
65
|
progress?: Record<string, unknown>;
|
|
65
66
|
cli?: Record<string, unknown>;
|
|
67
|
+
quality?: string | Record<string, unknown>;
|
|
66
68
|
responseLanguage?: Record<string, unknown>;
|
|
67
69
|
responsePresentation?: Record<string, unknown>;
|
|
68
70
|
};
|
|
71
|
+
export type WorkspaceAgentRoute = {
|
|
72
|
+
id: string;
|
|
73
|
+
agentId: string;
|
|
74
|
+
keywords?: string[];
|
|
75
|
+
pattern?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
};
|
|
69
78
|
export type WorkspaceValidationPolicy = {
|
|
70
79
|
boundaryScan?: WorkspaceBoundaryScanPolicy;
|
|
71
80
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"node:path";import{pathToFileURL as e}from"node:url";import{createInMemoryToolGateway as t}from"./in-memory.js";export async function createModuleToolGateway(o){const e=[];for(const t of o.tools){const o=await loadModuleTool(t);o&&e.push(o)}return t(e,o.options)}async function loadModuleTool(t){if(!t.sourcePath)return;const n=await import(e(o.resolve(t.sourcePath)).href),r=n[t.name??t.id]??n.default;return function hasInvoke(o){return"object"==typeof o&&null!==o&&"invoke"in o&&"function"==typeof o.invoke}(r)?{id:t.id,description:t.description??r.description,schema:t.schema??r.schema,validateArgs:r.validateArgs,invoke:(o,e)=>async function invokeWithWorkspaceCwd(o,e,t){const n=process.cwd();try{return process.chdir(t.workspaceRoot),await o(e)}finally{process.chdir(n)}}(r.invoke,o,e)}:void 0}
|
|
1
|
+
import o from"node:path";import{pathToFileURL as e}from"node:url";import{createInMemoryToolGateway as t}from"./in-memory.js";export async function createModuleToolGateway(o){const e=[];for(const t of o.tools){const o=await loadModuleTool(t);o&&e.push(o)}return t(e,o.options)}async function loadModuleTool(t){if(!t.sourcePath)return;const n=await import(e(o.resolve(t.sourcePath)).href),r=n[t.name??t.id]??n.default;return function hasInvoke(o){return"object"==typeof o&&null!==o&&"invoke"in o&&"function"==typeof o.invoke}(r)?{id:t.id,description:t.description??r.description,schema:t.schema??r.schema,validateArgs:r.validateArgs,invoke:(o,e)=>async function invokeWithWorkspaceCwd(o,e,t){const n=process.cwd();try{return process.chdir(t.workspaceRoot),await o(e,t)}finally{process.chdir(n)}}(r.invoke,o,e)}:void 0}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{assertSpecDrivenWorkflowPolicy as e,createSpecDrivenWorkflowPolicy as r}from"@stable-harness/core";function assertRecord(e,r){if("object"!=typeof e||null===e||Array.isArray(e))throw new Error(`${r} must be an object`);return e}function readName(e,r){const t=e.metadata?.name;if("string"==typeof t&&t.trim())return t.trim();if(r)return r;throw new Error(`Document kind ${String(e.kind)} requires metadata.name`)}function readDescription(e){const r=e.metadata?.description;return"string"==typeof r&&r.trim()?r.trim():void 0}function readOptionalString(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function toStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.trim().length>0):[]}function resolveValue(e){if("string"!=typeof e)return e;const r=e.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return r?process.env[r[1]]??r[2]??"":e}export function compileRuntime(e){const r=assertRecord(e.spec,"Runtime.spec"),t=assertRecord(r.routing??{},"Runtime.spec.routing");return{defaultAgentId:"string"==typeof t.defaultAgentId&&t.defaultAgentId.trim()?t.defaultAgentId.trim():"orchestra",...readOptionalString(r.workspaceId)?{workspaceId:readOptionalString(r.workspaceId)}:{},...readOptionalString(r.profile)?{profile:readOptionalString(r.profile)}:{},...void 0!==r.adapters?{adapters:readAdapters(r.adapters)}:{},..."object"==typeof r.workflowRouting&&r.workflowRouting?{workflowRouting:readWorkflowRouting(r.workflowRouting)}:{},..."object"==typeof r.specDrivenWorkflow&&r.specDrivenWorkflow?{specDrivenWorkflow:readSpecDrivenWorkflow(r.specDrivenWorkflow)}:{},..."object"==typeof r.approvals&&r.approvals?{approvals:r.approvals}:{},..."object"==typeof r.recovery&&r.recovery?{recovery:r.recovery}:{},..."object"==typeof r.retry&&r.retry?{retry:r.retry}:{},..."object"==typeof r.toolGateway&&r.toolGateway?{toolGateway:r.toolGateway}:{},..."object"==typeof r.memory&&r.memory?{memory:r.memory}:{},..."object"==typeof r.protocols&&r.protocols?{protocols:r.protocols}:{},..."object"==typeof r.progress&&r.progress?{progress:r.progress}:{},..."object"==typeof r.cli&&r.cli?{cli:r.cli}:{},..."object"==typeof r.workspaceValidation&&r.workspaceValidation?{workspaceValidation:r.workspaceValidation}:{},..."object"==typeof r.responseLanguage&&r.responseLanguage?{responseLanguage:r.responseLanguage}:{},..."object"==typeof r.responsePresentation&&r.responsePresentation?{responsePresentation:r.responsePresentation}:{}}}function readSpecDrivenWorkflow(t){const o=assertRecord(t,"Runtime.spec.specDrivenWorkflow"),n=r({enabled:!0===o.enabled,constitution:readOptionalString(o.constitution),artifactsDir:readOptionalString(o.artifactsDir),phases:void 0===o.phases?void 0:readSpecDrivenPhases(o.phases),..."object"==typeof o.gates&&o.gates?{gates:o.gates}:{},..."object"==typeof o.config&&o.config?{config:o.config}:{}});return e(n),n}function readSpecDrivenPhases(e){if(!Array.isArray(e))throw new Error("Runtime.spec.specDrivenWorkflow.phases must be an array");return e.map(e=>{if("string"==typeof e&&e.trim())return{id:e.trim()};const r=assertRecord(e,"Runtime.spec.specDrivenWorkflow.phases[]"),t=readOptionalString(r.id);if(!t)throw new Error("Runtime.spec.specDrivenWorkflow.phases[] requires id");return{id:t,...readOptionalString(r.artifactKind)?{artifactKind:readOptionalString(r.artifactKind)}:{},..."boolean"==typeof r.required?{required:r.required}:{},...readOptionalString(r.gate)?{gate:readOptionalString(r.gate)}:{},..."object"==typeof r.config&&r.config?{config:r.config}:{}}})}export function compileAgent(e,r){const t=assertRecord(e.spec,"Agent.spec"),o=readName(e),n=readOptionalString(t.backend);if(!n)throw new Error(`Agent ${o} requires spec.backend`);const i="object"==typeof t.config&&t.config?t.config:{},a="string"==typeof t.systemPrompt?t.systemPrompt:"string"==typeof i.systemPrompt?i.systemPrompt:void 0;return{id:o,...readDescription(e)?{description:readDescription(e)}:{},sourcePath:r,backend:n,..."string"==typeof t.modelRef&&t.modelRef.trim()?{modelRef:(s=t.modelRef,s.replace(/^[^/]+\//u,""))}:{},...void 0!==a?{systemPrompt:a}:{},tools:toStringArray(t.tools),skills:toStringArray(t.skills),memory:Array.isArray(t.memory)?t.memory:[],subagents:toStringArray(t.subagents),...void 0!==t.edges?{edges:readAgentEdges(t.edges,o)}:{},config:i};var s}export function compileModel(e){return compileModelSpec(assertRecord(e.spec,"Model.spec"),readName(e))}export function compileModelSpec(e,r){const t="string"==typeof e.name&&e.name.trim()?e.name.trim():r??"default",o=resolveValue(e.provider),n=resolveValue(e.model),i="string"==typeof o&&o.trim()?o.trim():"unknown",a="string"==typeof n&&n.trim()?n.trim():t,s={...e};return delete s.name,delete s.provider,delete s.model,{id:t,provider:i,model:a,config:Object.fromEntries(Object.entries(s).map(([e,r])=>[e,resolveValue(r)]))}}export function compileTool(e,r){const t=assertRecord(e.spec,"Tool.spec");return{id:readName(e),...r?{sourcePath:r}:{},..."string"==typeof t.description?{description:t.description}:{},...void 0!==t.schema?{schema:t.schema}:{},..."string"==typeof t.implementation?{implementation:t.implementation}:{}}}export function compileMemory(e){const r=assertRecord(e.spec,"Memory.spec"),t=readName(e),o={...r};return delete o.provider,delete o.profile,delete o.mode,delete o.enabled,delete o.prompts,{id:t,provider:readOptionalString(r.provider)??"langmem",...readOptionalString(r.profile)?{profile:readOptionalString(r.profile)}:{},...readOptionalString(r.mode)?{mode:readOptionalString(r.mode)}:{},enabled:!1!==r.enabled,..."object"==typeof r.prompts&&r.prompts?{prompts:readMemoryPrompts(r.prompts)}:{},...Object.keys(o).length>0?{config:o}:{}}}function readWorkflowRouting(e){const r=assertRecord(e,"Runtime.spec.workflowRouting"),t=void 0===r.routes?void 0:function readWorkflowRoutes(e){if(!Array.isArray(e))throw new Error("Runtime.spec.workflowRouting.routes must be an array");return e.map(e=>{const r=assertRecord(e,"Runtime.spec.workflowRouting.routes[]"),t=readOptionalString(r.id),o=readOptionalString(r.workflowId);if(!t||!o)throw new Error("Runtime.spec.workflowRouting.routes[] requires id and workflowId");return{id:t,workflowId:o,...readOptionalString(r.description)?{description:readOptionalString(r.description)}:{},..."object"==typeof r.metadata&&r.metadata?{metadata:r.metadata}:{}}})}(r.routes);return{...readOptionalString(r.defaultWorkflowId)?{defaultWorkflowId:readOptionalString(r.defaultWorkflowId)}:{},...t?{routes:t}:{}}}function readAdapters(e){if(!Array.isArray(e))throw new Error("Runtime.spec.adapters must be an array");return e.map(readAdapter)}function readAgentEdges(e,r){if(!Array.isArray(e))throw new Error(`Agent ${r} spec.edges must be an array`);return e.map(e=>{const t=assertRecord(e,`Agent ${r} spec.edges[]`),o=readOptionalString(t.from),n=readOptionalString(t.to);if(!o||!n)throw new Error(`Agent ${r} spec.edges[] requires from and to`);return{from:o,to:n,...readOptionalString(t.condition)?{condition:readOptionalString(t.condition)}:{}}})}function readAdapter(e){if("string"==typeof e&&e.trim())return{name:e.trim()};const r=assertRecord(e,"Runtime.spec.adapters[]"),t=readOptionalString(r.name)??readOptionalString(r.id)??readOptionalString(r.backend);if(!t)throw new Error("Runtime.spec.adapters[] requires name");return{name:t,..."boolean"==typeof r.enabled?{enabled:r.enabled}:{},..."object"==typeof r.config&&r.config?{config:r.config}:{}}}function readMemoryPrompts(e){const r=assertRecord(e,"Memory.spec.prompts");return{...readOptionalString(r.semantic)?{semantic:readOptionalString(r.semantic)}:{},...readOptionalString(r.episodic)?{episodic:readOptionalString(r.episodic)}:{},...readOptionalString(r.procedural)?{procedural:readOptionalString(r.procedural)}:{}}}
|
|
1
|
+
import{assertSpecDrivenWorkflowPolicy as e,createSpecDrivenWorkflowPolicy as r}from"@stable-harness/core";function assertRecord(e,r){if("object"!=typeof e||null===e||Array.isArray(e))throw new Error(`${r} must be an object`);return e}function readName(e,r){const t=e.metadata?.name;if("string"==typeof t&&t.trim())return t.trim();if(r)return r;throw new Error(`Document kind ${String(e.kind)} requires metadata.name`)}function readDescription(e){const r=e.metadata?.description;return"string"==typeof r&&r.trim()?r.trim():void 0}function readOptionalString(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function toStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.trim().length>0):[]}function resolveValue(e){if("string"!=typeof e)return e;const r=e.match(/^\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-(.*))?\}$/u);return r?process.env[r[1]]??r[2]??"":e}export function compileRuntime(e){const r=assertRecord(e.spec,"Runtime.spec"),t=assertRecord(r.routing??{},"Runtime.spec.routing");return{defaultAgentId:"string"==typeof t.defaultAgentId&&t.defaultAgentId.trim()?t.defaultAgentId.trim():"orchestra",...void 0!==t.routes?{routes:readAgentRoutes(t.routes)}:{},...readOptionalString(r.workspaceId)?{workspaceId:readOptionalString(r.workspaceId)}:{},...readOptionalString(r.profile)?{profile:readOptionalString(r.profile)}:{},...void 0!==r.adapters?{adapters:readAdapters(r.adapters)}:{},..."object"==typeof r.workflowRouting&&r.workflowRouting?{workflowRouting:readWorkflowRouting(r.workflowRouting)}:{},..."object"==typeof r.specDrivenWorkflow&&r.specDrivenWorkflow?{specDrivenWorkflow:readSpecDrivenWorkflow(r.specDrivenWorkflow)}:{},..."object"==typeof r.approvals&&r.approvals?{approvals:r.approvals}:{},..."object"==typeof r.recovery&&r.recovery?{recovery:r.recovery}:{},..."object"==typeof r.retry&&r.retry?{retry:r.retry}:{},..."object"==typeof r.toolGateway&&r.toolGateway?{toolGateway:r.toolGateway}:{},..."object"==typeof r.memory&&r.memory?{memory:r.memory}:{},..."object"==typeof r.protocols&&r.protocols?{protocols:r.protocols}:{},..."object"==typeof r.progress&&r.progress?{progress:r.progress}:{},..."object"==typeof r.cli&&r.cli?{cli:r.cli}:{},..."string"==typeof r.quality||"object"==typeof r.quality&&r.quality?{quality:r.quality}:{},..."object"==typeof r.workspaceValidation&&r.workspaceValidation?{workspaceValidation:r.workspaceValidation}:{},..."object"==typeof r.responseLanguage&&r.responseLanguage?{responseLanguage:r.responseLanguage}:{},..."object"==typeof r.responsePresentation&&r.responsePresentation?{responsePresentation:r.responsePresentation}:{}}}function readAgentRoutes(e){if(!Array.isArray(e))throw new Error("Runtime.spec.routing.routes must be an array");return e.map(e=>{const r=assertRecord(e,"Runtime.spec.routing.routes[]"),t=readOptionalString(r.id),o=readOptionalString(r.agentId);if(!t||!o)throw new Error("Runtime.spec.routing.routes[] requires id and agentId");const n=void 0===r.keywords?void 0:function assertStringArray(e,r){if(!Array.isArray(e))throw new Error(`${r} must be an array`);return e.map(e=>{if("string"!=typeof e||!e.trim())throw new Error(`${r} must contain non-empty strings`);return e.trim()})}(r.keywords,"Runtime.spec.routing.routes[].keywords"),i=readOptionalString(r.pattern);if(!(n&&0!==n.length||i))throw new Error("Runtime.spec.routing.routes[] requires keywords or pattern");return{id:t,agentId:o,...n&&n.length>0?{keywords:n}:{},...i?{pattern:i}:{},...readOptionalString(r.description)?{description:readOptionalString(r.description)}:{}}})}function readSpecDrivenWorkflow(t){const o=assertRecord(t,"Runtime.spec.specDrivenWorkflow"),n=r({enabled:!0===o.enabled,constitution:readOptionalString(o.constitution),artifactsDir:readOptionalString(o.artifactsDir),phases:void 0===o.phases?void 0:readSpecDrivenPhases(o.phases),..."object"==typeof o.gates&&o.gates?{gates:o.gates}:{},..."object"==typeof o.config&&o.config?{config:o.config}:{}});return e(n),n}function readSpecDrivenPhases(e){if(!Array.isArray(e))throw new Error("Runtime.spec.specDrivenWorkflow.phases must be an array");return e.map(e=>{if("string"==typeof e&&e.trim())return{id:e.trim()};const r=assertRecord(e,"Runtime.spec.specDrivenWorkflow.phases[]"),t=readOptionalString(r.id);if(!t)throw new Error("Runtime.spec.specDrivenWorkflow.phases[] requires id");return{id:t,...readOptionalString(r.artifactKind)?{artifactKind:readOptionalString(r.artifactKind)}:{},..."boolean"==typeof r.required?{required:r.required}:{},...readOptionalString(r.gate)?{gate:readOptionalString(r.gate)}:{},..."object"==typeof r.config&&r.config?{config:r.config}:{}}})}export function compileAgent(e,r){const t=assertRecord(e.spec,"Agent.spec"),o=readName(e),n=readOptionalString(t.backend);if(!n)throw new Error(`Agent ${o} requires spec.backend`);const i="object"==typeof t.config&&t.config?t.config:{},a="string"==typeof t.systemPrompt?t.systemPrompt:"string"==typeof i.systemPrompt?i.systemPrompt:void 0;return{id:o,...readDescription(e)?{description:readDescription(e)}:{},sourcePath:r,backend:n,..."string"==typeof t.modelRef&&t.modelRef.trim()?{modelRef:(s=t.modelRef,s.replace(/^[^/]+\//u,""))}:{},...void 0!==a?{systemPrompt:a}:{},tools:toStringArray(t.tools),skills:toStringArray(t.skills),memory:Array.isArray(t.memory)?t.memory:[],subagents:toStringArray(t.subagents),...void 0!==t.edges?{edges:readAgentEdges(t.edges,o)}:{},config:i};var s}export function compileModel(e){return compileModelSpec(assertRecord(e.spec,"Model.spec"),readName(e))}export function compileModelSpec(e,r){const t="string"==typeof e.name&&e.name.trim()?e.name.trim():r??"default",o=resolveValue(e.provider),n=resolveValue(e.model),i="string"==typeof o&&o.trim()?o.trim():"unknown",a="string"==typeof n&&n.trim()?n.trim():t,s={...e};return delete s.name,delete s.provider,delete s.model,{id:t,provider:i,model:a,config:Object.fromEntries(Object.entries(s).map(([e,r])=>[e,resolveValue(r)]))}}export function compileTool(e,r){const t=assertRecord(e.spec,"Tool.spec");return{id:readName(e),...r?{sourcePath:r}:{},..."string"==typeof t.description?{description:t.description}:{},...void 0!==t.schema?{schema:t.schema}:{},..."string"==typeof t.implementation?{implementation:t.implementation}:{}}}export function compileMemory(e){const r=assertRecord(e.spec,"Memory.spec"),t=readName(e),o={...r};return delete o.provider,delete o.profile,delete o.mode,delete o.enabled,delete o.prompts,{id:t,provider:readOptionalString(r.provider)??"langmem",...readOptionalString(r.profile)?{profile:readOptionalString(r.profile)}:{},...readOptionalString(r.mode)?{mode:readOptionalString(r.mode)}:{},enabled:!1!==r.enabled,..."object"==typeof r.prompts&&r.prompts?{prompts:readMemoryPrompts(r.prompts)}:{},...Object.keys(o).length>0?{config:o}:{}}}function readWorkflowRouting(e){const r=assertRecord(e,"Runtime.spec.workflowRouting"),t=void 0===r.routes?void 0:function readWorkflowRoutes(e){if(!Array.isArray(e))throw new Error("Runtime.spec.workflowRouting.routes must be an array");return e.map(e=>{const r=assertRecord(e,"Runtime.spec.workflowRouting.routes[]"),t=readOptionalString(r.id),o=readOptionalString(r.workflowId);if(!t||!o)throw new Error("Runtime.spec.workflowRouting.routes[] requires id and workflowId");return{id:t,workflowId:o,...readOptionalString(r.description)?{description:readOptionalString(r.description)}:{},..."object"==typeof r.metadata&&r.metadata?{metadata:r.metadata}:{}}})}(r.routes);return{...readOptionalString(r.defaultWorkflowId)?{defaultWorkflowId:readOptionalString(r.defaultWorkflowId)}:{},...t?{routes:t}:{}}}function readAdapters(e){if(!Array.isArray(e))throw new Error("Runtime.spec.adapters must be an array");return e.map(readAdapter)}function readAgentEdges(e,r){if(!Array.isArray(e))throw new Error(`Agent ${r} spec.edges must be an array`);return e.map(e=>{const t=assertRecord(e,`Agent ${r} spec.edges[]`),o=readOptionalString(t.from),n=readOptionalString(t.to);if(!o||!n)throw new Error(`Agent ${r} spec.edges[] requires from and to`);return{from:o,to:n,...readOptionalString(t.condition)?{condition:readOptionalString(t.condition)}:{}}})}function readAdapter(e){if("string"==typeof e&&e.trim())return{name:e.trim()};const r=assertRecord(e,"Runtime.spec.adapters[]"),t=readOptionalString(r.name)??readOptionalString(r.id)??readOptionalString(r.backend);if(!t)throw new Error("Runtime.spec.adapters[] requires name");return{name:t,..."boolean"==typeof r.enabled?{enabled:r.enabled}:{},..."object"==typeof r.config&&r.config?{config:r.config}:{}}}function readMemoryPrompts(e){const r=assertRecord(e,"Memory.spec.prompts");return{...readOptionalString(r.semantic)?{semantic:readOptionalString(r.semantic)}:{},...readOptionalString(r.episodic)?{episodic:readOptionalString(r.episodic)}:{},...readOptionalString(r.procedural)?{procedural:readOptionalString(r.procedural)}:{}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as o}from"node:fs/promises";import
|
|
1
|
+
import{readFile as o}from"node:fs/promises";import e from"node:path";import{parseAllDocuments as t}from"yaml";import{discoverModuleTools as s,discoverSkills as r,listYamlFiles as n}from"./discovery.js";import{compileAgent as a,compileMemory as l,compileModel as i,compileModelSpec as c,compileRuntime as f,compileTool as u}from"./documents.js";import{compileWorkflow as w,validateWorkflows as m}from"./workflows.js";import{compileEvaluation as d,validateEvaluations as p}from"./evaluations.js";import{assertWorkspaceBoundaryDiagnostics as k,scanWorkspaceBoundaries as g}from"./boundary-scan.js";export async function loadWorkspaceFromYaml(a){const l=e.join(a,"config"),i=await n(l),c=[],f=new Map,u=new Map,w=new Map,d=new Map,v=new Map,M=new Map,y=new Map;for(const e of i){const s=await o(e,"utf8"),r=t(s).map(o=>o.toJSON()).filter(o=>null!==o);for(const o of r)collectWorkspaceDocument(o,e,{runtimeDocs:c,agents:f,models:u,tools:w,memories:v,workflows:M,evaluations:y})}for(const o of await s(a))w.has(o.id)||w.set(o.id,o);for(const o of await r(a))d.set(o.id,o);const W=c.at(-1)??{defaultAgentId:"orchestra"};m({workflows:M,agents:f,tools:w,skills:d}),p({evaluations:y,agents:f,tools:w,workflows:M}),function validateAgentRouting(o,e){for(const t of o.routes??[])if(!e.has(t.agentId))throw new Error(`Runtime routing route ${t.id} references unknown agent ${t.agentId}`)}(W,f),function validateWorkflowRouting(o,e){const t=o.workflowRouting;if(t){if(t.defaultWorkflowId&&!e.has(t.defaultWorkflowId))throw new Error(`Runtime workflowRouting.defaultWorkflowId references unknown workflow ${t.defaultWorkflowId}`);for(const o of t.routes??[])if(!e.has(o.workflowId))throw new Error(`Runtime workflowRouting route ${o.id} references unknown workflow ${o.workflowId}`)}}(W,M);const h={root:a,runtime:W,agents:f,models:u,tools:w,skills:d,memories:v,workflows:M,evaluations:y},R=g(h);return k(R),{...h,...R.length>0?{diagnostics:R}:{}}}function collectWorkspaceDocument(o,e,t){if("string"==typeof o.kind)switch(o.kind){case"Runtime":return void t.runtimeDocs.push(f(o));case"Agent":return collectOne(t.agents,a(o,e));case"Model":return collectOne(t.models,i(o));case"Models":return function collectModelSpecs(o,e){if(Array.isArray(o.spec))for(const t of o.spec)if("object"==typeof t&&null!==t&&!Array.isArray(t)){const o=c(t);e.set(o.id,o)}}(o,t.models);case"Tool":return collectOne(t.tools,u(o,e));case"Memory":return collectOne(t.memories,l(o));case"Workflow":return collectOne(t.workflows,w(o,e));case"Evaluation":return collectOne(t.evaluations,d(o,e));default:return}}function collectOne(o,e){o.set(e.id,e)}
|