@qvac/harness 0.7.0 → 1.0.0-rc.0

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 (204) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +239 -2
  3. package/dist-lib/index.d.ts +58 -3
  4. package/dist-lib/index.js +736 -239
  5. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
  6. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
  7. package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
  8. package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
  9. package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
  10. package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
  11. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
  12. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
  13. package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
  14. package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
  15. package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
  16. package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
  17. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
  18. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
  19. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
  20. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
  21. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
  22. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
  23. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
  24. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
  25. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
  26. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
  27. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
  28. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
  29. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
  30. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
  31. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
  32. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
  33. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
  34. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
  35. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
  36. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
  37. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
  38. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
  39. package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
  40. package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
  41. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
  42. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
  43. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
  44. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
  45. package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
  46. package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
  47. package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
  48. package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
  49. package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
  50. package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
  51. package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
  52. package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
  53. package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
  54. package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
  55. package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
  56. package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
  57. package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
  58. package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
  59. package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
  60. package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
  61. package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
  62. package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
  63. package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
  64. package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
  65. package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
  66. package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
  67. package/dist-lib/lib/capabilities/completion.d.ts +18 -335
  68. package/dist-lib/lib/capabilities/completion.js +20 -1301
  69. package/dist-lib/lib/capabilities/download.d.ts +1 -1
  70. package/dist-lib/lib/capabilities/download.js +7 -2
  71. package/dist-lib/lib/capabilities/invoke.js +5 -3
  72. package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
  73. package/dist-lib/lib/capabilities/tool-plan.js +66 -0
  74. package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
  75. package/dist-lib/lib/capabilities/tool-turns.js +30 -2
  76. package/dist-lib/lib/capabilities/types.d.ts +287 -0
  77. package/dist-lib/lib/capabilities/types.js +1 -0
  78. package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
  79. package/dist-lib/lib/completion-lifecycle.js +126 -0
  80. package/dist-lib/lib/connect.d.ts +10 -3
  81. package/dist-lib/lib/connect.js +329 -23
  82. package/dist-lib/lib/contract-types.d.ts +19 -2
  83. package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
  84. package/dist-lib/lib/harness-lifecycle.js +129 -0
  85. package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
  86. package/dist-lib/lib/helpers/detach-json.js +146 -0
  87. package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
  88. package/dist-lib/lib/helpers/markup-scanner.js +156 -0
  89. package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
  90. package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
  91. package/dist-lib/lib/helpers/race-abort.js +2 -7
  92. package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
  93. package/dist-lib/lib/helpers/recovery-required.js +1 -0
  94. package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
  95. package/dist-lib/lib/helpers/run-outcome.js +8 -5
  96. package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
  97. package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
  98. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
  99. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
  100. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
  101. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  102. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
  103. package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
  104. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
  105. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  106. package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
  107. package/dist-lib/lib/hooks/pipeline.js +24 -2
  108. package/dist-lib/lib/hooks/run.d.ts +2 -1
  109. package/dist-lib/lib/hooks/run.js +19 -7
  110. package/dist-lib/lib/hooks/types.d.ts +17 -6
  111. package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
  112. package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
  113. package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
  114. package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
  115. package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
  116. package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
  117. package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
  118. package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
  119. package/dist-lib/lib/machine/completion/context.d.ts +9 -0
  120. package/dist-lib/lib/machine/completion/context.js +44 -0
  121. package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
  122. package/dist-lib/lib/machine/completion/contract.js +1 -0
  123. package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
  124. package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
  125. package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
  126. package/dist-lib/lib/machine/completion/implementations.js +152 -0
  127. package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
  128. package/dist-lib/lib/machine/completion/machine.js +511 -0
  129. package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
  130. package/dist-lib/lib/machine/completion/ports.js +224 -0
  131. package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
  132. package/dist-lib/lib/machine/completion/rules.js +115 -0
  133. package/dist-lib/lib/machine/completion/types.d.ts +493 -0
  134. package/dist-lib/lib/machine/completion/types.js +1 -0
  135. package/dist-lib/lib/machine/engine.d.ts +93 -0
  136. package/dist-lib/lib/machine/engine.js +483 -0
  137. package/dist-lib/lib/machine/errors.d.ts +44 -0
  138. package/dist-lib/lib/machine/errors.js +373 -0
  139. package/dist-lib/lib/machine/guards.d.ts +2 -0
  140. package/dist-lib/lib/machine/guards.js +6 -0
  141. package/dist-lib/lib/machine/index.d.ts +20 -0
  142. package/dist-lib/lib/machine/index.js +11 -0
  143. package/dist-lib/lib/machine/replay-output.d.ts +24 -0
  144. package/dist-lib/lib/machine/replay-output.js +50 -0
  145. package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
  146. package/dist-lib/lib/machine/stable-stringify.js +11 -0
  147. package/dist-lib/lib/models/model-handle.d.ts +5 -1
  148. package/dist-lib/lib/models/model-handle.js +8 -3
  149. package/dist-lib/lib/models/model-list.d.ts +5 -3
  150. package/dist-lib/lib/models/model-pool.d.ts +18 -7
  151. package/dist-lib/lib/models/model-pool.js +126 -38
  152. package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
  153. package/dist-lib/lib/run-log/history-codec.js +973 -0
  154. package/dist-lib/lib/run-log/patch.d.ts +6 -0
  155. package/dist-lib/lib/run-log/patch.js +93 -0
  156. package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
  157. package/dist-lib/lib/run-log/persisted-actor.js +218 -0
  158. package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
  159. package/dist-lib/lib/run-log/protocol-types.js +1 -0
  160. package/dist-lib/lib/run-log/protocol.d.ts +27 -0
  161. package/dist-lib/lib/run-log/protocol.js +1056 -0
  162. package/dist-lib/lib/run-log/replay.d.ts +25 -0
  163. package/dist-lib/lib/run-log/replay.js +129 -0
  164. package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
  165. package/dist-lib/lib/run-log/run-journal.js +783 -0
  166. package/dist-lib/lib/run-log-inspect.d.ts +25 -0
  167. package/dist-lib/lib/run-log-inspect.js +627 -0
  168. package/dist-lib/lib/run-log-protocol.d.ts +2 -0
  169. package/dist-lib/lib/run-log-protocol.js +1 -0
  170. package/dist-lib/lib/run-log-redact.d.ts +27 -0
  171. package/dist-lib/lib/run-log-redact.js +1107 -0
  172. package/dist-lib/lib/serve.js +308 -12
  173. package/dist-lib/lib/spawn.js +3 -2
  174. package/dist-lib/lib/tool-call-display.d.ts +1 -21
  175. package/dist-lib/lib/tools/exec.d.ts +2 -1
  176. package/dist-lib/lib/tools/exec.js +57 -2
  177. package/dist-lib/lib/tools/generate-image.js +1 -1
  178. package/dist-lib/lib/tools/generate-music.js +1 -1
  179. package/dist-lib/lib/tools/http-request.d.ts +2 -1
  180. package/dist-lib/lib/tools/http-request.js +2 -1
  181. package/dist-lib/lib/tools/mcp-call.js +8 -1
  182. package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
  183. package/dist-lib/lib/tools/operations/builders.js +1 -0
  184. package/dist-lib/lib/tools/operations/compile.js +11 -1
  185. package/dist-lib/lib/tools/operations/manifest.js +1 -0
  186. package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
  187. package/dist-lib/lib/tools/outbound-guard.js +25 -8
  188. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  189. package/dist-lib/lib/tools/skills/catalog.js +35 -0
  190. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
  191. package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
  192. package/dist-lib/lib/wire.d.ts +5 -0
  193. package/dist-lib/lib/wire.js +128 -5
  194. package/dist-lib/schema/harness.js +22 -6
  195. package/dist-lib/spec/hrpc/messages.js +100 -18
  196. package/dist-lib/spec/hyperschema/index.js +100 -18
  197. package/dist-lib/spec/hyperschema/schema.json +74 -1
  198. package/dist-lib/spec/hyperschema/types.d.ts +11 -3
  199. package/package.json +19 -1
  200. package/patches/@qvac%2Finference@0.20.0.patch +67 -0
  201. package/patches/bare-stow@0.1.5.patch +68 -0
  202. package/patches/hypercore-blob-server@1.15.0.patch +15 -0
  203. package/patches/localwatch@1.4.3.patch +13 -0
  204. package/patches/pyodide@314.0.2.patch +144 -0
