stable-harness 0.0.118 → 0.0.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +6 -0
  2. package/node_modules/@stable-harness/adapter-deepagents/package.json +3 -2
  3. package/node_modules/@stable-harness/adapter-langgraph/package.json +3 -2
  4. package/node_modules/@stable-harness/core/dist/recovery/execution-contract.js +1 -1
  5. package/node_modules/@stable-harness/core/dist/recovery/raw-args.d.ts +1 -0
  6. package/node_modules/@stable-harness/core/dist/recovery/raw-args.js +1 -1
  7. package/node_modules/@stable-harness/core/dist/runtime/recovery/adapter-result.js +1 -1
  8. package/node_modules/@stable-harness/core/package.json +4 -3
  9. package/node_modules/@stable-harness/governance/package.json +2 -1
  10. package/node_modules/@stable-harness/memory/package.json +2 -1
  11. package/node_modules/@stable-harness/protocols/package.json +3 -2
  12. package/node_modules/@stable-harness/tool-gateway/package.json +2 -1
  13. package/node_modules/@stable-harness/workspace-yaml/package.json +3 -2
  14. package/package.json +9 -9
  15. package/packages/adapter-deepagents/package.json +3 -2
  16. package/packages/adapter-langgraph/package.json +3 -2
  17. package/packages/cli/package.json +9 -8
  18. package/packages/core/dist/recovery/execution-contract.js +1 -1
  19. package/packages/core/dist/recovery/raw-args.d.ts +1 -0
  20. package/packages/core/dist/recovery/raw-args.js +1 -1
  21. package/packages/core/dist/runtime/recovery/adapter-result.js +1 -1
  22. package/packages/core/package.json +4 -3
  23. package/packages/evaluation/package.json +3 -2
  24. package/packages/governance/package.json +2 -1
  25. package/packages/memory/package.json +2 -1
  26. package/packages/protocols/package.json +3 -2
  27. package/packages/tool-gateway/package.json +2 -1
  28. package/packages/workspace-yaml/package.json +3 -2
package/README.md CHANGED
@@ -18,6 +18,12 @@ Better Call hardens the tool-call boundary, Stable Harness governs the agent
18
18
  workspace runtime, and Flev turns that governed workspace into user-facing
19
19
  product surfaces such as Studio, chat, embed, review, and delivery workflows.
20
20
 
21
+ ## License
22
+
23
+ Stable Harness is licensed under the Apache License, Version 2.0. Copyright
24
+ 2026 BotBotGo. Please preserve the included license and attribution notices when
25
+ you use, copy, modify, or distribute this software.
26
+
21
27
  ## Why Use It
22
28
 
23
29
  Agent frameworks are good at deciding what an agent should do next. Production
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -15,7 +16,7 @@
15
16
  "@langchain/node-vfs": "^0.1.4",
16
17
  "@langchain/ollama": "^1.2.7",
17
18
  "@langchain/openai": "^1.4.5",
18
- "@stable-harness/core": "0.0.118",
19
+ "@stable-harness/core": "0.0.120",
19
20
  "deepagents": "^1.10.1",
20
21
  "langchain": "^1.4.0"
21
22
  },
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-langgraph",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -11,6 +12,6 @@
11
12
  "types": "dist/src/index.d.ts",
12
13
  "peerDependencies": {
13
14
  "@langchain/langgraph": "^1.3.0",
14
- "@stable-harness/core": "0.0.118"
15
+ "@stable-harness/core": "0.0.120"
15
16
  }
16
17
  }
