@exellix/ai-tasks 10.0.9 → 10.0.11

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 (187) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +32 -35
  3. package/RUNTASK_REQUEST.md +4 -4
  4. package/dist/activix/phaseTracking.d.ts +1 -1
  5. package/dist/activix/phaseTracking.d.ts.map +1 -1
  6. package/dist/activix/phaseTracking.js.map +1 -1
  7. package/dist/builders/task-request-builder.d.ts +1 -54
  8. package/dist/builders/task-request-builder.d.ts.map +1 -1
  9. package/dist/builders/task-request-builder.js +1 -180
  10. package/dist/builders/task-request-builder.js.map +1 -1
  11. package/dist/core/task-sdk.d.ts +4 -10
  12. package/dist/core/task-sdk.d.ts.map +1 -1
  13. package/dist/core/task-sdk.js +35 -117
  14. package/dist/core/task-sdk.js.map +1 -1
  15. package/dist/errors/runTaskExecutionError.d.ts +1 -1
  16. package/dist/errors/runTaskExecutionError.d.ts.map +1 -1
  17. package/dist/errors/runTaskExecutionError.js +0 -2
  18. package/dist/errors/runTaskExecutionError.js.map +1 -1
  19. package/dist/errors/smartInputValidationError.d.ts +1 -1
  20. package/dist/index.d.ts +5 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/internal/resolveRunTaskRuntimeKnobs.d.ts +2 -5
  25. package/dist/internal/resolveRunTaskRuntimeKnobs.d.ts.map +1 -1
  26. package/dist/internal/resolveRunTaskRuntimeKnobs.js +3 -36
  27. package/dist/internal/resolveRunTaskRuntimeKnobs.js.map +1 -1
  28. package/dist/localTasks/collectEvidence.d.ts.map +1 -1
  29. package/dist/localTasks/collectEvidence.js +13 -23
  30. package/dist/localTasks/collectEvidence.js.map +1 -1
  31. package/dist/localTasks/decideWebScope.js +1 -1
  32. package/dist/localTasks/decideWebScope.js.map +1 -1
  33. package/dist/localTasks/index.d.ts +0 -1
  34. package/dist/localTasks/index.d.ts.map +1 -1
  35. package/dist/localTasks/index.js +2 -4
  36. package/dist/localTasks/index.js.map +1 -1
  37. package/dist/localTasks/playgroundSeedMain.d.ts +4 -0
  38. package/dist/localTasks/playgroundSeedMain.d.ts.map +1 -0
  39. package/dist/localTasks/playgroundSeedMain.js +26 -0
  40. package/dist/localTasks/playgroundSeedMain.js.map +1 -0
  41. package/dist/logxer/aiTasksDiagnosticCodes.d.ts +1 -1
  42. package/dist/logxer/aiTasksDiagnosticCodes.js +1 -1
  43. package/dist/logxer/aiTasksDiagnosticCodes.js.map +1 -1
  44. package/dist/narrix/narrixUnitExecution.d.ts.map +1 -1
  45. package/dist/narrix/narrixUnitExecution.js +1 -6
  46. package/dist/narrix/narrixUnitExecution.js.map +1 -1
  47. package/dist/narrix/webContextMarkdown.d.ts +2 -25
  48. package/dist/narrix/webContextMarkdown.d.ts.map +1 -1
  49. package/dist/narrix/webContextMarkdown.js +33 -57
  50. package/dist/narrix/webContextMarkdown.js.map +1 -1
  51. package/dist/node-execution/buildRequestFromNodePlan.d.ts.map +1 -1
  52. package/dist/node-execution/buildRequestFromNodePlan.js +0 -12
  53. package/dist/node-execution/buildRequestFromNodePlan.js.map +1 -1
  54. package/dist/node-execution/createNodeExecutionHost.d.ts.map +1 -1
  55. package/dist/node-execution/createNodeExecutionHost.js +1 -26
  56. package/dist/node-execution/createNodeExecutionHost.js.map +1 -1
  57. package/dist/node-execution/dispatchExecutionUnit.d.ts.map +1 -1
  58. package/dist/node-execution/dispatchExecutionUnit.js +0 -15
  59. package/dist/node-execution/dispatchExecutionUnit.js.map +1 -1
  60. package/dist/node-execution/executeNodeFromPlan.d.ts.map +1 -1
  61. package/dist/node-execution/executeNodeFromPlan.js +0 -15
  62. package/dist/node-execution/executeNodeFromPlan.js.map +1 -1
  63. package/dist/node-execution/executePlaygroundElement.d.ts +23 -0
  64. package/dist/node-execution/executePlaygroundElement.d.ts.map +1 -0
  65. package/dist/node-execution/executePlaygroundElement.js +80 -0
  66. package/dist/node-execution/executePlaygroundElement.js.map +1 -0
  67. package/dist/node-execution/index.d.ts +2 -0
  68. package/dist/node-execution/index.d.ts.map +1 -1
  69. package/dist/node-execution/index.js +1 -0
  70. package/dist/node-execution/index.js.map +1 -1
  71. package/dist/node-execution/types.d.ts +0 -2
  72. package/dist/node-execution/types.d.ts.map +1 -1
  73. package/dist/observability/classifyRunTaskFailure.d.ts +1 -1
  74. package/dist/observability/classifyRunTaskFailure.d.ts.map +1 -1
  75. package/dist/observability/classifyRunTaskFailure.js +0 -3
  76. package/dist/observability/classifyRunTaskFailure.js.map +1 -1
  77. package/dist/observability/extractAiTasksObservability.d.ts.map +1 -1
  78. package/dist/observability/extractAiTasksObservability.js +0 -8
  79. package/dist/observability/extractAiTasksObservability.js.map +1 -1
  80. package/dist/planWebScopeQuestions/index.d.ts +1 -1
  81. package/dist/planWebScopeQuestions/index.js +1 -1
  82. package/dist/rendrixUpstreamExports.d.ts +1 -1
  83. package/dist/rendrixUpstreamExports.d.ts.map +1 -1
  84. package/dist/rendrixUpstreamExports.js +1 -1
  85. package/dist/rendrixUpstreamExports.js.map +1 -1
  86. package/dist/synthesis/index.d.ts +1 -1
  87. package/dist/synthesis/index.js +1 -1
  88. package/dist/synthesis/resolveSourceMaterial.d.ts +3 -7
  89. package/dist/synthesis/resolveSourceMaterial.d.ts.map +1 -1
  90. package/dist/synthesis/resolveSourceMaterial.js +15 -78
  91. package/dist/synthesis/resolveSourceMaterial.js.map +1 -1
  92. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.d.ts +0 -1
  93. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.d.ts.map +1 -1
  94. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.js +1 -9
  95. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.js.map +1 -1
  96. package/dist/task-strategies/canonicalInputExecutionStrategies.d.ts +3 -4
  97. package/dist/task-strategies/canonicalInputExecutionStrategies.d.ts.map +1 -1
  98. package/dist/task-strategies/canonicalInputExecutionStrategies.js +3 -4
  99. package/dist/task-strategies/canonicalInputExecutionStrategies.js.map +1 -1
  100. package/dist/task-strategies/canonicalTaskStrategies.d.ts +2 -2
  101. package/dist/task-strategies/canonicalTaskStrategies.js +1 -1
  102. package/dist/task-strategies/canonicalTaskStrategies.js.map +1 -1
  103. package/dist/task-strategies/cataloxCatalogViews.d.ts +2 -5
  104. package/dist/task-strategies/cataloxCatalogViews.d.ts.map +1 -1
  105. package/dist/task-strategies/cataloxCatalogViews.js +3 -6
  106. package/dist/task-strategies/cataloxCatalogViews.js.map +1 -1
  107. package/dist/task-strategies/constants.d.ts +0 -4
  108. package/dist/task-strategies/constants.d.ts.map +1 -1
  109. package/dist/task-strategies/constants.js +0 -4
  110. package/dist/task-strategies/constants.js.map +1 -1
  111. package/dist/task-strategies/index.d.ts +4 -6
  112. package/dist/task-strategies/index.d.ts.map +1 -1
  113. package/dist/task-strategies/index.js +4 -5
  114. package/dist/task-strategies/index.js.map +1 -1
  115. package/dist/task-strategies/listAiTaskStrategies.d.ts +0 -2
  116. package/dist/task-strategies/listAiTaskStrategies.d.ts.map +1 -1
  117. package/dist/task-strategies/listAiTaskStrategies.js +1 -5
  118. package/dist/task-strategies/listAiTaskStrategies.js.map +1 -1
  119. package/dist/types/index.d.ts +1 -4
  120. package/dist/types/index.d.ts.map +1 -1
  121. package/dist/types/index.js +0 -1
  122. package/dist/types/index.js.map +1 -1
  123. package/dist/types/task-types.d.ts +9 -133
  124. package/dist/types/task-types.d.ts.map +1 -1
  125. package/dist/types/task-types.js +0 -4
  126. package/dist/types/task-types.js.map +1 -1
  127. package/dist/types/web-scope-types.d.ts +20 -0
  128. package/dist/types/web-scope-types.d.ts.map +1 -0
  129. package/dist/types/web-scope-types.js +2 -0
  130. package/dist/types/web-scope-types.js.map +1 -0
  131. package/dist/utils/runTaskRequestShape.d.ts +1 -5
  132. package/dist/utils/runTaskRequestShape.d.ts.map +1 -1
  133. package/dist/utils/runTaskRequestShape.js +0 -26
  134. package/dist/utils/runTaskRequestShape.js.map +1 -1
  135. package/dist/utils/templateContext.d.ts +16 -0
  136. package/dist/utils/templateContext.d.ts.map +1 -0
  137. package/dist/utils/templateContext.js +35 -0
  138. package/dist/utils/templateContext.js.map +1 -0
  139. package/dist/validation/helpers.d.ts.map +1 -1
  140. package/dist/validation/helpers.js +3 -22
  141. package/dist/validation/helpers.js.map +1 -1
  142. package/dist/validation/validateRunTaskConfig.d.ts.map +1 -1
  143. package/dist/validation/validateRunTaskConfig.js +2 -0
  144. package/dist/validation/validateRunTaskConfig.js.map +1 -1
  145. package/dist/validation/validateRunTaskInvoke.d.ts.map +1 -1
  146. package/dist/validation/validateRunTaskInvoke.js +2 -0
  147. package/dist/validation/validateRunTaskInvoke.js.map +1 -1
  148. package/dist/validation/validateWebScopeUnit.d.ts +16 -0
  149. package/dist/validation/validateWebScopeUnit.d.ts.map +1 -0
  150. package/dist/validation/validateWebScopeUnit.js +107 -0
  151. package/dist/validation/validateWebScopeUnit.js.map +1 -0
  152. package/dist/web-scope/applyWebScopeToRequest.d.ts +11 -0
  153. package/dist/web-scope/applyWebScopeToRequest.d.ts.map +1 -0
  154. package/dist/web-scope/applyWebScopeToRequest.js +42 -0
  155. package/dist/web-scope/applyWebScopeToRequest.js.map +1 -0
  156. package/dist/web-scope/buildSearchInput.d.ts +22 -0
  157. package/dist/web-scope/buildSearchInput.d.ts.map +1 -0
  158. package/dist/web-scope/buildSearchInput.js +108 -0
  159. package/dist/web-scope/buildSearchInput.js.map +1 -0
  160. package/dist/web-scope/client.d.ts +12 -0
  161. package/dist/web-scope/client.d.ts.map +1 -0
  162. package/dist/web-scope/client.js +72 -0
  163. package/dist/web-scope/client.js.map +1 -0
  164. package/dist/web-scope/renderWebQueryTemplate.d.ts +14 -0
  165. package/dist/web-scope/renderWebQueryTemplate.d.ts.map +1 -0
  166. package/dist/web-scope/renderWebQueryTemplate.js +44 -0
  167. package/dist/web-scope/renderWebQueryTemplate.js.map +1 -0
  168. package/dist/web-scope/webContextMarkdown.d.ts +22 -0
  169. package/dist/web-scope/webContextMarkdown.d.ts.map +1 -0
  170. package/dist/web-scope/webContextMarkdown.js +163 -0
  171. package/dist/web-scope/webContextMarkdown.js.map +1 -0
  172. package/documenations/run-task-execution-flow.md +3 -7
  173. package/documenations/web-scoping-in-ai-tasks.md +88 -428
  174. package/package.json +16 -33
  175. package/dist/narrix/applyWebScopeToRequest.d.ts +0 -9
  176. package/dist/narrix/applyWebScopeToRequest.d.ts.map +0 -1
  177. package/dist/narrix/applyWebScopeToRequest.js +0 -156
  178. package/dist/narrix/applyWebScopeToRequest.js.map +0 -1
  179. package/dist/narrix/buildWebScopeScopeInput.d.ts +0 -39
  180. package/dist/narrix/buildWebScopeScopeInput.d.ts.map +0 -1
  181. package/dist/narrix/buildWebScopeScopeInput.js +0 -193
  182. package/dist/narrix/buildWebScopeScopeInput.js.map +0 -1
  183. package/dist/narrix/webScoper.d.ts +0 -43
  184. package/dist/narrix/webScoper.d.ts.map +0 -1
  185. package/dist/narrix/webScoper.js +0 -144
  186. package/dist/narrix/webScoper.js.map +0 -1
  187. package/documenations/activix.md +0 -187