@@ -1,7 +1,9 @@
1
1
  import { z } from 'zod';
2
+ import { BUILDERS, BUILDER_POLICY_VERSION } from './builders.js';
3
+ import { createHash } from 'crypto';
4
+ import { stableStringify } from '../../machine/index.js';
2
5
  import { READ_METHODS } from '../http-methods.js';
3
6
  import { truncateResponseBody } from '../http-request/truncate-response.js';
4
- import { BUILDERS } from './builders.js';
5
7
  import { DECODERS } from './decoders.js';
6
8
  export function compileOperationTools(catalog, { httpRequestTool, mcpCallTool }) {
7
9
  return catalog.flatMap((entry) => (entry.operations ?? []).flatMap((operation) => {
@@ -15,6 +17,14 @@ function operationTool(owningSkill, operation, transportTool) {
15
17
  const read = !mcp && READ_METHODS.has(operation.request.method);
16
18
  return {
17
19
  name: operation.tool,
20
+ policyVersion: createHash('sha256')
21
+ .update(stableStringify({
22
+ owningSkill,
23
+ operation,
24
+ builderVersion: BUILDER_POLICY_VERSION,
25
+ transportVersion: transportTool.policyVersion ?? null
26
+ }))
27
+ .digest('hex'),
18
28
  owningSkill,
19
29
  description: operation.description,
20
30
  parameters: operation.parameters,
@@ -4,6 +4,7 @@ import { DECODER_NAMES } from './decoders.js';
4
4
  const ParameterType = z.enum(['string', 'number', 'boolean', 'object', 'integer', 'array', 'null']);
5
5
  const ParameterSchema = z.lazy(() => z.object({
6
6
  type: z.union([ParameterType, z.array(ParameterType).min(1)]),
7
+ sensitive: z.literal(true).optional(),
7
8
  description: z.string().optional(),
8
9
  enum: z.array(z.string()).optional(),
9
10
  items: ParameterSchema.optional(),
@@ -1,11 +1,13 @@
1
+ import type { TrustedOrigin, TrustedSource, TrustedSourceFacts } from '../machine/index.ts';
1
2
  export declare const REFUSAL = "Refusing to send:";
2
- export type TrustedOrigin = 'user' | 'tool-result' | 'skill';
3
- export interface TrustedSource {
4
- origin: TrustedOrigin;
5
- text: string;
6
- }
3
+ export type { TrustedOrigin, TrustedSource };
7
4
  export interface GuardOptions {
8
5
  readonly sources: readonly TrustedSource[];
9
6
  externalWrite?: boolean;
10
7
  }
11
- export declare function guardToolCall(toolName: string, args: Record<string, unknown>, { sources, externalWrite }: GuardOptions): string | null;
8
+ export interface GuardFactOptions extends GuardOptions {
9
+ readonly sourceFacts?: readonly TrustedSourceFacts[];
10
+ }
11
+ export declare function trustedSourceFacts(origin: TrustedSourceFacts['origin'], text: string): TrustedSourceFacts;
12
+ export declare function guardToolCall(toolName: string, args: Record<string, unknown>, options: GuardOptions): string | null;
13
+ export declare function guardToolCallWithFacts(toolName: string, args: Record<string, unknown>, { sources, sourceFacts, externalWrite }: GuardFactOptions): string | null;
@@ -9,7 +9,18 @@ const OUTBOUND_TOOLS = new Set(['http_request', 'mcp_call']);
9
9
  const MCP_READ_METHOD = /^(tools\/list|resources\/\w+|(get|list|search|read|find|fetch)[_.-])/i;
10
10
  const HINT_LIMIT = 5;
11
11
  export const REFUSAL = 'Refusing to send:';
12
- export function guardToolCall(toolName, args, { sources, externalWrite = false }) {
12
+ export function trustedSourceFacts(origin, text) {
13
+ const addresses = text.includes('@') ? unique(text.match(ADDRESS) ?? []) : [];
14
+ return {
15
+ origin,
16
+ addresses,
17
+ refused: text.includes(REFUSAL)
18
+ };
19
+ }
20
+ export function guardToolCall(toolName, args, options) {
21
+ return guardToolCallWithFacts(toolName, args, options);
22
+ }
23
+ export function guardToolCallWithFacts(toolName, args, { sources, sourceFacts = [], externalWrite = false }) {
13
24
  if (!externalWrite && !isOutbound(toolName, args))
14
25
  return null;
15
26
  const addresses = unique(scanPayload(args));
@@ -20,15 +31,15 @@ export function guardToolCall(toolName, args, { sources, externalWrite = false }
20
31
  if (reserved) {
21
32
  return `${REFUSAL} ${mask(reserved)} is a reserved documentation domain (RFC 2606) that cannot receive mail — a placeholder copied from an example, not a real address. Ask the user for the actual address.`;
22
33
  }
23
- const trusted = addressesIn(sources, (origin) => origin !== 'skill');
34
+ const trusted = addressesIn(sources, sourceFacts, (origin) => origin !== 'skill');
24
35
  const unverified = addresses.filter((value) => !grounded(value, trusted).ok);
25
36
  if (!unverified.length) {
26
37
  return null;
27
38
  }
28
- if (alreadyRefusedThisTurn(sources)) {
39
+ if (alreadyRefusedThisTurn(sources, sourceFacts)) {
29
40
  return `${REFUSAL} ${masked(unverified)} still cannot be verified, and an earlier call in this turn was already refused for the same reason. Stop calling this tool. Reply to the user in plain text and ask them for the address.`;
30
41
  }
31
- const fromSkill = addressesIn(sources, (origin) => origin === 'skill');
42
+ const fromSkill = addressesIn(sources, sourceFacts, (origin) => origin === 'skill');
32
43
  const placeholders = unverified.filter((value) => grounded(value, fromSkill).ok);
33
44
  if (placeholders.length) {
34
45
  return `${REFUSAL} ${masked(placeholders)} appears only in the skill's own documentation — an example placeholder, not a real address. Ask the user for the actual address.`;
@@ -53,16 +64,17 @@ function invented(unverified, trusted) {
53
64
  }
54
65
  return `${base} Addresses that do appear: ${trusted.slice(0, HINT_LIMIT).join(', ')}. Use one of those if it is the intended target; otherwise ask the user instead of retrying.`;
55
66
  }
56
- function alreadyRefusedThisTurn(sources) {
67
+ function alreadyRefusedThisTurn(sources, sourceFacts) {
57
68
  let turnStart = 0;
58
69
  for (const [index, source] of sources.entries()) {
59
70
  if (source.origin === 'user') {
60
71
  turnStart = index + 1;
61
72
  }
62
73
  }
63
- return sources
74
+ return (sources
64
75
  .slice(turnStart)
65
- .some((source) => source.origin === 'tool-result' && source.text.includes(REFUSAL));
76
+ .some((source) => source.origin === 'tool-result' && source.text.includes(REFUSAL)) ||
77
+ sourceFacts.some((source) => source.origin === 'tool-result' && source.refused));
66
78
  }
67
79
  function isOutbound(toolName, args) {
68
80
  if (!OUTBOUND_TOOLS.has(toolName))
@@ -100,13 +112,18 @@ function decodeEncoded(value) {
100
112
  return null;
101
113
  }
102
114
  }
103
- function addressesIn(sources, accept) {
115
+ function addressesIn(sources, sourceFacts, accept) {
104
116
  const found = [];
105
117
  for (const source of sources) {
106
118
  if (!accept(source.origin))
107
119
  continue;
108
120
  found.push(...(source.text.match(ADDRESS) ?? []));
109
121
  }
122
+ for (const source of sourceFacts) {
123
+ if (!accept(source.origin))
124
+ continue;
125
+ found.push(...source.addresses);
126
+ }
110
127
  return unique(found);
111
128
  }
112
129
  function unique(values) {
@@ -10,6 +10,7 @@ import type { CapabilityProfile } from '../../models/model-catalog.ts';
10
10
  export interface SkillCatalogEntry {
11
11
  name: string;
12
12
  description: string;
13
+ contentDigest?: string;
13
14
  aliases?: string[];
14
15
  preloadOnName?: boolean;
15
16
  tools: string[];
@@ -1,6 +1,8 @@
1
1
  import fs from 'fs';
2
+ import { createHash } from 'crypto';
2
3
  import { platform } from 'os';
3
4
  import path from 'path';
5
+ import { errorCode } from '../../guards.js';
4
6
  import { parseSkillFrontMatter } from './frontmatter-parser.js';
5
7
  import { buildCliValidator, loadSkillCli } from './cli-schema.js';
6
8
  import { isModelCompatible, isStub, isToolSupported, resolveSkillState, SKILL_STATE, supportsPlatform } from './skill-state.js';
@@ -79,6 +81,9 @@ function mergeOverlay(base, overlay) {
79
81
  const { origin: _origin, mcpReads: _mcpReads, operations: _operations, ...rest } = overlay;
80
82
  return {
81
83
  ...rest,
84
+ contentDigest: createHash('sha256')
85
+ .update(`${base.contentDigest ?? ''}:${overlay.contentDigest ?? ''}`)
86
+ .digest('hex'),
82
87
  credentials: base.credentials,
83
88
  allowList: base.allowList,
84
89
  platform: base.platform,
@@ -151,11 +156,18 @@ async function loadEntry(skillsDir, dir, origin) {
151
156
  const tools = meta.tools ?? [];
152
157
  const cli = await loadSkillCli(skillDir, tools);
153
158
  const referenceFiles = await loadReferenceFiles(skillDir);
159
+ const contentHash = createHash('sha256').update(raw);
160
+ for (const file of referenceFiles) {
161
+ contentHash.update(file);
162
+ contentHash.update(await fs.promises.readFile(path.join(skillDir, file)));
163
+ }
164
+ await hashSkillScripts(path.join(skillDir, 'scripts'), contentHash);
154
165
  const operations = origin === 'overlay' ? [] : await loadOperations(skillDir, dir);
155
166
  const platform = platformsOf(meta.platform, origin);
156
167
  return {
157
168
  name: dir,
158
169
  description: meta.description ?? '',
170
+ contentDigest: contentHash.digest('hex'),
159
171
  aliases: meta.aliases,
160
172
  preloadOnName: meta.preloadOnName === false ? false : undefined,
161
173
  tools,
@@ -194,6 +206,29 @@ async function loadOperations(skillDir, name) {
194
206
  }
195
207
  return operations;
196
208
  }
209
+ async function hashSkillScripts(directory, hash, prefix = 'scripts') {
210
+ let entries;
211
+ try {
212
+ entries = await fs.promises.readdir(directory, { withFileTypes: true });
213
+ }
214
+ catch (error) {
215
+ if (errorCode(error) === 'ENOENT') {
216
+ return;
217
+ }
218
+ throw error;
219
+ }
220
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
221
+ const file = path.join(directory, entry.name);
222
+ const relative = `${prefix}/${entry.name}`;
223
+ if (entry.isDirectory()) {
224
+ await hashSkillScripts(file, hash, relative);
225
+ }
226
+ else if (entry.isFile() && entry.name.endsWith('.py')) {
227
+ hash.update(relative);
228
+ hash.update(await fs.promises.readFile(file));
229
+ }
230
+ }
231
+ }
197
232
  async function loadReferenceFiles(skillDir) {
198
233
  try {
199
234
  const entries = await fs.promises.readdir(path.join(skillDir, 'references'));
@@ -24,6 +24,7 @@ export interface SkillCli {
24
24
  export declare const CLI_SCHEMA_FILENAME = "cli.schema.json";
25
25
  export interface CliValidator {
26
26
  readonly bin: string;
27
+ readonly fingerprint: string;
27
28
  readonly check: (argv: readonly string[]) => string | null;
28
29
  readonly annotation: (argv: readonly string[]) => 'read' | 'write';
29
30
  readonly effect: (argv: readonly string[]) => 'read' | 'write';
@@ -2,11 +2,35 @@ export { CLI_SCHEMA_FILENAME } from './cli-schema-types.js';
2
2
  export { argvToObject } from './cli-argv.js';
3
3
  export { loadSkillCli, resolveCliSchema, execBinFromTools } from './cli-schema-load.js';
4
4
  import { buildSchemaAnnotation, buildSchemaCheck } from './cli-schema-check.js';
5
+ import { createHash } from 'crypto';
6
+ import fs from 'fs';
7
+ import { errorCode } from '../../guards.js';
8
+ import { stableStringify } from '../../machine/index.js';
9
+ const CLI_POLICY_VERSION = 1;
10
+ function resourceIdentity(resourcePath) {
11
+ try {
12
+ const digest = createHash('sha256').update(fs.readFileSync(resourcePath)).digest('hex');
13
+ return [resourcePath, digest];
14
+ }
15
+ catch (error) {
16
+ const code = errorCode(error) ?? 'unreadable';
17
+ return [resourcePath, `unreadable:${code}`];
18
+ }
19
+ }
5
20
  export function buildCliValidator(def) {
6
21
  const bin = def.bin.toLowerCase();
22
+ const fingerprint = createHash('sha256')
23
+ .update(stableStringify({
24
+ version: CLI_POLICY_VERSION,
25
+ bin,
26
+ schema: def.schema ?? null,
27
+ resources: (def.resourcePaths ?? []).map(resourceIdentity)
28
+ }))
29
+ .digest('hex');
7
30
  if (!def.schema) {
8
31
  return {
9
32
  bin,
33
+ fingerprint,
10
34
  check: () => 'skill cli schema is missing',
11
35
  annotation: () => 'write',
12
36
  effect: () => 'write'
@@ -16,6 +40,7 @@ export function buildCliValidator(def) {
16
40
  const annotation = buildSchemaAnnotation(def.schema);
17
41
  return {
18
42
  bin,
43
+ fingerprint,
19
44
  check,
20
45
  annotation,
21
46
  effect: (argv) => (check(argv) === null ? annotation(argv) : 'write')
@@ -1,5 +1,8 @@
1
1
  import type { HarnessEvent } from './contract-types.ts';
2
2
  export interface WireEvent {
3
+ runId?: string | null;
4
+ round?: number | null;
5
+ replacedTypes?: string[] | null;
3
6
  type: string;
4
7
  text?: string | null;
5
8
  name?: string | null;
@@ -40,6 +43,8 @@ export interface WireDownloadEvent {
40
43
  assetId?: string | null;
41
44
  message?: string | null;
42
45
  }
46
+ export declare function isPlainWireValue(value: unknown): boolean;
47
+ export declare function redactedRecoveryFields(source: Record<string, unknown>): Record<string, unknown>;
43
48
  export declare function toWire(event: HarnessEvent): WireEvent;
44
49
  export declare function fromWire(event: WireEvent): HarnessEvent;
45
50
  export declare function toDownloadWire(event: HarnessEvent): WireDownloadEvent;
@@ -3,14 +3,66 @@ import { isRecord } from './guards.js';
3
3
  function stringField(value) {
4
4
  return typeof value === 'string' ? value : undefined;
5
5
  }
6
+ function isRunVersion(value) {
7
+ return (typeof value === 'string' &&
8
+ value.length <= 40 &&
9
+ /^\d{1,10}(?:\.\d{1,10}){0,3}(?:-[A-Za-z0-9.-]{1,16})?$/.test(value));
10
+ }
6
11
  function objectField(value) {
7
12
  return isRecord(value) ? value : {};
8
13
  }
14
+ export function isPlainWireValue(value) {
15
+ if (value === null)
16
+ return true;
17
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
18
+ return true;
19
+ }
20
+ return (Array.isArray(value) &&
21
+ value.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'));
22
+ }
9
23
  function isResource(value) {
10
24
  return isRecord(value) && typeof value.kind === 'string' && typeof value.label === 'string';
11
25
  }
26
+ const REDACTED_RECOVERY_KEYS = new Set(['forkAt', 'forkSafe', 'recoveryOperationIds']);
27
+ export function redactedRecoveryFields(source) {
28
+ const fields = {};
29
+ for (const [key, value] of Object.entries(source)) {
30
+ if (!REDACTED_RECOVERY_KEYS.has(key))
31
+ continue;
32
+ if (isPlainWireValue(value)) {
33
+ fields[key] = value;
34
+ }
35
+ }
36
+ return fields;
37
+ }
12
38
  export function toWire(event) {
39
+ const wire = toWirePayload(event);
40
+ if (typeof event.runId === 'string') {
41
+ wire.runId = event.runId;
42
+ }
43
+ if (typeof event.round === 'number') {
44
+ wire.round = event.round;
45
+ }
46
+ if (typeof event.callId === 'string') {
47
+ wire.callId = event.callId;
48
+ }
49
+ return wire;
50
+ }
51
+ function toWirePayload(event) {
13
52
  switch (event.type) {
53
+ case 'run-started':
54
+ case 'run-resumed':
55
+ case 'paused':
56
+ return { type: event.type };
57
+ case 'round-reset': {
58
+ const wire = { type: event.type };
59
+ if (Array.isArray(event.replacedTypes)) {
60
+ wire.replacedTypes = event.replacedTypes.filter((value) => typeof value === 'string');
61
+ }
62
+ return wire;
63
+ }
64
+ case 'recovery-required':
65
+ return { type: event.type, payload: event.operation };
14
66
  case 'content':
15
67
  case 'thinking':
16
68
  return { type: event.type, text: event.text };
@@ -39,7 +91,7 @@ export function toWire(event) {
39
91
  decision: stringField(event.decision)
40
92
  };
41
93
  case 'metrics': {
42
- const { type, ...metrics } = event;
94
+ const { type, runId, ...metrics } = event;
43
95
  return { type, metrics };
44
96
  }
45
97
  case 'inference-call':
@@ -56,8 +108,29 @@ export function toWire(event) {
56
108
  reason: event.reason
57
109
  }
58
110
  };
59
- case 'error':
60
- return { type: 'error', message: event.message };
111
+ case 'error': {
112
+ const diagnostic = {};
113
+ if (event.code === 'PENDING_ARGS_REDACTED') {
114
+ diagnostic.code = event.code;
115
+ Object.assign(diagnostic, redactedRecoveryFields(event));
116
+ }
117
+ if (event.name === 'RunIncompatible' &&
118
+ (event.kind === 'machine' || event.kind === 'log' || event.kind === 'xstate') &&
119
+ isRunVersion(event.found) &&
120
+ isRunVersion(event.expected)) {
121
+ diagnostic.kind = event.kind;
122
+ diagnostic.found = event.found;
123
+ diagnostic.expected = event.expected;
124
+ }
125
+ const wire = { type: 'error', message: event.message };
126
+ if (typeof event.name === 'string') {
127
+ wire.name = event.name;
128
+ }
129
+ if (Object.keys(diagnostic).length > 0) {
130
+ wire.payload = diagnostic;
131
+ }
132
+ return wire;
133
+ }
61
134
  case 'aborted':
62
135
  return { type: 'aborted' };
63
136
  default:
@@ -65,7 +138,34 @@ export function toWire(event) {
65
138
  }
66
139
  }
67
140
  export function fromWire(event) {
141
+ const decoded = fromWirePayload(event);
142
+ if (event.runId) {
143
+ decoded.runId = event.runId;
144
+ }
145
+ if (event.runId &&
146
+ event.round !== null &&
147
+ event.round !== undefined &&
148
+ ['content', 'thinking', 'tool-call', 'tool-result', 'tool-activity', 'round-reset'].includes(event.type)) {
149
+ decoded.round = event.round;
150
+ }
151
+ if (event.callId) {
152
+ decoded.callId = event.callId;
153
+ }
154
+ return decoded;
155
+ }
156
+ function fromWirePayload(event) {
68
157
  switch (event.type) {
158
+ case 'run-started':
159
+ case 'run-resumed':
160
+ case 'paused':
161
+ return { type: event.type };
162
+ case 'round-reset':
163
+ return {
164
+ type: event.type,
165
+ replacedTypes: event.replacedTypes ?? undefined
166
+ };
167
+ case 'recovery-required':
168
+ return { type: event.type, operation: event.payload };
69
169
  case 'content':
70
170
  case 'thinking':
71
171
  return { type: event.type, text: event.text ?? '' };
@@ -109,8 +209,31 @@ export function fromWire(event) {
109
209
  reason: sent.reason || undefined
110
210
  };
111
211
  }
112
- case 'error':
113
- return { type: 'error', message: event.message ?? 'harness error' };
212
+ case 'error': {
213
+ const diagnostic = objectField(event.payload);
214
+ const decoded = {
215
+ type: 'error',
216
+ message: event.message ?? 'harness error'
217
+ };
218
+ if (event.name) {
219
+ decoded.name = event.name;
220
+ }
221
+ if (diagnostic.code === 'PENDING_ARGS_REDACTED') {
222
+ decoded.code = diagnostic.code;
223
+ Object.assign(decoded, redactedRecoveryFields(diagnostic));
224
+ }
225
+ if (event.name === 'RunIncompatible' &&
226
+ (diagnostic.kind === 'machine' ||
227
+ diagnostic.kind === 'log' ||
228
+ diagnostic.kind === 'xstate') &&
229
+ isRunVersion(diagnostic.found) &&
230
+ isRunVersion(diagnostic.expected)) {
231
+ decoded.kind = diagnostic.kind;
232
+ decoded.found = diagnostic.found;
233
+ decoded.expected = diagnostic.expected;
234
+ }
235
+ return decoded;
236
+ }
114
237
  case 'aborted':
115
238
  return { type: 'aborted' };
116
239
  default:
@@ -70,7 +70,9 @@ export function registerHarnessTypes(ns) {
70
70
  'rag-result',
71
71
  'question-result',
72
72
  'drive-result',
73
- 'invoke'
73
+ 'invoke',
74
+ 'run-log-result',
75
+ 'pause'
74
76
  ],
75
77
  strings: true
76
78
  });
@@ -113,7 +115,8 @@ export function registerHarnessTypes(ns) {
113
115
  { name: 'captureInferenceCalls', type: 'bool' },
114
116
  { name: 'capability', type: 'string' },
115
117
  { name: 'arguments', type: 'json' },
116
- { name: 'enhance', type: 'string' }
118
+ { name: 'enhance', type: 'string' },
119
+ { name: 'runId', type: 'string' }
117
120
  ]
118
121
  });
119
122
  ns.register({
@@ -136,7 +139,13 @@ export function registerHarnessTypes(ns) {
136
139
  'tool-activity',
137
140
  'drive-ask',
138
141
  'inference-call',
139
- 'inference-sent'
142
+ 'inference-sent',
143
+ 'run-started',
144
+ 'run-resumed',
145
+ 'paused',
146
+ 'recovery-required',
147
+ 'round-reset',
148
+ 'run-log-append'
140
149
  ],
141
150
  strings: true
142
151
  });
@@ -170,7 +179,10 @@ export function registerHarnessTypes(ns) {
170
179
  { name: 'payload', type: 'json' },
171
180
  { name: 'intent', type: 'string' },
172
181
  { name: 'display', type: 'json' },
173
- { name: 'rawCall', type: 'string' }
182
+ { name: 'rawCall', type: 'string' },
183
+ { name: 'runId', type: 'string' },
184
+ { name: 'round', type: 'uint' },
185
+ { name: 'replacedTypes', type: 'string', array: true }
174
186
  ]
175
187
  });
176
188
  ns.register({
@@ -339,7 +351,8 @@ export function registerHarnessTypes(ns) {
339
351
  { name: 'supportsImages', type: 'bool', required: true },
340
352
  { name: 'expectedSize', type: 'uint', required: true },
341
353
  { name: 'actualSize', type: 'uint' },
342
- { name: 'label', type: 'string' }
354
+ { name: 'label', type: 'string' },
355
+ { name: 'isLoading', type: 'bool' }
343
356
  ]
344
357
  });
345
358
  ns.register({
@@ -451,7 +464,10 @@ export function registerHarnessTypes(ns) {
451
464
  { name: 'capabilityProfile', type: '@harness/capability-profile', required: true },
452
465
  { name: 'recommendedModelName', type: 'string', required: true },
453
466
  { name: 'memoryBudget', type: '@harness/memory-budget' },
454
- { name: 'mobile', type: 'bool' }
467
+ { name: 'mobile', type: 'bool' },
468
+ { name: 'completionRecovery', type: 'bool' },
469
+ { name: 'hostRunLog', type: 'bool' },
470
+ { name: 'hostRunLogVersion', type: 'uint' }
455
471
  ]
456
472
  });
457
473
  ns.register({