@@ -1 +1 @@
1
- export function buildExecutionContractRecoveryRequest(e){if(!0!==function readToolCallRecovery(e){if(!isRecord(e))return{};const t=isRecord(e.recovery)?e.recovery:{};return isRecord(t.toolCall)?t.toolCall:{}}(e.policy).enabled)return;const t=function lastExecutionContractFailure(e){for(let t=e.length-1;t>=0;t-=1){const r=e[t];if("runtime.execution.contract.failed"===r?.type)return{reason:r.reason,missing:readStringArray(r.missingEvidenceTools)}}return{missing:[]}}(e.events);return 0!==t.missing.length?"missing_required_evidence_usage"===t.reason?recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`The final answer did not use required executed evidence from: ${t.missing.join(", ")}`,"Synthesize from those required source(s), or call them through normal structured tool calling if their evidence is unavailable.","Do not replace required evidence with unrelated intermediate evidence, progress text, or summaries from other tools."]):recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`Required evidence tool(s) were missing: ${t.missing.join(", ")}`,`For this recovery turn, the only allowed next tool call target(s) are: ${t.missing.join(", ")}`,"The next assistant action must be the backend's structured tool call for one missing required evidence tool, with no prose before it.","Do not call already completed intermediate tools unless they are listed above.","Do not produce a final answer until the required evidence tool call has executed and you have synthesized its result.","Do not print XML, JSON, markdown fences, pseudo tool-call text, plans, or future-intent text in the final answer."],{stableHarnessRequiredEvidenceTools:t.missing}):void 0}function recoverRequest(e,t,r={}){return{...e,input:[e.input,"",...t].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call",...r}}}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):[]}
1
+ import{successfulEvidenceItems as e}from"../quality/event-evidence.js";export function buildExecutionContractRecoveryRequest(e){if(!0!==function readToolCallRecovery(e){if(!isRecord(e))return{};const t=isRecord(e.recovery)?e.recovery:{};return isRecord(t.toolCall)?t.toolCall:{}}(e.policy).enabled)return;const t=function lastExecutionContractFailure(e){for(let t=e.length-1;t>=0;t-=1){const r=e[t];if("runtime.execution.contract.failed"===r?.type)return{reason:r.reason,missing:readStringArray(r.missingEvidenceTools)}}return{missing:[]}}(e.events);if(0===t.missing.length)return;if("missing_required_evidence_usage"===t.reason){const r=recentSuccessfulEvidence(e.events);return recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`The final answer did not use required executed evidence from: ${t.missing.join(", ")}`,"Synthesize from those required source(s), or call them through normal structured tool calling if their evidence is unavailable.","Do not replace required evidence with unrelated intermediate evidence, progress text, or summaries from other tools.",...r.length>0?["","Prior successful tool evidence:",...r]:[]])}const r=recentSuccessfulEvidence(e.events);return recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`Required evidence tool(s) were missing: ${t.missing.join(", ")}`,`For this recovery turn, the only allowed next tool call target(s) are: ${t.missing.join(", ")}`,"Reuse exact values from prior successful tool evidence when building the required tool call. Do not invent replacement paths, IDs, handles, URLs, or parameters.","The next assistant action must be the backend's structured tool call for one missing required evidence tool, with no prose before it.","Do not call already completed intermediate tools unless they are listed above.","Do not produce a final answer until the required evidence tool call has executed and you have synthesized its result.","Do not print XML, JSON, markdown fences, pseudo tool-call text, plans, or future-intent text in the final answer.",...r.length>0?["","Prior successful tool evidence:",...r]:[]],{stableHarnessRequiredEvidenceTools:t.missing})}function recoverRequest(e,t,r={}){return{...e,input:[e.input,"",...t].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call",...r}}}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):[]}function recentSuccessfulEvidence(t){return e(t).slice(-5).map(e=>`- ${e.source}: ${function previewEvidence(e){const t=e.replace(/\s+/gu," ").trim();return t.length>1500?`${t.slice(0,1497)}...`:t}(e.output)}`)}
@@ -7,6 +7,7 @@ export type RawArgsRecoveryInput = {
7
7
  workspace: CompiledWorkspace;
8
8
  toolGateway?: RuntimeToolGateway;
9
9
  events?: RuntimeEvent[];
10
+ candidateToolIds?: string[];
10
11
  policy: unknown;
11
12
  };
12
13
  export declare function buildRawArgsRecoveryRequest(input: RawArgsRecoveryInput): RuntimeRequest | undefined;
@@ -1 +1 @@
1
- import{selectCallCandidateByArgsSchema as e}from"@easynet/better-call";export function buildRawArgsRecoveryRequest(e){if(!toolCallRecoveryEnabled(e.policy))return;const t=matchUniqueRawArgsTool(e);return t?function buildRawArgsRecoveryPrompt(e,t){return{...e,input:[e.input,"","Stable runtime recovery: your previous final answer was a JSON argument object for a declared tool, not the final answer.",`Matched configured tool: ${t.toolId}`,"Continue the same user request by calling that tool through the backend's normal structured tool-calling mechanism with the JSON arguments below.","If the tool call succeeds, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Previous JSON arguments:",JSON.stringify(t.args)].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call"}}}(e.request,t):void 0}export function buildRawArgsToolEvidenceRecoveryRequest(e){return{...e.request,input:[e.request.input,"","Stable runtime recovery: your previous final answer repeated a JSON argument object for a declared tool after a structured tool-call recovery request.",`Matched configured tool: ${e.match.toolId}`,"Stable runtime executed that matched declared tool through the governed tool gateway.","Continue the same user request from the executed evidence below.","If another declared tool is needed, call it through the backend's normal structured tool-calling mechanism.","Otherwise, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool output:",e.toolOutput].join("\n"),metadata:{...e.request.metadata,stableHarnessRecovery:"tool_call"}}}export function hasUniqueRawArgsTool(e){return toolCallRecoveryEnabled(e.policy)&&Boolean(matchUniqueRawArgsTool(e))}export function matchUniqueRawArgsTool(t){const o=function parseStandaloneJsonObject(e){const t=e.trim(),o=t.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu)?.[1]?.trim(),r=o??t;if(r.startsWith("{")&&r.endsWith("}")&&!(r.length>6e3))try{const e=JSON.parse(r);return!isRecord(e)||function isToolCallEnvelope(e){const t=["tool","tool_name","name","type","subagent_type"].some(t=>"string"==typeof e[t]),o=["args","arguments","parameters","kwargs"].some(t=>t in e);return t&&o}(e)||function isRuntimeControlObject(e){return"string"==typeof e.status||"string"==typeof e.error||"string"==typeof e.controlStatus}(e)?void 0:e}catch{return}}(t.output);if(!o)return;const r=e({args:o,candidates:buildCallCandidates(t)});if(r.ok)return{toolId:r.candidateId,args:r.args};const n=function latestSingleToolCandidate(e,t){for(let o=(e?.length??0)-1;o>=0;o-=1){const r=e[o];if(!r||!("diagnostics"in r)||!isRecord(r.diagnostics))continue;const n=readStringArray(r.diagnostics.toolCandidateIds).filter(e=>t.has(e));if(1===n.length)return n[0];if(n.length>1)return}}(t.events,new Set(t.agent.tools));return n?{toolId:n,args:o}:void 0}function buildCallCandidates(e){return e.agent.tools.map(t=>({id:t,schema:e.workspace.tools.get(t)?.schema??e.toolGateway?.get(t)?.schema}))}function toolCallRecoveryEnabled(e){return!!(isRecord(e)&&isRecord(e.recovery)&&isRecord(e.recovery.toolCall))&&!0===e.recovery.toolCall.enabled}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
1
+ import{selectCallCandidateByArgsSchema as e}from"@easynet/better-call";export function buildRawArgsRecoveryRequest(e){if(!toolCallRecoveryEnabled(e.policy))return;const t=matchUniqueRawArgsTool(e);return t?function buildRawArgsRecoveryPrompt(e,t){return{...e,input:[e.input,"","Stable runtime recovery: your previous final answer was a JSON argument object for a declared tool, not the final answer.",`Matched configured tool: ${t.toolId}`,"Continue the same user request by calling that tool through the backend's normal structured tool-calling mechanism with the JSON arguments below.","If the tool call succeeds, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Previous JSON arguments:",JSON.stringify(t.args)].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call"}}}(e.request,t):void 0}export function buildRawArgsToolEvidenceRecoveryRequest(e){return{...e.request,input:[e.request.input,"","Stable runtime recovery: your previous final answer repeated a JSON argument object for a declared tool after a structured tool-call recovery request.",`Matched configured tool: ${e.match.toolId}`,"Stable runtime executed that matched declared tool through the governed tool gateway.","Continue the same user request from the executed evidence below.","If another declared tool is needed, call it through the backend's normal structured tool-calling mechanism.","Otherwise, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool output:",e.toolOutput].join("\n"),metadata:{...e.request.metadata,stableHarnessRecovery:"tool_call"}}}export function hasUniqueRawArgsTool(e){return toolCallRecoveryEnabled(e.policy)&&Boolean(matchUniqueRawArgsTool(e))}export function matchUniqueRawArgsTool(t){const o=function parseStandaloneJsonObject(e){const t=e.trim(),o=t.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu)?.[1]?.trim(),r=o??t;if(r.startsWith("{")&&r.endsWith("}")&&!(r.length>6e3))try{const e=JSON.parse(r);return!isRecord(e)||function isToolCallEnvelope(e){const t=["tool","tool_name","name","type","subagent_type"].some(t=>"string"==typeof e[t]),o=["args","arguments","parameters","kwargs"].some(t=>t in e);return t&&o}(e)||function isRuntimeControlObject(e){return"string"==typeof e.status||"string"==typeof e.error||"string"==typeof e.controlStatus}(e)?void 0:e}catch{return}}(t.output);if(!o)return;const r=e({args:o,candidates:buildCallCandidates(t)});if(r.ok)return{toolId:r.candidateId,args:r.args};const n=function latestSingleToolCandidate(e,t){for(let o=(e?.length??0)-1;o>=0;o-=1){const r=e[o];if(!r||!("diagnostics"in r)||!isRecord(r.diagnostics))continue;const n=readStringArray(r.diagnostics.toolCandidateIds).filter(e=>t.has(e));if(1===n.length)return n[0];if(n.length>1)return}}(t.events,new Set(candidateToolIds(t)));return n?{toolId:n,args:o}:void 0}function buildCallCandidates(e){return candidateToolIds(e).map(t=>({id:t,schema:e.workspace.tools.get(t)?.schema??e.toolGateway?.get(t)?.schema}))}function candidateToolIds(e){const t=new Set(e.agent.tools),o=(e.candidateToolIds??[]).filter(e=>t.has(e));return o.length>0?o:e.agent.tools}function toolCallRecoveryEnabled(e){return!!(isRecord(e)&&isRecord(e.recovery)&&isRecord(e.recovery.toolCall))&&!0===e.recovery.toolCall.enabled}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
@@ -1 +1 @@
1
- import{assertNoDeclaredActionOmissionOutput as e}from"../../recovery/control-omission.js";import{buildRawArgsRecoveryRequest as t,buildRawArgsToolEvidenceRecoveryRequest as o,hasUniqueRawArgsTool as r,matchUniqueRawArgsTool as a}from"../../recovery/raw-args.js";import{assertNoProgressOnlyToolIntentOutput as s,assertNoRawToolCallOutput as l,assertNoRawToolResultOutput as u,assertNoToolExecutionErrorOutput as i,buildEvidenceSynthesisOutput as n,buildResultRecoveryRequest as c,containsRawToolCallOutput as p,rawToolCallFailureMessage as d,rawToolCallOutputPreview as y,toolCallRecoveryEnabled as v}from"../../recovery/tool-call.js";import{controlGaps as g}from"../../quality/event-evidence.js";export async function recoverAdapterResultOutput(o){let g=o.result,m=o.request;const w=function resultRecoveryAttempts(e){const t="object"!=typeof e||null===e||Array.isArray(e)?void 0:e.recovery,o="object"!=typeof t||null===t||Array.isArray(t)?void 0:t.toolCall,r="object"!=typeof o||null===o||Array.isArray(o)?void 0:o.maxResultRecoveryAttempts;return"number"==typeof r&&Number.isInteger(r)&&r>0?r:3}(o.recoveryPolicy),R=new Set,_=new Set;for(let e=0;;e+=1){const r=o.store.getRun(o.requestId)?.events??[],s=a({output:g.text,agent:o.agent,workspace:o.workspace,toolGateway:o.toolGateway,events:r,policy:o.recoveryPolicy}),l=s?rawArgsToolKey(s):void 0,u=l&&!R.has(l)?t({request:m,output:g.text,agent:o.agent,workspace:o.workspace,toolGateway:o.toolGateway,events:r,policy:o.recoveryPolicy}):void 0,i=e<w?c({request:m,output:g.text,events:r,availableToolIds:o.agent.tools,policy:o.recoveryPolicy}):void 0,n=u??i;if(!n)break;if(n===u&&R.add(l),m=n,emitRepair(o,"runtime.repair.started","result_output",e+1,"recoverable_result_output",void 0,repairDiagnostics(g.text,o.agent.tools)),g=await o.runAdapter(n),emitRepair(o,"runtime.repair.completed","result_output",e+1,"recoverable_result_output","retried",repairDiagnostics(g.text,o.agent.tools)),n===u){const t=await recoverRepeatedRawArgsByToolGateway(o,m,g,R,_,e+1);t&&(m=t.request,g=t.result)}}return function finalizeRecoveredOutput(t,o){if(!v(t.recoveryPolicy))return o;let a=!1;if(p(o.text,t.recoveryPolicy)&&function rawToolCallFailureReturnsMessage(e){return"message"===("object"!=typeof e?.toolCallRecovery||null===e.toolCallRecovery||Array.isArray(e.toolCallRecovery)?{}:e.toolCallRecovery).onFailure}(t.request.metadata)){const e=o.text;o={...o,text:d(),metadata:{...o.metadata,toolCallRecovery:{failed:!0,reason:"raw_tool_call_output"}}},emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(e,t.agent.tools))}const c=n({request:t.request,output:o.text,events:t.store.getRun(t.requestId)?.events??[],policy:t.recoveryPolicy});return c&&(a=!0,o={...o,text:c,metadata:{...o.metadata,toolCallRecovery:{synthesized:!0,reason:"raw_tool_call_output_with_evidence"}}},emitRepair(t,"runtime.repair.completed","evidence_synthesis",void 0,"raw_tool_call_output_with_evidence","synthesized")),a||(p(o.text,t.recoveryPolicy)&&emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(o.text,t.agent.tools)),l(o.text,t.recoveryPolicy),function assertNoRawArgsToolOutput(e,t){if(r({output:t,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],policy:e.recoveryPolicy}))throw new Error(`Adapter returned raw tool argument JSON as the final answer after recovery. The backend must execute the matching tool instead. Output preview: ${y(t)}`)}(t,o.text),s(o.text,t.agent.tools,t.recoveryPolicy),u(o.text,t.store.getRun(t.requestId)?.events??[],t.recoveryPolicy),i(o.text,t.recoveryPolicy),e({output:o.text,events:t.store.getRun(t.requestId)?.events??[],availableToolIds:t.agent.tools})),o}(o,g)}async function recoverRepeatedRawArgsByToolGateway(e,t,r,s,l,u){const i=a({output:r.text,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],policy:e.recoveryPolicy});if(!i||!e.runRecoveredToolCall)return;const n=rawArgsToolKey(i);if(!s.has(n)||l.has(n))return;l.add(n),emitRepair(e,"runtime.repair.started","result_output",u,"raw_args_tool_gateway_recovery",void 0,repairDiagnostics(r.text,e.agent.tools));const c=function visibleRecoveredToolOutput(e){return("string"==typeof e.text?e.text.trim():"")||"The recovered tool completed successfully but returned no user-visible output."}(await e.runRecoveredToolCall(i.toolId,i.args)),p=o({request:t,match:i,toolOutput:c}),d=await e.runAdapter(p),y=d.text.trim()?d:{...d,text:buildEmptyRecoveredToolBlocker({match:i,toolOutput:c,events:e.store.getRun(e.requestId)?.events??[]}),metadata:{...d.metadata,toolCallRecovery:{blocked:!0,reason:"empty_output_after_recovered_tool"}}};return emitRepair(e,"runtime.repair.completed","result_output",u,"raw_args_tool_gateway_recovery","retried",repairDiagnostics(y.text,e.agent.tools)),{request:p,result:y}}function emitRepair(e,t,o,r,a,s,l){const u={requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,layer:o,attempt:r,reason:a,...l?{diagnostics:l}:{}};e.emit("runtime.repair.started"===t?{type:t,...u}:{type:t,...u,outcome:s??"retried"})}function repairDiagnostics(e,t){return{outputPreview:y(e),toolCandidateIds:visibleToolCandidates(e,t)}}function visibleToolCandidates(e,t){const o=new Set;for(const r of t??[])new RegExp(`(?:^|[^A-Za-z0-9_-])${escapeRegexp(r)}(?:$|[^A-Za-z0-9_-])`,"u").test(e)&&o.add(r);return[...o]}function escapeRegexp(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function rawArgsToolKey(e){return`${e.toolId}:${JSON.stringify(e.args)}`}function buildEmptyRecoveredToolBlocker(e){const t=g(e.events);return["Stable runtime recovery executed the matched declared tool, but the backend returned no user-facing output after receiving the executed evidence.","",`Executed tool: ${e.match.toolId}`,"","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool evidence:",e.toolOutput,...t.length>0?["","Unresolved control gaps:",...t.map(e=>`- ${e}`)]:[]].join("\n")}
1
+ import{assertNoDeclaredActionOmissionOutput as e}from"../../recovery/control-omission.js";import{containsProgressOnlyToolIntent as t}from"../../recovery/progress-intent.js";import{buildRawArgsRecoveryRequest as o,buildRawArgsToolEvidenceRecoveryRequest as r,hasUniqueRawArgsTool as a,matchUniqueRawArgsTool as s}from"../../recovery/raw-args.js";import{assertNoProgressOnlyToolIntentOutput as n,assertNoRawToolCallOutput as l,assertNoRawToolResultOutput as i,assertNoToolExecutionErrorOutput as u,buildEvidenceSynthesisOutput as c,buildResultRecoveryRequest as d,containsRawToolCallOutput as p,rawToolCallFailureMessage as y,rawToolCallOutputPreview as v,toolCallRecoveryEnabled as g}from"../../recovery/tool-call.js";import{controlGaps as m}from"../../quality/event-evidence.js";export async function recoverAdapterResultOutput(t){let r=t.result,m=t.request;const f=function resultRecoveryAttempts(e){const t="object"!=typeof e||null===e||Array.isArray(e)?void 0:e.recovery,o="object"!=typeof t||null===t||Array.isArray(t)?void 0:t.toolCall,r="object"!=typeof o||null===o||Array.isArray(o)?void 0:o.maxResultRecoveryAttempts;return"number"==typeof r&&Number.isInteger(r)&&r>0?r:3}(t.recoveryPolicy),w=new Set,R=new Set;for(let e=0;;e+=1){const a=t.store.getRun(t.requestId)?.events??[],n=effectiveRecoveryToolIds(m,t.agent.tools);assertNoNonFocusedRecoveryIntent(r.text,t.recoveryPolicy,t.agent.tools,n);const l=s({output:r.text,agent:t.agent,workspace:t.workspace,toolGateway:t.toolGateway,events:a,candidateToolIds:n,policy:t.recoveryPolicy}),i=l?rawArgsToolKey(l):void 0,u=i&&!w.has(i)?o({request:m,output:r.text,agent:t.agent,workspace:t.workspace,toolGateway:t.toolGateway,events:a,candidateToolIds:n,policy:t.recoveryPolicy}):void 0,c=e<f?d({request:m,output:r.text,events:a,availableToolIds:n,policy:t.recoveryPolicy}):void 0,p=u??c;if(!p)break;if(p===u&&w.add(i),m=p,emitRepair(t,"runtime.repair.started","result_output",e+1,"recoverable_result_output",void 0,repairDiagnostics(r.text,n)),r=await t.runAdapter(p),emitRepair(t,"runtime.repair.completed","result_output",e+1,"recoverable_result_output","retried",repairDiagnostics(r.text,n)),p===u){const o=await recoverRepeatedRawArgsByToolGateway(t,m,r,w,R,e+1);o&&(m=o.request,r=o.result)}}return function finalizeRecoveredOutput(t,o){if(!g(t.recoveryPolicy))return o;let r=!1;if(p(o.text,t.recoveryPolicy)&&function rawToolCallFailureReturnsMessage(e){return"message"===("object"!=typeof e?.toolCallRecovery||null===e.toolCallRecovery||Array.isArray(e.toolCallRecovery)?{}:e.toolCallRecovery).onFailure}(t.request.metadata)){const e=o.text;o={...o,text:y(),metadata:{...o.metadata,toolCallRecovery:{failed:!0,reason:"raw_tool_call_output"}}},emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(e,t.agent.tools))}const s=c({request:t.request,output:o.text,events:t.store.getRun(t.requestId)?.events??[],policy:t.recoveryPolicy});return s&&(r=!0,o={...o,text:s,metadata:{...o.metadata,toolCallRecovery:{synthesized:!0,reason:"raw_tool_call_output_with_evidence"}}},emitRepair(t,"runtime.repair.completed","evidence_synthesis",void 0,"raw_tool_call_output_with_evidence","synthesized")),r||(p(o.text,t.recoveryPolicy)&&emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(o.text,t.agent.tools)),l(o.text,t.recoveryPolicy),function assertNoRawArgsToolOutput(e,t){if(a({output:t,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],candidateToolIds:effectiveRecoveryToolIds(e.request,e.agent.tools),policy:e.recoveryPolicy}))throw new Error(`Adapter returned raw tool argument JSON as the final answer after recovery. The backend must execute the matching tool instead. Output preview: ${v(t)}`)}(t,o.text),n(o.text,t.agent.tools,t.recoveryPolicy),i(o.text,t.store.getRun(t.requestId)?.events??[],t.recoveryPolicy),u(o.text,t.recoveryPolicy),e({output:o.text,events:t.store.getRun(t.requestId)?.events??[],availableToolIds:t.agent.tools})),o}(t,r)}async function recoverRepeatedRawArgsByToolGateway(e,t,o,a,n,l){const i=s({output:o.text,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],candidateToolIds:effectiveRecoveryToolIds(t,e.agent.tools),policy:e.recoveryPolicy});if(!i||!e.runRecoveredToolCall)return;const u=rawArgsToolKey(i);if(!a.has(u)||n.has(u))return;n.add(u),emitRepair(e,"runtime.repair.started","result_output",l,"raw_args_tool_gateway_recovery",void 0,repairDiagnostics(o.text,e.agent.tools));const c=function visibleRecoveredToolOutput(e){return("string"==typeof e.text?e.text.trim():"")||"The recovered tool completed successfully but returned no user-visible output."}(await e.runRecoveredToolCall(i.toolId,i.args)),d=r({request:t,match:i,toolOutput:c}),p=await e.runAdapter(d),y=p.text.trim()?p:{...p,text:buildEmptyRecoveredToolBlocker({match:i,toolOutput:c,events:e.store.getRun(e.requestId)?.events??[]}),metadata:{...p.metadata,toolCallRecovery:{blocked:!0,reason:"empty_output_after_recovered_tool"}}};return emitRepair(e,"runtime.repair.completed","result_output",l,"raw_args_tool_gateway_recovery","retried",repairDiagnostics(y.text,e.agent.tools)),{request:d,result:y}}function assertNoNonFocusedRecoveryIntent(e,o,r,a){if(0===a.length||a.length===r.length)return;const s=new Set(a),n=visibleToolCandidates(e,r).filter(e=>!s.has(e)),l=p(e,o)||t(e,r)||function containsJsonToolEnvelope(e){const t=e.trim(),o=t.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu)?.[1]?.trim(),r=o??t;if(!r.startsWith("{")||!r.endsWith("}")||r.length>6e3)return!1;try{const e=JSON.parse(r);if(!function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}(e))return!1;const t=["tool","tool_name","name","type","subagent_type"].some(t=>"string"==typeof e[t]),o=["args","arguments","parameters","kwargs"].some(t=>t in e);return t&&o}catch{return!1}}(e);if(n.length>0&&l)throw new Error(`Focused recovery output referenced non-focused tool(s): ${n.join(", ")}. The backend must call one of the focused gateway tools: ${a.join(", ")}.`)}function emitRepair(e,t,o,r,a,s,n){const l={requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,layer:o,attempt:r,reason:a,...n?{diagnostics:n}:{}};e.emit("runtime.repair.started"===t?{type:t,...l}:{type:t,...l,outcome:s??"retried"})}function repairDiagnostics(e,t){return{outputPreview:v(e),toolCandidateIds:visibleToolCandidates(e,t)}}function visibleToolCandidates(e,t){const o=new Set;for(const r of t??[])new RegExp(`(?:^|[^A-Za-z0-9_-])${escapeRegexp(r)}(?:$|[^A-Za-z0-9_-])`,"u").test(e)&&o.add(r);return[...o]}function escapeRegexp(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function effectiveRecoveryToolIds(e,t){const o=function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}(e.metadata?.stableHarnessRequiredEvidenceTools).filter(e=>t.includes(e));return o.length>0?o:t}function rawArgsToolKey(e){return`${e.toolId}:${JSON.stringify(e.args)}`}function buildEmptyRecoveredToolBlocker(e){const t=m(e.events);return["Stable runtime recovery executed the matched declared tool, but the backend returned no user-facing output after receiving the executed evidence.","",`Executed tool: ${e.match.toolId}`,"","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool evidence:",e.toolOutput,...t.length>0?["","Unresolved control gaps:",...t.map(e=>`- ${e}`)]:[]].join("\n")}
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -11,7 +12,7 @@
11
12
  ".": "./dist/index.js"