@@ -1,503 +1,163 @@
1
1
  ## Web scoping in `@exellix/ai-tasks`
2
2
 
3
- This document explains, in technical detail, **how web scoping is wired into ai-tasks**, how requests opt into it, what data flows through, and what guarantees / failure modes exist. It is intended for engineers integrating with ai-tasks (e.g. graph builders, skill authors) and for maintainers changing the web scoping plumbing.
3
+ This document explains how web scoping is wired into ai-tasks, how requests opt in, what data flows through `@x12i/web-scoper`, and what guarantees / failure modes exist.
4
4
 
5
5
  ---
6
6
 
7
7
  ## 1. High-level overview
8
8
 
9
- - **Goal**: Enrich a NARRIX run with **external web context** (via `@exellix/narrix-web-scoper`) and expose that context to local tasks through `executionMemory.webContext`.
10
- - **Trigger**: A boolean flag on the task request:
9
+ - **Goal**: Retrieve **external web context** for a search query (optionally enriched with record facts) and expose it on `executionMemory.webContext`.
10
+ - **Package**: [`@x12i/web-scoper`](https://www.npmjs.com/package/@x12i/web-scoper) planning, Tavily search via `@x12i/search-adapter`, normalized `WebContext`.
11
+ - **Adapter layer**: `src/web-scope/` renders the unit's `webQueryTemplate` into `{ question, record?, options? }` (or a pack), then calls the scoper. No Graphenix, Activix, or NARRIX CNI fields are sent.
12
+ - **Trigger** (single, explicit): an `externalPreUtility` execution unit with **`strategyKey: "webScope"`**. There is **no** NARRIX trigger and **no** implicit question fallback.
13
+ - **Search query**: the **rendered `unitParams.webQueryTemplate`** (a Rendrix string, e.g. `"How to solve {{input.vulnerability}}?"`). The template is **required** when the unit is enabled.
14
+ - **Result**:
11
15
 
12
16
  ```ts
13
- RunTaskRequest.narrix = {
14
- datasetId: "...",
15
- enableWebScope: true, // ← opt-in; default is false/absent
16
- };
17
+ ctx.executionMemory.webContext: WebScopeResult | WebScopePackResult
17
18
  ```
18
19
 
19
- - **Where it runs**:
20
- - After a NARRIX run completes successfully (CNI ingested + runner executed).
21
- - Implemented in the **task SDK**, in the same flow that invokes the local NARRIX handler.
22
- - **Where the result lands**:
20
+ Always set when the unit runs, including miss/error shapes (`ok: false`).
23
21
 
24
- ```ts
25
- ctx.executionMemory.webContext: WebScoperResult
26
- ```
27
-
28
- where `WebScoperResult` is defined by `@exellix/narrix-web-scoper` and is always present when `enableWebScope === true`, even on miss/error.
29
-
30
- - **Failure stance**: **Lenient.** If web scoping fails or misses, the NARRIX task still runs and `_narrix` output is still attached. Web scoping only affects `executionMemory.webContext` and observability.
22
+ - **Failure stance**: **Lenient at runtime** (web scoping failures do not abort MAIN execution), but **loud and early at validation**: a missing/unresolvable `webQueryTemplate` is a config error.
31
23
 
32
24
  ---
33
25
 
34
- ## 2. Request contract: enabling web scoping
35
-
36
- ### 2.1. Request shape
37
-
38
- ai-tasks exposes NARRIX as a **local task** in the task SDK. The entry-point for callers is a `RunTaskRequest` that may include a `narrix` section used by the NARRIX pre-processor.
39
-
40
- The relevant part of the request is:
41
-
42
- ```ts
43
- // src/types/task-types.ts (conceptual)
44
- export interface NarrixPreProcessorConfig {
45
- datasetId: string;
46
- attachToField?: string;
47
- enableWebScope?: boolean; // ← added for web scoping
48
- }
49
-
50
- export interface RunTaskRequest {
51
- skillKey: string;
52
- input: unknown;
53
- narrix?: NarrixPreProcessorConfig;
54
- // ... other fields: executionMemory, variables, etc.
55
- }
56
- ```
57
-
58
- - **`narrix.datasetId`**: required; selects which NARRIX dataset / pack to run.
59
- - **`narrix.enableWebScope`**:
60
- - **`true`** → ai-tasks will attempt a web scoping call after NARRIX completes.
61
- - **absent / `false`** → web scoping is **completely skipped** for this request.
62
-
63
- ### 2.2. Example from a graph builder (worox-graphs)
26
+ ## 2. Scoper contract (`@x12i/web-scoper`)
64
27
 
65
- Downstream (e.g. `worox-graphs`) forwards configuration from node metadata into the NARRIX config:
66
-
67
- ```jsonc
68
- {
69
- "metadata": {
70
- "narrix": {
71
- "datasetId": "network.vuln.instances",
72
- "enableWebScope": true
73
- }
74
- }
75
- }
76
- ```
77
-
78
- In TypeScript:
28
+ ### 2.1. Single search
79
29
 
80
30
  ```ts
81
- narrix: {
82
- datasetId: node.taskConfiguration.narrix.datasetId,
83
- attachToField: node.taskConfiguration.narrix.attachToField,
84
- enableWebScope: node.taskConfiguration.narrix.enableWebScope ?? false,
85
- }
86
- ```
87
-
88
- Only when that `enableWebScope` flag is `true` will ai-tasks invoke the web scoper.
89
-
90
- ---
91
-
92
- ## 3. Execution flow inside ai-tasks
93
-
94
- ### 3.1. NARRIX run
95
-
96
- The normal NARRIX pipeline in ai-tasks is:
97
-
98
- 1. **Ingest** raw input into CNI via `@exellix/narrix-ingest`.
99
- 2. **Run** the NARRIX runner (`@exellix/narrix-runner`) with the CNI and `datasetId`.
100
- 3. **Attach** the NARRIX result to the task’s execution memory.
101
-
102
- This produces a **success result** with a shape similar to:
103
-
104
- ```ts
105
- export interface NarrixRunOutputSuccess {
106
- entity: unknown;
107
- signals: unknown;
108
- stories: unknown;
109
- passes: unknown;
110
- meta: unknown;
111
- cni?: unknown; // raw CNI (typed more strictly by narrix packages)
112
- }
31
+ await scoper.search({
32
+ question: "Is CVE-2021-44228 patched in OpenSSL 3.0?",
33
+ record?: { cveId: "CVE-2021-44228", product: "openssl" },
34
+ options?: { maxQueries?: 3, freshnessDays?: 30, /* … */ },
35
+ });
36
+ // → { ok: true, context?: WebContext } | { ok: false, error?: { reason, message? } }
113
37
  ```
114
38
 
115
- The success result is attached under `executionMemory._narrix` (or under `attachToField` when configured).
116
-
117
- ### 3.2. Web scoping hook
118
-
119
- After a successful NARRIX run, the task SDK checks `narrix.enableWebScope`:
120
-
121
- 1. If **absent/false**:
122
- - **No web scoping call is made.**
123
- - `executionMemory.webContext` remains `undefined`.
124
- 2. If **true**:
125
- - ai-tasks builds scope fields via **`resolveWebScopeQuestionAndTemplates`** in `src/narrix/buildWebScopeScopeInput.ts`, then either calls **`runWebScope()`** (`scopeGeneric` / `ScopeInput`) or **`runWebScopeQuestionPack()`** (`scopeQuestionPack` / explicit questions) from `src/narrix/webScoper.ts`.
126
- - The result is normalized to **`WebScoperResult`** and written into `executionMemory.webContext` **regardless of hit/miss/error**.
127
-
128
- #### 3.2.1 Search question / templates / explicit questions (ISSUE-006, `@exellix/narrix-web-scoper` 1.2.0+)
129
-
130
- Optional fields on **`RunTaskRequest.narrix`**:
131
-
132
- | Field | Purpose |
133
- |--------|--------|
134
- | **`webScopeTemplates`** | Non-empty array → passed as `ScopeInput.webScopeTemplates`. Uses **athenix-parser** token syntax in the web-scoper; **replaces** normal query planning when set. |
135
- | **`webScopeObjects`** | Merged on top of an **auto-built** context (CNI `knownFacts`, common `entity.graphized.vulnerability.*` fields, `question` / `baseQuestion`, `entityKey` / `label`). |
136
- | **`webScopeQuestionTemplate`** | When **`webScopeTemplates` is not set**, optional **Handlebars** string to produce the search `question` from that context plus `input` (full task input). |
137
- | **`webScopeQuestions`** | When **`webScopeTemplates` is not set**, optional list of explicit scope questions. Non-empty after trim → **`scopeQuestionPack`** with `WebScopeQuestion[]` (`id` generated when omitted). Shape: `Array<{ id?: string; question: string; source?: "manual" \| "ai-driven" }>`. |
138
- | **`webScoping`** | Optional slice of **`WebScoperConfig.scoping`** from `@exellix/narrix-web-scoper` (snippets, caps, raw content, `maxQueries`, `freshnessDays`, etc.). When non-empty, ai-tasks builds a scoper for that request with `createWebScoper({ enabled: true, searchAdapter, scoping: narrix.webScoping })` so behavior matches upstream docs. Omit or leave empty to use the package default scoper (backward compatible). |
139
-
140
- **Precedence:**
141
-
142
- 1. If **`webScopeTemplates`** is set (after trimming empty entries) → scoper receives **`webScopeTemplates`** + merged **`webScopeObjects`** only (no `question` / **`webScopeQuestions`** from this resolver).
143
- 2. Else if **`webScopeQuestions`** is non-empty after normalization → **`runWebScopeQuestionPack`** with **`questions`** only (no single **`question`** / Handlebars path).
144
- 3. Else if **`webScopeQuestionTemplate`** is set → **`question`** = Handlebars render; on template error, fall back to default enrichment.
145
- 4. Else → **`question`** = `input.question` (or string `input`) **plus** appended **CVE / vendor / product** tokens from context when those tokens are **not** already contained in the base question (case-insensitive).
146
-
147
- When there is no base question and no enrichable facts, **`question`** may be omitted (empty resolver result).
148
-
149
- Conceptually, the call looks like:
39
+ ### 2.2. Question pack
150
40
 
151
41
  ```ts
152
- import type { ScopeInput, WebScoperResult } from "@exellix/narrix-web-scoper";
153
- import { runWebScope, runWebScopeQuestionPack } from "../narrix/webScoper.js";
154
- import { resolveWebScopeQuestionAndTemplates } from "../narrix/buildWebScopeScopeInput.js";
155
-
156
- const scopeFields = resolveWebScopeQuestionAndTemplates({
157
- narrix: narrixConfig,
158
- requestInput: request.input,
159
- successResult,
160
- entity: /* record medium only */,
42
+ await scoper.searchMany({
43
+ questions: [{ id: "q1", question: "Patch status?" }, { id: "q2", question: "Workarounds?" }],
44
+ record?: { /* shared facts */ },
45
+ options?: { maxQueries?: 2 },
161
46
  });
162
-
163
- const base = {
164
- datasetId: narrixConfig.datasetId,
165
- subjectId: successResult.entity.entityKey,
166
- entityKind: successResult.entity.entityKind,
167
- entity: /* record */,
168
- cni: successResult.cni,
169
- };
170
-
171
- const webResult: WebScoperResult = scopeFields.packQuestions?.length
172
- ? await runWebScopeQuestionPack({ ...base, questions: scopeFields.packQuestions })
173
- : await runWebScope({ ...base, ...scopeFields } as ScopeInput);
174
-
175
- ctx.executionMemory.webContext = webResult;
47
+ // → WebScopePackResult with results + pack.scopes
176
48
  ```
177
49
 
178
- The **NARRIX output attachment** (`executionMemory._narrix` or the configured `attachToField`) happens independently of web scoping and is not affected by web scoping failures.
50
+ ### 2.3. WebContext shape
51
+
52
+ - **`context.findings[]`**, **`context.sources[]`** (each source requires **`retrievalStage`**: `"discovered" | "fetched" | "extracted"`).
53
+ - **`context.meta`**: `queriesUsed`, `retrievedAt`, `sourceCount`, `evidenceCount`, **`discoveredCount`**, optional intent/strategy/shape.
179
54
 
180
55
  ---
181
56
 
182
- ## 4. `runWebScope` and `WebScoperResult`
57
+ ## 3. How ai-tasks compiles the payload
183
58
 
184
- ### 4.1. The `runWebScope` helper
59
+ Implementation: **`src/web-scope/buildSearchInput.ts`**, **`src/web-scope/renderWebQueryTemplate.ts`**, **`src/web-scope/applyWebScopeToRequest.ts`**, **`src/web-scope/client.ts`**.
185
60
 
186
- `src/narrix/webScoper.ts` provides a thin, testable wrapper around `@exellix/narrix-web-scoper`:
61
+ ### 3.1. Query resolution rendered template only
187
62
 
188
- - Maintains a **singleton** `NarrixWebScoper` instance in production.
189
- - Allows tests to **inject** a fake scoper via `setWebScoperForTesting`.
190
- - Normalizes failures into a consistent `WebScoperResult`.
63
+ The search query is the **rendered `unitParams.webQueryTemplate`**. There is **no** fallback to `input.question`, `taskVariable.question`, or `executionMemory.taskVariables.question`.
191
64
 
192
- The tests in `test/narrix/webScoper.test.ts` show its behavior:
65
+ 1. `webScopeInvokeConfigFromUnitParams(unitParams)` reads `webQueryTemplate` (single) and/or `webQueryTemplates` (pack).
66
+ 2. `renderWebQueryTemplate(template, request)` renders it with `@x12i/rendrix` `render()` against `buildTemplateContext(request)` — the same roots used by validation (`input`, `jobVariables`, `taskVariables`, `executionMemory`, …).
67
+ 3. An empty render (e.g. a template that is only an unresolved token) yields no query → `{ ok: false, error: { reason: "no_queries" } }`.
193
68
 
194
- - **Happy path**:
195
- - When the injected scoper returns `{ available: true, context, cached: false }`,
196
- - `runWebScope` forwards that as-is, and tests assert:
69
+ ### 3.2. Record resolution
197
70
 
198
- ```ts
199
- assert.strictEqual(result.available, true);
200
- assert.deepStrictEqual(result.context, webContext);
201
- ```
71
+ Merged from (when present):
202
72
 
203
- - **Miss / ineligibility**:
204
- - When scoper returns `{ available: false, reason: "notEligible" }`,
205
- - `runWebScope` passes this through without throwing:
73
+ - `input.record` or fields on object `input`
74
+ - `executionMemory.input` / `executionMemory.input.raw`
75
+ - CNI `knownFacts` (when facts are passed into the web-scope hop)
206
76
 
207
- ```ts
208
- assert.strictEqual(result.available, false);
209
- assert.strictEqual(result.reason, "notEligible");
210
- ```
77
+ The record carries **facts only** — the query comes from the template, which references those facts via `{{input.*}}`.
211
78
 
212
- - **Error handling**:
213
- - When the scoper throws (e.g. network error, adapter unavailable),
214
- - `runWebScope` **catches** the error and returns:
79
+ ### 3.3. Options
215
80
 
216
- ```ts
217
- { available: false, reason: "error", error: "<message>" }
218
- ```
81
+ | Source | Maps to |
82
+ |--------|---------|
83
+ | `unitParams.options` | `WebScopeOptions` (`maxQueries`, `freshnessDays`, domain filters, `queryTemplates`, …) |
219
84
 
220
- - Tests assert:
85
+ There are no legacy `webScoping` / `webScopeOptions` / `webScopeQuestions` mappings and no NARRIX option sources.
221
86
 
222
- ```ts
223
- assert.strictEqual(result.available, false);
224
- assert.strictEqual(result.reason, "error");
225
- assert.ok(result.error?.includes("search timed out"));
226
- ```
87
+ ### 3.4. Pack mode
227
88
 
228
- ### 4.2. Result contract
89
+ When `unitParams.webQueryTemplates` is a non-empty `string[]`, each template is rendered independently (`renderWebQueryTemplates`) into a `searchMany` question pack (UUID ids assigned per question). Otherwise a single `search` runs with the rendered `webQueryTemplate`.
229
90
 
230
- The effective contract, as used by ai-tasks, is:
91
+ ### 3.5. The web-scope unit
231
92
 
232
- ```ts
233
- // Hit
234
- {
235
- available: true;
236
- context: WebContext; // rich object with findings, queriesUsed, sources, timestamps, ...
237
- cached: boolean;
238
- }
93
+ Graphenix / playground unit:
239
94
 
240
- // Miss or error
95
+ ```json
241
96
  {
242
- available: false;
243
- reason: WebScoperMissReason | "error";
244
- error?: string; // present when reason === "error"
97
+ "unitKind": "externalPreUtility",
98
+ "strategyKey": "webScope",
99
+ "unitParams": {
100
+ "enableWebScope": true,
101
+ "webQueryTemplate": "How to solve {{input.vulnerability}}?",
102
+ "options": { "maxQueries": 3 }
103
+ }
245
104
  }
246
105
  ```
247
106
 
248
- This shape is **stable from the perspective of local tasks**: they only need to check `available` and, optionally, `reason`/`error`.
249
-
250
- ### 4.3. Source bodies and snippets (`WebSource`)
251
-
252
- Upstream behavior is implemented in **`@exellix/narrix-web-scoper`** (and Tavily field mapping in **`@exellix/search-adapter`**). ai-tasks does not map provider JSON; it forwards **`RunTaskRequest.narrix.webScoping`** into the scoper factory when set. Field names and semantics follow the **`@exellix/narrix-web-scoper` README** (current releases use first-class **`providerContent`** / **`providerRawContent`**; **`content`** / **`rawContent`** are legacy mirrors for the same roles).
253
-
254
- When **`webScoping.includeSourceSnippets`** is **`true`**, each `WebContext.sources[]` entry may include:
255
-
256
- - **`providerContent`** / **`providerRawContent`**: bounded excerpt and optional raw payload from the adapter (when requested).
257
- - **`content`** / **`rawContent`**: deprecated mirrors of **`providerContent`** / **`providerRawContent`**.
258
- - **`snippet`**: primary excerpt for the source, chosen via **`webScoping.sourceExcerptFrom`** (default **`providerContent`**, or **`providerRawContent`**, with deprecated aliases **`content`** / **`rawContent`**).
259
- - **`snippetCharCount`**, **`contentOrigin`**, **`retrievalStage`**, **`contentSource`**, **`score`** / **`rank`**: provenance and metadata when the adapter supplies them (see web-scoper README).
260
-
261
- Defaults are backward compatible: **`includeSourceSnippets` defaults to false**, so these fields are omitted unless you opt in (via **`narrix.webScoping`** or the scoper’s own defaults when extended).
262
-
263
- Optional caps (only apply when snippets are enabled):
264
-
265
- - **`maxSnippetCharsPerSource`**: Unicode cap applied per source to **`providerContent`**, **`providerRawContent`** (and legacy mirrors), and the text chosen for **`snippet`**. When set to a positive number, it is also forwarded as **`snippetMaxChars`** on the shared search request so the adapter can normalize earlier.
266
- - **`maxTotalWebContextChars`**: additional budget applied **only** to **`WebSource.snippet`** across sources (after each snippet’s per-source cap). It does **not** shrink stored **`providerContent`** / **`providerRawContent`**.
267
-
268
- To request raw body text from the provider, set **`webScoping.snippetIncludeRawContent`** (e.g. **`true`** or **`"markdown"`**); it is forwarded as **`includeRawContent`** on search requests. Use **`@exellix/search-adapter`** and **`@exellix/narrix-web-scoper`** versions from the same release family as this repo’s dependencies.
269
-
270
- Example:
271
-
272
- ```ts
273
- narrix: {
274
- datasetId: "network.vuln.instances",
275
- enableWebScope: true,
276
- webScoping: {
277
- includeSourceSnippets: true,
278
- maxQueries: 2,
279
- maxSnippetCharsPerSource: 4000,
280
- snippetIncludeRawContent: false,
281
- },
282
- },
283
- ```
284
-
285
- ---
286
-
287
- ## 5. How `executionMemory` is affected
288
-
289
- ### 5.1. Baseline (without web scoping)
290
-
291
- Without web scoping, for a NARRIX task, `executionMemory` will generally contain:
292
-
293
- - `executionMemory._narrix` — NARRIX run result (entity, signals, stories, passes, meta, etc.).
294
- - Any initial `executionMemory` provided by the caller (e.g. `executionMemory.input.raw` for raw records).
295
-
296
- `executionMemory.webContext` is **undefined** and should not be relied on.
297
-
298
- ### 5.2. With `enableWebScope: true`
299
-
300
- When `enableWebScope` is true:
107
+ Playground input (record/facts only referenced by the template):
301
108
 
302
- - **Always present**:
303
-
304
- ```ts
305
- ctx.executionMemory.webContext: WebScoperResult;
306
- ```
307
-
308
- even when the scoper returns a miss or throws an error.
309
-
310
- - **Never removed / mutated**:
311
- - `_narrix` (or the configured attachment field) is **still present and unchanged**, even when web scoping fails.
312
- - Tests explicitly assert that `_narrix` remains present in the error path.
313
-
314
- Key behaviors from `test/narrix/webScoper.test.ts`:
315
-
316
- - When scoper succeeds:
317
- - `webContext.available === true`
318
- - `_narrix` is still present.
319
- - When scoper fails:
320
- - `_narrix` is **still present**.
321
- - `webContext` exists with `available === false` and `reason === "error"`.
322
-
323
- ### 5.3. Question forwarding and enrichment
324
-
325
- When the caller passes a **natural-language question** in `RunTaskRequest.input.question`, the NARRIX pre-processor derives **`ScopeInput.question`** via **`resolveWebScopeQuestionAndTemplates`** (see §3.2.1): the base string is usually that field, **optionally extended** with CVE / vendor / product from CNI `knownFacts` or common `entity.graphized` paths when those tokens are not already in the question. With **`narrix.webScopeTemplates`**, the scoper uses template-driven queries instead and **`question`** may be omitted.
326
-
327
- ```ts
328
- await runTask({
329
- skillKey: "skills/skill.local:webScopeTestQuestion",
330
- narrix: { datasetId: "test.fixture", enableWebScope: true },
331
- executionMemory: { input: { raw: { id: "v3" } } },
332
- input: { question: "Is this vulnerability critical?" },
333
- });
109
+ ```json
110
+ {
111
+ "vulnerability": "CVE-2021-44228",
112
+ "vendor": "apache"
113
+ }
334
114
  ```
335
115
 
336
- Integration tests (with `USE_NARRIX_INGEST=1`) assert the captured `question` when the fixture does not add conflicting CNI facts. Unit tests in the same file cover template precedence, Handlebars `webScopeQuestionTemplate`, and default append behavior.
337
-
338
- This allows the scoper to tailor its search and context building to the actual question the LLM or downstream logic is trying to answer, with **entity-specific tokens** when available.
116
+ `enableWebScope` (default `true`) is the on/off switch. When `false`, the unit is a no-op.
339
117
 
340
118
  ---
341
119
 
342
- ## 6. Env and configuration
120
+ ## 4. NARRIX (no longer triggers web scope)
343
121
 
344
- ### 6.1. Required env: Tavily API
345
-
346
- The web scoper uses `@exellix/search-adapter` backed by Tavily. It requires:
347
-
348
- - **`TAVILY_API_KEY`** — the API key for Tavily search.
349
-
350
- Behavior:
351
-
352
- - If `TAVILY_API_KEY` is **missing or invalid**:
353
- - The scoper returns `available: false` with an appropriate `reason` (e.g. `"error"`).
354
- - ai-tasks surfaces this as `executionMemory.webContext` with `available: false`.
355
- - The task **continues** normally using NARRIX output only; `_narrix` is still present.
356
-
357
- ### 6.2. Other relevant flags
358
-
359
- - `USE_NARRIX_INGEST`:
360
- - When `"1"`, NARRIX ingest + runner are enabled and web scoping is meaningful.
361
- - When not set, NARRIX-related flows (including web scoping) may be skipped or disabled in tests/playgrounds.
362
-
363
- Optional per-request **`narrix.webScoping`** forwards snippet/cap/raw-content and query limits into `@exellix/narrix-web-scoper` (see §4.3). The main on/off switch remains **`enableWebScope`**.
122
+ Web scoping is **fully decoupled from NARRIX**. The NARRIX preprocessor/handler still builds its CNI attachment (`executionMemory._narrix` or `attachToField`), but it does **not** trigger or configure web search. To web-scope after NARRIX, add an explicit `webScope` unit to the node plan with its own `webQueryTemplate`.
364
123
 
365
124
  ---
366
125
 
367
- ## 7. Error handling and observability
368
-
369
- Web scoping is explicitly designed to **never crash the task**:
126
+ ## 5. Environment
370
127
 
371
- - Any exception in the scoper is caught by `runWebScope`.
372
- - The task SDK:
373
- - Continues to attach `_narrix` as usual.
374
- - Stores a structured error in `executionMemory.webContext` for debugging.
128
+ - **`TAVILY_API_KEY`** required for live search. When missing, `webContext.ok === false` with an appropriate error; the task continues.
375
129
 
376
- This gives you:
130
+ Optional:
377
131
 
378
- - Reliable signals in logs / memory:
379
- - `webContext.available === false`
380
- - `webContext.reason === "error"`
381
- - `webContext.error` contains a human-readable message.
382
- - Freedom to treat web scoping as an **optional enhancement** while still observing its behavior in production.
132
+ - **`WEB_CONTEXT_MARKDOWN_MAX_CHARS`** caps markdown built from web hits for LLM context (`src/narrix/webContextMarkdown.ts`).
383
133
 
384
134
  ---
385
135
 
386
- ## 8. How to consume web scoping from local tasks
387
-
388
- From the perspective of a **local task handler**, web scoping is just another field on `ctx.executionMemory`:
136
+ ## 6. Consuming `webContext` in tasks
389
137
 
390
138
  ```ts
391
- type LocalTaskHandler = (args: {
392
- input: any;
393
- ctx: {
394
- executionMemory?: {
395
- webContext?: WebScoperResult;
396
- _narrix?: unknown;
397
- // ...
398
- };
399
- // ...other context fields...
400
- };
401
- }) => Promise<any>;
402
- ```
403
-
404
- ### 8.1. Recommended consumption pattern
405
-
406
- 1. **Check for presence**:
407
-
408
- ```ts
409
- const webContext = ctx.executionMemory?.webContext;
410
- if (!webContext) {
411
- // web scoping was not enabled for this task
412
- }
413
- ```
414
-
415
- 2. **Branch on `available`**:
416
-
417
- ```ts
418
- if (!webContext.available) {
419
- // Option A: log / record reason and proceed without web data
420
- // Option B: degrade gracefully (e.g. fallback to CNI-only reasoning)
421
- } else {
422
- // Safe to use webContext.context to enrich reasoning
423
- }
424
- ```
425
-
426
- 3. **Do not rely on web scoping for correctness**:
427
- - Handlers should behave sensibly even when `webContext` is absent or `available === false`.
428
- - Web scoping is an **augmentation**, not a hard dependency.
429
-
430
- ### 8.2. Example usage (pseudocode)
431
-
432
- ```ts
433
- registerLocalTask("skills/skill.local:example", async ({ input, ctx }) => {
434
- const narrix = ctx.executionMemory?._narrix;
435
- const web = ctx.executionMemory?.webContext;
436
-
437
- if (web?.available) {
438
- // Combine NARRIX narrative with fresh web evidence
439
- return buildExplanationWithWeb(narrix, web.context);
440
- }
441
-
442
- // Fallback: only NARRIX narrative
443
- return buildExplanationFromNarrixOnly(narrix);
444
- });
139
+ const web = ctx.executionMemory?.webContext;
140
+ if (web && "ok" in web && web.ok === true && web.context) {
141
+ // use web.context.sources, findings, summary
142
+ }
445
143
  ```
446
144
 
447
- ### 8.3. LLM tasks (`runTask` DIRECT) and synthesized-context
448
-
449
- For skills executed through the task SDK with **`includeContextInPrompt: true`**, when NARRIX is in play ai-tasks appends a **Web sources (primary evidence)** markdown section derived from **`executionMemory.webContext`** (when **`available === true`**), using per-source **`providerRawContent`** → **`rawContent`** → **`providerContent`** → **`content`** → **`snippet`**, and labeling **`summary`** / **`findings`** as hints only. The same markdown is merged into source material for the **`synthesized-context`** PRE step under **`narrix-only`** / **`narrix+memory`** (with **`executionMemory.webContext`** stripped from the JSON memory slice when **`narrix+memory`** to avoid duplication). Implementation: **`src/narrix/webContextMarkdown.ts`**. Optional env **`WEB_CONTEXT_MARKDOWN_MAX_CHARS`** caps the size of that markdown string.
145
+ For **`includeContextInPrompt`** / synthesis PRE, ai-tasks builds **Web sources (primary evidence)** markdown when `ok === true`, preferring source `snippet` / `title`. Raw `webContext` JSON is omitted from synthesized memory slices when markdown is included (see `resolveSourceMaterial` tests). Always check **`ok`** on `WebScopeResult` / `WebScopePackResult`.
450
146
 
451
147
  ---
452
148
 
453
- ## 9. Testing strategy and guarantees
454
-
455
- ai-tasks contains tests that exercise both **unit-level** and **integration-level** web scoping behavior:
456
-
457
- - `test/narrix/webScoper.test.ts`:
458
- - Unit-tests **`buildWebScopeTemplateContext`** / **`resolveWebScopeQuestionAndTemplates`** (question enrichment, Handlebars template, `webScopeTemplates` precedence).
459
- - Unit-tests `runWebScope` with mocked `NarrixWebScoper`.
460
- - Verifies:
461
- - Pass-through of `available: true` and `context`.
462
- - Stable miss handling (`available: false`, specific `reason`).
463
- - Error → `available: false`, `reason: "error"`, populated `error`.
464
- - Singleton reset behavior (`resetWebScoperSingleton`, `setWebScoperForTesting`).
465
- - Integration with `runTask`:
466
- - When `enableWebScope` is **absent**:
467
- - `executionMemory.webContext` remains `undefined`.
468
- - When `enableWebScope === true` and scoper returns success:
469
- - `executionMemory.webContext.available === true`.
470
- - `_narrix` is still present.
471
- - When scoper throws:
472
- - `_narrix` is still present.
473
- - `webContext.available === false`, `webContext.reason === "error"`.
474
- - When a question is provided (and `webScopeTemplates` is not set):
475
- - `ScopeInput.question` equals `request.input.question` when there are no extra CNI/entity tokens to append; otherwise it is the **enriched** string (see §3.2.1).
476
- - When `webScopeTemplates` is set:
477
- - `ScopeInput.webScopeTemplates` / `webScopeObjects` are passed through as configured (merged with auto-built context).
478
-
479
- These tests define the **public contract** of web scoping within ai-tasks: any change that breaks their assertions is a breaking change to web scoping behavior.
149
+ ## 7. Testing
480
150
 
481
- ---
151
+ - **`test/web-scope/buildSearchInput.test.ts`** — template rendering, payload compilation, client error handling, `applyWebScopeToRequest`
152
+ - **`test/validation/validateWebScopeUnit.test.ts`** — required `webQueryTemplate` and token resolution checks
153
+ - **`test/narrix/webContextMarkdown.test.ts`** — markdown from `{ ok, context }`
154
+ - **`test/synthesis/resolveSourceMaterial.test.ts`** — web evidence in source material
155
+ - **`test/utils/collectEvidence.test.ts`** — local handler uses web scoper for query discovery
482
156
 
483
- ## 10. Summary of contracts
157
+ Run targeted tests:
484
158
 
485
- - **Opt-in**:
486
- - `RunTaskRequest.narrix.enableWebScope === true` is required to trigger web scoping.
487
- - **Data flow**:
488
- - NARRIX → success result (`_narrix`) → web scoper via `ScopeInput` → `executionMemory.webContext`.
489
- - **Consumer surface**:
490
- - Local tasks read `ctx.executionMemory.webContext` and `_narrix`.
491
- - **Failure behavior**:
492
- - Web scoping is **non-fatal**.
493
- - `_narrix` is always attached on success, independent of web scoping.
494
- - `webContext` is always present when enabled, with `available` + `reason`/`error` for observability.
495
- - **Config**:
496
- - Controlled by per-request `enableWebScope`, optional `webScopeTemplates` / `webScopeObjects` / `webScopeQuestionTemplate` / **`webScoping`** on `narrix`, and `TAVILY_API_KEY` env.
497
-
498
- With this, integrators should have enough detail to:
499
-
500
- - Correctly enable web scoping from graphs / callers.
501
- - Reliably consume `executionMemory.webContext` in local tasks.
502
- - Understand and debug web scoping behavior in logs and execution memory without diving into the entire codebase.
159
+ ```bash
160
+ cd ai-tasks && npm run build && npx tsc -p tsconfig.test.json && node --test dist-test/test/web-scope/buildSearchInput.test.js dist-test/test/validation/validateWebScopeUnit.test.js
161
+ ```
503
162
 
163
+ Live Tavily: **`npm run test:e2e:webScope`** (requires `TAVILY_API_KEY`).