12
13
  },
13
14
  "peerDependencies": {
14
- "@stable-harness/governance": "0.0.118",
15
- "@stable-harness/memory": "0.0.118"
15
+ "@stable-harness/governance": "0.0.120",
16
+ "@stable-harness/memory": "0.0.120"
16
17
  }
17
18
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/memory",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/protocols",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -10,6 +11,6 @@
10
11
  "main": "dist/src/index.js",
11
12
  "types": "dist/src/index.d.ts",
12
13
  "peerDependencies": {
13
- "@stable-harness/core": "0.0.118"
14
+ "@stable-harness/core": "0.0.120"
14
15
  }
15
16
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/workspace-yaml",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -11,6 +12,6 @@
11
12
  ".": "./dist/index.js"
12
13
  },
13
14
  "peerDependencies": {
14
- "@stable-harness/core": "0.0.118"
15
+ "@stable-harness/core": "0.0.120"
15
16
  }
16
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stable-harness",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
4
  "type": "module",
5
5
  "description": "Stable application runtime and operator control plane for agent workspaces.",
6
6
  "license": "Apache-2.0",
@@ -82,14 +82,14 @@
82
82
  "@langchain/node-vfs": "^0.1.4",
83
83
  "@langchain/ollama": "^1.2.7",
84
84
  "@langchain/openai": "^1.4.5",
85
- "@stable-harness/adapter-deepagents": "0.0.118",
86
- "@stable-harness/adapter-langgraph": "0.0.118",
87
- "@stable-harness/core": "0.0.118",
88
- "@stable-harness/governance": "0.0.118",
89
- "@stable-harness/memory": "0.0.118",
90
- "@stable-harness/protocols": "0.0.118",
91
- "@stable-harness/tool-gateway": "0.0.118",
92
- "@stable-harness/workspace-yaml": "0.0.118",
85
+ "@stable-harness/adapter-deepagents": "0.0.120",
86
+ "@stable-harness/adapter-langgraph": "0.0.120",
87
+ "@stable-harness/core": "0.0.120",
88
+ "@stable-harness/governance": "0.0.120",
89
+ "@stable-harness/memory": "0.0.120",
90
+ "@stable-harness/protocols": "0.0.120",
91
+ "@stable-harness/tool-gateway": "0.0.120",
92
+ "@stable-harness/workspace-yaml": "0.0.120",
93
93
  "deepagents": "^1.10.1",
94
94
  "langchain": "^1.4.0",
95
95
  "yaml": "^2.8.2",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-deepagents",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -15,7 +16,7 @@
15
16
  "@langchain/node-vfs": "^0.1.4",
16
17
  "@langchain/ollama": "^1.2.7",
17
18
  "@langchain/openai": "^1.4.5",
18
- "@stable-harness/core": "0.0.118",
19
+ "@stable-harness/core": "0.0.120",
19
20
  "deepagents": "^1.10.1",
20
21
  "langchain": "^1.4.0"
21
22
  },
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/adapter-langgraph",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -11,6 +12,6 @@
11
12
  "types": "dist/src/index.d.ts",
12
13
  "peerDependencies": {
13
14
  "@langchain/langgraph": "^1.3.0",
14
- "@stable-harness/core": "0.0.118"
15
+ "@stable-harness/core": "0.0.120"
15
16
  }
16
17
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/cli",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -14,12 +15,12 @@
14
15
  "types": "dist/src/index.d.ts",
15
16
  "peerDependencies": {
16
17
  "@langchain/langgraph-api": "^1.2.1",
17
- "@stable-harness/adapter-deepagents": "0.0.118",
18
- "@stable-harness/adapter-langgraph": "0.0.118",
19
- "@stable-harness/core": "0.0.118",
20
- "@stable-harness/memory": "0.0.118",
21
- "@stable-harness/protocols": "0.0.118",
22
- "@stable-harness/tool-gateway": "0.0.118",
23
- "@stable-harness/workspace-yaml": "0.0.118"
18
+ "@stable-harness/adapter-deepagents": "0.0.120",
19
+ "@stable-harness/adapter-langgraph": "0.0.120",
20
+ "@stable-harness/core": "0.0.120",
21
+ "@stable-harness/memory": "0.0.120",
22
+ "@stable-harness/protocols": "0.0.120",
23
+ "@stable-harness/tool-gateway": "0.0.120",
24
+ "@stable-harness/workspace-yaml": "0.0.120"
24
25
  }
25
26
  }
@@ -1 +1 @@
1
- export function buildExecutionContractRecoveryRequest(e){if(!0!==function readToolCallRecovery(e){if(!isRecord(e))return{};const t=isRecord(e.recovery)?e.recovery:{};return isRecord(t.toolCall)?t.toolCall:{}}(e.policy).enabled)return;const t=function lastExecutionContractFailure(e){for(let t=e.length-1;t>=0;t-=1){const r=e[t];if("runtime.execution.contract.failed"===r?.type)return{reason:r.reason,missing:readStringArray(r.missingEvidenceTools)}}return{missing:[]}}(e.events);return 0!==t.missing.length?"missing_required_evidence_usage"===t.reason?recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`The final answer did not use required executed evidence from: ${t.missing.join(", ")}`,"Synthesize from those required source(s), or call them through normal structured tool calling if their evidence is unavailable.","Do not replace required evidence with unrelated intermediate evidence, progress text, or summaries from other tools."]):recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`Required evidence tool(s) were missing: ${t.missing.join(", ")}`,`For this recovery turn, the only allowed next tool call target(s) are: ${t.missing.join(", ")}`,"The next assistant action must be the backend's structured tool call for one missing required evidence tool, with no prose before it.","Do not call already completed intermediate tools unless they are listed above.","Do not produce a final answer until the required evidence tool call has executed and you have synthesized its result.","Do not print XML, JSON, markdown fences, pseudo tool-call text, plans, or future-intent text in the final answer."],{stableHarnessRequiredEvidenceTools:t.missing}):void 0}function recoverRequest(e,t,r={}){return{...e,input:[e.input,"",...t].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call",...r}}}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):[]}
1
+ import{successfulEvidenceItems as e}from"../quality/event-evidence.js";export function buildExecutionContractRecoveryRequest(e){if(!0!==function readToolCallRecovery(e){if(!isRecord(e))return{};const t=isRecord(e.recovery)?e.recovery:{};return isRecord(t.toolCall)?t.toolCall:{}}(e.policy).enabled)return;const t=function lastExecutionContractFailure(e){for(let t=e.length-1;t>=0;t-=1){const r=e[t];if("runtime.execution.contract.failed"===r?.type)return{reason:r.reason,missing:readStringArray(r.missingEvidenceTools)}}return{missing:[]}}(e.events);if(0===t.missing.length)return;if("missing_required_evidence_usage"===t.reason){const r=recentSuccessfulEvidence(e.events);return recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`The final answer did not use required executed evidence from: ${t.missing.join(", ")}`,"Synthesize from those required source(s), or call them through normal structured tool calling if their evidence is unavailable.","Do not replace required evidence with unrelated intermediate evidence, progress text, or summaries from other tools.",...r.length>0?["","Prior successful tool evidence:",...r]:[]])}const r=recentSuccessfulEvidence(e.events);return recoverRequest(e.request,["Stable runtime recovery: the execution contract was not satisfied.",`Required evidence tool(s) were missing: ${t.missing.join(", ")}`,`For this recovery turn, the only allowed next tool call target(s) are: ${t.missing.join(", ")}`,"Reuse exact values from prior successful tool evidence when building the required tool call. Do not invent replacement paths, IDs, handles, URLs, or parameters.","The next assistant action must be the backend's structured tool call for one missing required evidence tool, with no prose before it.","Do not call already completed intermediate tools unless they are listed above.","Do not produce a final answer until the required evidence tool call has executed and you have synthesized its result.","Do not print XML, JSON, markdown fences, pseudo tool-call text, plans, or future-intent text in the final answer.",...r.length>0?["","Prior successful tool evidence:",...r]:[]],{stableHarnessRequiredEvidenceTools:t.missing})}function recoverRequest(e,t,r={}){return{...e,input:[e.input,"",...t].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call",...r}}}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):[]}function recentSuccessfulEvidence(t){return e(t).slice(-5).map(e=>`- ${e.source}: ${function previewEvidence(e){const t=e.replace(/\s+/gu," ").trim();return t.length>1500?`${t.slice(0,1497)}...`:t}(e.output)}`)}
@@ -7,6 +7,7 @@ export type RawArgsRecoveryInput = {
7
7
  workspace: CompiledWorkspace;
8
8
  toolGateway?: RuntimeToolGateway;
9
9
  events?: RuntimeEvent[];
10
+ candidateToolIds?: string[];
10
11
  policy: unknown;
11
12
  };
12
13
  export declare function buildRawArgsRecoveryRequest(input: RawArgsRecoveryInput): RuntimeRequest | undefined;
@@ -1 +1 @@
1
- import{selectCallCandidateByArgsSchema as e}from"@easynet/better-call";export function buildRawArgsRecoveryRequest(e){if(!toolCallRecoveryEnabled(e.policy))return;const t=matchUniqueRawArgsTool(e);return t?function buildRawArgsRecoveryPrompt(e,t){return{...e,input:[e.input,"","Stable runtime recovery: your previous final answer was a JSON argument object for a declared tool, not the final answer.",`Matched configured tool: ${t.toolId}`,"Continue the same user request by calling that tool through the backend's normal structured tool-calling mechanism with the JSON arguments below.","If the tool call succeeds, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Previous JSON arguments:",JSON.stringify(t.args)].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call"}}}(e.request,t):void 0}export function buildRawArgsToolEvidenceRecoveryRequest(e){return{...e.request,input:[e.request.input,"","Stable runtime recovery: your previous final answer repeated a JSON argument object for a declared tool after a structured tool-call recovery request.",`Matched configured tool: ${e.match.toolId}`,"Stable runtime executed that matched declared tool through the governed tool gateway.","Continue the same user request from the executed evidence below.","If another declared tool is needed, call it through the backend's normal structured tool-calling mechanism.","Otherwise, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool output:",e.toolOutput].join("\n"),metadata:{...e.request.metadata,stableHarnessRecovery:"tool_call"}}}export function hasUniqueRawArgsTool(e){return toolCallRecoveryEnabled(e.policy)&&Boolean(matchUniqueRawArgsTool(e))}export function matchUniqueRawArgsTool(t){const o=function parseStandaloneJsonObject(e){const t=e.trim(),o=t.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu)?.[1]?.trim(),r=o??t;if(r.startsWith("{")&&r.endsWith("}")&&!(r.length>6e3))try{const e=JSON.parse(r);return!isRecord(e)||function isToolCallEnvelope(e){const t=["tool","tool_name","name","type","subagent_type"].some(t=>"string"==typeof e[t]),o=["args","arguments","parameters","kwargs"].some(t=>t in e);return t&&o}(e)||function isRuntimeControlObject(e){return"string"==typeof e.status||"string"==typeof e.error||"string"==typeof e.controlStatus}(e)?void 0:e}catch{return}}(t.output);if(!o)return;const r=e({args:o,candidates:buildCallCandidates(t)});if(r.ok)return{toolId:r.candidateId,args:r.args};const n=function latestSingleToolCandidate(e,t){for(let o=(e?.length??0)-1;o>=0;o-=1){const r=e[o];if(!r||!("diagnostics"in r)||!isRecord(r.diagnostics))continue;const n=readStringArray(r.diagnostics.toolCandidateIds).filter(e=>t.has(e));if(1===n.length)return n[0];if(n.length>1)return}}(t.events,new Set(t.agent.tools));return n?{toolId:n,args:o}:void 0}function buildCallCandidates(e){return e.agent.tools.map(t=>({id:t,schema:e.workspace.tools.get(t)?.schema??e.toolGateway?.get(t)?.schema}))}function toolCallRecoveryEnabled(e){return!!(isRecord(e)&&isRecord(e.recovery)&&isRecord(e.recovery.toolCall))&&!0===e.recovery.toolCall.enabled}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
1
+ import{selectCallCandidateByArgsSchema as e}from"@easynet/better-call";export function buildRawArgsRecoveryRequest(e){if(!toolCallRecoveryEnabled(e.policy))return;const t=matchUniqueRawArgsTool(e);return t?function buildRawArgsRecoveryPrompt(e,t){return{...e,input:[e.input,"","Stable runtime recovery: your previous final answer was a JSON argument object for a declared tool, not the final answer.",`Matched configured tool: ${t.toolId}`,"Continue the same user request by calling that tool through the backend's normal structured tool-calling mechanism with the JSON arguments below.","If the tool call succeeds, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Previous JSON arguments:",JSON.stringify(t.args)].join("\n"),metadata:{...e.metadata,stableHarnessRecovery:"tool_call"}}}(e.request,t):void 0}export function buildRawArgsToolEvidenceRecoveryRequest(e){return{...e.request,input:[e.request.input,"","Stable runtime recovery: your previous final answer repeated a JSON argument object for a declared tool after a structured tool-call recovery request.",`Matched configured tool: ${e.match.toolId}`,"Stable runtime executed that matched declared tool through the governed tool gateway.","Continue the same user request from the executed evidence below.","If another declared tool is needed, call it through the backend's normal structured tool-calling mechanism.","Otherwise, synthesize the final user-facing answer from the executed evidence.","Do not print JSON argument objects, raw tool-call markup, plans, or future-intent text as the final answer.","","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool output:",e.toolOutput].join("\n"),metadata:{...e.request.metadata,stableHarnessRecovery:"tool_call"}}}export function hasUniqueRawArgsTool(e){return toolCallRecoveryEnabled(e.policy)&&Boolean(matchUniqueRawArgsTool(e))}export function matchUniqueRawArgsTool(t){const o=function parseStandaloneJsonObject(e){const t=e.trim(),o=t.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu)?.[1]?.trim(),r=o??t;if(r.startsWith("{")&&r.endsWith("}")&&!(r.length>6e3))try{const e=JSON.parse(r);return!isRecord(e)||function isToolCallEnvelope(e){const t=["tool","tool_name","name","type","subagent_type"].some(t=>"string"==typeof e[t]),o=["args","arguments","parameters","kwargs"].some(t=>t in e);return t&&o}(e)||function isRuntimeControlObject(e){return"string"==typeof e.status||"string"==typeof e.error||"string"==typeof e.controlStatus}(e)?void 0:e}catch{return}}(t.output);if(!o)return;const r=e({args:o,candidates:buildCallCandidates(t)});if(r.ok)return{toolId:r.candidateId,args:r.args};const n=function latestSingleToolCandidate(e,t){for(let o=(e?.length??0)-1;o>=0;o-=1){const r=e[o];if(!r||!("diagnostics"in r)||!isRecord(r.diagnostics))continue;const n=readStringArray(r.diagnostics.toolCandidateIds).filter(e=>t.has(e));if(1===n.length)return n[0];if(n.length>1)return}}(t.events,new Set(candidateToolIds(t)));return n?{toolId:n,args:o}:void 0}function buildCallCandidates(e){return candidateToolIds(e).map(t=>({id:t,schema:e.workspace.tools.get(t)?.schema??e.toolGateway?.get(t)?.schema}))}function candidateToolIds(e){const t=new Set(e.agent.tools),o=(e.candidateToolIds??[]).filter(e=>t.has(e));return o.length>0?o:e.agent.tools}function toolCallRecoveryEnabled(e){return!!(isRecord(e)&&isRecord(e.recovery)&&isRecord(e.recovery.toolCall))&&!0===e.recovery.toolCall.enabled}function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}
@@ -1 +1 @@
1
- import{assertNoDeclaredActionOmissionOutput as e}from"../../recovery/control-omission.js";import{buildRawArgsRecoveryRequest as t,buildRawArgsToolEvidenceRecoveryRequest as o,hasUniqueRawArgsTool as r,matchUniqueRawArgsTool as a}from"../../recovery/raw-args.js";import{assertNoProgressOnlyToolIntentOutput as s,assertNoRawToolCallOutput as l,assertNoRawToolResultOutput as u,assertNoToolExecutionErrorOutput as i,buildEvidenceSynthesisOutput as n,buildResultRecoveryRequest as c,containsRawToolCallOutput as p,rawToolCallFailureMessage as d,rawToolCallOutputPreview as y,toolCallRecoveryEnabled as v}from"../../recovery/tool-call.js";import{controlGaps as g}from"../../quality/event-evidence.js";export async function recoverAdapterResultOutput(o){let g=o.result,m=o.request;const w=function resultRecoveryAttempts(e){const t="object"!=typeof e||null===e||Array.isArray(e)?void 0:e.recovery,o="object"!=typeof t||null===t||Array.isArray(t)?void 0:t.toolCall,r="object"!=typeof o||null===o||Array.isArray(o)?void 0:o.maxResultRecoveryAttempts;return"number"==typeof r&&Number.isInteger(r)&&r>0?r:3}(o.recoveryPolicy),R=new Set,_=new Set;for(let e=0;;e+=1){const r=o.store.getRun(o.requestId)?.events??[],s=a({output:g.text,agent:o.agent,workspace:o.workspace,toolGateway:o.toolGateway,events:r,policy:o.recoveryPolicy}),l=s?rawArgsToolKey(s):void 0,u=l&&!R.has(l)?t({request:m,output:g.text,agent:o.agent,workspace:o.workspace,toolGateway:o.toolGateway,events:r,policy:o.recoveryPolicy}):void 0,i=e<w?c({request:m,output:g.text,events:r,availableToolIds:o.agent.tools,policy:o.recoveryPolicy}):void 0,n=u??i;if(!n)break;if(n===u&&R.add(l),m=n,emitRepair(o,"runtime.repair.started","result_output",e+1,"recoverable_result_output",void 0,repairDiagnostics(g.text,o.agent.tools)),g=await o.runAdapter(n),emitRepair(o,"runtime.repair.completed","result_output",e+1,"recoverable_result_output","retried",repairDiagnostics(g.text,o.agent.tools)),n===u){const t=await recoverRepeatedRawArgsByToolGateway(o,m,g,R,_,e+1);t&&(m=t.request,g=t.result)}}return function finalizeRecoveredOutput(t,o){if(!v(t.recoveryPolicy))return o;let a=!1;if(p(o.text,t.recoveryPolicy)&&function rawToolCallFailureReturnsMessage(e){return"message"===("object"!=typeof e?.toolCallRecovery||null===e.toolCallRecovery||Array.isArray(e.toolCallRecovery)?{}:e.toolCallRecovery).onFailure}(t.request.metadata)){const e=o.text;o={...o,text:d(),metadata:{...o.metadata,toolCallRecovery:{failed:!0,reason:"raw_tool_call_output"}}},emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(e,t.agent.tools))}const c=n({request:t.request,output:o.text,events:t.store.getRun(t.requestId)?.events??[],policy:t.recoveryPolicy});return c&&(a=!0,o={...o,text:c,metadata:{...o.metadata,toolCallRecovery:{synthesized:!0,reason:"raw_tool_call_output_with_evidence"}}},emitRepair(t,"runtime.repair.completed","evidence_synthesis",void 0,"raw_tool_call_output_with_evidence","synthesized")),a||(p(o.text,t.recoveryPolicy)&&emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(o.text,t.agent.tools)),l(o.text,t.recoveryPolicy),function assertNoRawArgsToolOutput(e,t){if(r({output:t,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],policy:e.recoveryPolicy}))throw new Error(`Adapter returned raw tool argument JSON as the final answer after recovery. The backend must execute the matching tool instead. Output preview: ${y(t)}`)}(t,o.text),s(o.text,t.agent.tools,t.recoveryPolicy),u(o.text,t.store.getRun(t.requestId)?.events??[],t.recoveryPolicy),i(o.text,t.recoveryPolicy),e({output:o.text,events:t.store.getRun(t.requestId)?.events??[],availableToolIds:t.agent.tools})),o}(o,g)}async function recoverRepeatedRawArgsByToolGateway(e,t,r,s,l,u){const i=a({output:r.text,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],policy:e.recoveryPolicy});if(!i||!e.runRecoveredToolCall)return;const n=rawArgsToolKey(i);if(!s.has(n)||l.has(n))return;l.add(n),emitRepair(e,"runtime.repair.started","result_output",u,"raw_args_tool_gateway_recovery",void 0,repairDiagnostics(r.text,e.agent.tools));const c=function visibleRecoveredToolOutput(e){return("string"==typeof e.text?e.text.trim():"")||"The recovered tool completed successfully but returned no user-visible output."}(await e.runRecoveredToolCall(i.toolId,i.args)),p=o({request:t,match:i,toolOutput:c}),d=await e.runAdapter(p),y=d.text.trim()?d:{...d,text:buildEmptyRecoveredToolBlocker({match:i,toolOutput:c,events:e.store.getRun(e.requestId)?.events??[]}),metadata:{...d.metadata,toolCallRecovery:{blocked:!0,reason:"empty_output_after_recovered_tool"}}};return emitRepair(e,"runtime.repair.completed","result_output",u,"raw_args_tool_gateway_recovery","retried",repairDiagnostics(y.text,e.agent.tools)),{request:p,result:y}}function emitRepair(e,t,o,r,a,s,l){const u={requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,layer:o,attempt:r,reason:a,...l?{diagnostics:l}:{}};e.emit("runtime.repair.started"===t?{type:t,...u}:{type:t,...u,outcome:s??"retried"})}function repairDiagnostics(e,t){return{outputPreview:y(e),toolCandidateIds:visibleToolCandidates(e,t)}}function visibleToolCandidates(e,t){const o=new Set;for(const r of t??[])new RegExp(`(?:^|[^A-Za-z0-9_-])${escapeRegexp(r)}(?:$|[^A-Za-z0-9_-])`,"u").test(e)&&o.add(r);return[...o]}function escapeRegexp(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function rawArgsToolKey(e){return`${e.toolId}:${JSON.stringify(e.args)}`}function buildEmptyRecoveredToolBlocker(e){const t=g(e.events);return["Stable runtime recovery executed the matched declared tool, but the backend returned no user-facing output after receiving the executed evidence.","",`Executed tool: ${e.match.toolId}`,"","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool evidence:",e.toolOutput,...t.length>0?["","Unresolved control gaps:",...t.map(e=>`- ${e}`)]:[]].join("\n")}
1
+ import{assertNoDeclaredActionOmissionOutput as e}from"../../recovery/control-omission.js";import{containsProgressOnlyToolIntent as t}from"../../recovery/progress-intent.js";import{buildRawArgsRecoveryRequest as o,buildRawArgsToolEvidenceRecoveryRequest as r,hasUniqueRawArgsTool as a,matchUniqueRawArgsTool as s}from"../../recovery/raw-args.js";import{assertNoProgressOnlyToolIntentOutput as n,assertNoRawToolCallOutput as l,assertNoRawToolResultOutput as i,assertNoToolExecutionErrorOutput as u,buildEvidenceSynthesisOutput as c,buildResultRecoveryRequest as d,containsRawToolCallOutput as p,rawToolCallFailureMessage as y,rawToolCallOutputPreview as v,toolCallRecoveryEnabled as g}from"../../recovery/tool-call.js";import{controlGaps as m}from"../../quality/event-evidence.js";export async function recoverAdapterResultOutput(t){let r=t.result,m=t.request;const f=function resultRecoveryAttempts(e){const t="object"!=typeof e||null===e||Array.isArray(e)?void 0:e.recovery,o="object"!=typeof t||null===t||Array.isArray(t)?void 0:t.toolCall,r="object"!=typeof o||null===o||Array.isArray(o)?void 0:o.maxResultRecoveryAttempts;return"number"==typeof r&&Number.isInteger(r)&&r>0?r:3}(t.recoveryPolicy),w=new Set,R=new Set;for(let e=0;;e+=1){const a=t.store.getRun(t.requestId)?.events??[],n=effectiveRecoveryToolIds(m,t.agent.tools);assertNoNonFocusedRecoveryIntent(r.text,t.recoveryPolicy,t.agent.tools,n);const l=s({output:r.text,agent:t.agent,workspace:t.workspace,toolGateway:t.toolGateway,events:a,candidateToolIds:n,policy:t.recoveryPolicy}),i=l?rawArgsToolKey(l):void 0,u=i&&!w.has(i)?o({request:m,output:r.text,agent:t.agent,workspace:t.workspace,toolGateway:t.toolGateway,events:a,candidateToolIds:n,policy:t.recoveryPolicy}):void 0,c=e<f?d({request:m,output:r.text,events:a,availableToolIds:n,policy:t.recoveryPolicy}):void 0,p=u??c;if(!p)break;if(p===u&&w.add(i),m=p,emitRepair(t,"runtime.repair.started","result_output",e+1,"recoverable_result_output",void 0,repairDiagnostics(r.text,n)),r=await t.runAdapter(p),emitRepair(t,"runtime.repair.completed","result_output",e+1,"recoverable_result_output","retried",repairDiagnostics(r.text,n)),p===u){const o=await recoverRepeatedRawArgsByToolGateway(t,m,r,w,R,e+1);o&&(m=o.request,r=o.result)}}return function finalizeRecoveredOutput(t,o){if(!g(t.recoveryPolicy))return o;let r=!1;if(p(o.text,t.recoveryPolicy)&&function rawToolCallFailureReturnsMessage(e){return"message"===("object"!=typeof e?.toolCallRecovery||null===e.toolCallRecovery||Array.isArray(e.toolCallRecovery)?{}:e.toolCallRecovery).onFailure}(t.request.metadata)){const e=o.text;o={...o,text:y(),metadata:{...o.metadata,toolCallRecovery:{failed:!0,reason:"raw_tool_call_output"}}},emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(e,t.agent.tools))}const s=c({request:t.request,output:o.text,events:t.store.getRun(t.requestId)?.events??[],policy:t.recoveryPolicy});return s&&(r=!0,o={...o,text:s,metadata:{...o.metadata,toolCallRecovery:{synthesized:!0,reason:"raw_tool_call_output_with_evidence"}}},emitRepair(t,"runtime.repair.completed","evidence_synthesis",void 0,"raw_tool_call_output_with_evidence","synthesized")),r||(p(o.text,t.recoveryPolicy)&&emitRepair(t,"runtime.repair.completed","result_output",void 0,"raw_tool_call_output","blocked",repairDiagnostics(o.text,t.agent.tools)),l(o.text,t.recoveryPolicy),function assertNoRawArgsToolOutput(e,t){if(a({output:t,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],candidateToolIds:effectiveRecoveryToolIds(e.request,e.agent.tools),policy:e.recoveryPolicy}))throw new Error(`Adapter returned raw tool argument JSON as the final answer after recovery. The backend must execute the matching tool instead. Output preview: ${v(t)}`)}(t,o.text),n(o.text,t.agent.tools,t.recoveryPolicy),i(o.text,t.store.getRun(t.requestId)?.events??[],t.recoveryPolicy),u(o.text,t.recoveryPolicy),e({output:o.text,events:t.store.getRun(t.requestId)?.events??[],availableToolIds:t.agent.tools})),o}(t,r)}async function recoverRepeatedRawArgsByToolGateway(e,t,o,a,n,l){const i=s({output:o.text,agent:e.agent,workspace:e.workspace,toolGateway:e.toolGateway,events:e.store.getRun(e.requestId)?.events??[],candidateToolIds:effectiveRecoveryToolIds(t,e.agent.tools),policy:e.recoveryPolicy});if(!i||!e.runRecoveredToolCall)return;const u=rawArgsToolKey(i);if(!a.has(u)||n.has(u))return;n.add(u),emitRepair(e,"runtime.repair.started","result_output",l,"raw_args_tool_gateway_recovery",void 0,repairDiagnostics(o.text,e.agent.tools));const c=function visibleRecoveredToolOutput(e){return("string"==typeof e.text?e.text.trim():"")||"The recovered tool completed successfully but returned no user-visible output."}(await e.runRecoveredToolCall(i.toolId,i.args)),d=r({request:t,match:i,toolOutput:c}),p=await e.runAdapter(d),y=p.text.trim()?p:{...p,text:buildEmptyRecoveredToolBlocker({match:i,toolOutput:c,events:e.store.getRun(e.requestId)?.events??[]}),metadata:{...p.metadata,toolCallRecovery:{blocked:!0,reason:"empty_output_after_recovered_tool"}}};return emitRepair(e,"runtime.repair.completed","result_output",l,"raw_args_tool_gateway_recovery","retried",repairDiagnostics(y.text,e.agent.tools)),{request:d,result:y}}function assertNoNonFocusedRecoveryIntent(e,o,r,a){if(0===a.length||a.length===r.length)return;const s=new Set(a),n=visibleToolCandidates(e,r).filter(e=>!s.has(e)),l=p(e,o)||t(e,r)||function containsJsonToolEnvelope(e){const t=e.trim(),o=t.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/iu)?.[1]?.trim(),r=o??t;if(!r.startsWith("{")||!r.endsWith("}")||r.length>6e3)return!1;try{const e=JSON.parse(r);if(!function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}(e))return!1;const t=["tool","tool_name","name","type","subagent_type"].some(t=>"string"==typeof e[t]),o=["args","arguments","parameters","kwargs"].some(t=>t in e);return t&&o}catch{return!1}}(e);if(n.length>0&&l)throw new Error(`Focused recovery output referenced non-focused tool(s): ${n.join(", ")}. The backend must call one of the focused gateway tools: ${a.join(", ")}.`)}function emitRepair(e,t,o,r,a,s,n){const l={requestId:e.requestId,sessionId:e.sessionId,agentId:e.agent.id,layer:o,attempt:r,reason:a,...n?{diagnostics:n}:{}};e.emit("runtime.repair.started"===t?{type:t,...l}:{type:t,...l,outcome:s??"retried"})}function repairDiagnostics(e,t){return{outputPreview:v(e),toolCandidateIds:visibleToolCandidates(e,t)}}function visibleToolCandidates(e,t){const o=new Set;for(const r of t??[])new RegExp(`(?:^|[^A-Za-z0-9_-])${escapeRegexp(r)}(?:$|[^A-Za-z0-9_-])`,"u").test(e)&&o.add(r);return[...o]}function escapeRegexp(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}function effectiveRecoveryToolIds(e,t){const o=function readStringArray(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&e.length>0):[]}(e.metadata?.stableHarnessRequiredEvidenceTools).filter(e=>t.includes(e));return o.length>0?o:t}function rawArgsToolKey(e){return`${e.toolId}:${JSON.stringify(e.args)}`}function buildEmptyRecoveredToolBlocker(e){const t=m(e.events);return["Stable runtime recovery executed the matched declared tool, but the backend returned no user-facing output after receiving the executed evidence.","",`Executed tool: ${e.match.toolId}`,"","Executed JSON arguments:",JSON.stringify(e.match.args),"","Executed tool evidence:",e.toolOutput,...t.length>0?["","Unresolved control gaps:",...t.map(e=>`- ${e}`)]:[]].join("\n")}
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/core",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -11,7 +12,7 @@
11
12
  ".": "./dist/index.js"
12
13
  },
13
14
  "peerDependencies": {
14
- "@stable-harness/governance": "0.0.118",
15
- "@stable-harness/memory": "0.0.118"
15
+ "@stable-harness/governance": "0.0.120",
16
+ "@stable-harness/memory": "0.0.120"
16
17
  }
17
18
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/evaluation",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -10,6 +11,6 @@
10
11
  "main": "dist/src/index.js",
11
12
  "types": "dist/src/index.d.ts",
12
13
  "peerDependencies": {
13
- "@stable-harness/core": "0.0.118"
14
+ "@stable-harness/core": "0.0.120"
14
15
  }
15
16
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/governance",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/memory",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/protocols",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -10,6 +11,6 @@
10
11
  "main": "dist/src/index.js",
11
12
  "types": "dist/src/index.d.ts",
12
13
  "peerDependencies": {
13
- "@stable-harness/core": "0.0.118"
14
+ "@stable-harness/core": "0.0.120"
14
15
  }
15
16
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/tool-gateway",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@stable-harness/workspace-yaml",
3
- "version": "0.0.118",
3
+ "version": "0.0.120",
4
+ "license": "Apache-2.0",
4
5
  "type": "module",
5
6
  "files": [
6
7
  "dist/**/*.js",
@@ -11,6 +12,6 @@
11
12
  ".": "./dist/index.js"
12
13
  },
13
14
  "peerDependencies": {
14
- "@stable-harness/core": "0.0.118"
15
+ "@stable-harness/core": "0.0.120"
15
16
  }
16
17
  }