@exellix/ai-tasks 10.0.9 → 10.0.10

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 (60) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +15 -15
  3. package/RUNTASK_REQUEST.md +1 -1
  4. package/dist/core/task-sdk.d.ts.map +1 -1
  5. package/dist/core/task-sdk.js +26 -12
  6. package/dist/core/task-sdk.js.map +1 -1
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/localTasks/collectEvidence.d.ts.map +1 -1
  12. package/dist/localTasks/collectEvidence.js +12 -22
  13. package/dist/localTasks/collectEvidence.js.map +1 -1
  14. package/dist/localTasks/decideWebScope.js +1 -1
  15. package/dist/localTasks/decideWebScope.js.map +1 -1
  16. package/dist/localTasks/index.d.ts.map +1 -1
  17. package/dist/localTasks/index.js +2 -0
  18. package/dist/localTasks/index.js.map +1 -1
  19. package/dist/localTasks/playgroundSeedMain.d.ts +4 -0
  20. package/dist/localTasks/playgroundSeedMain.d.ts.map +1 -0
  21. package/dist/localTasks/playgroundSeedMain.js +26 -0
  22. package/dist/localTasks/playgroundSeedMain.js.map +1 -0
  23. package/dist/narrix/narrixUnitExecution.d.ts.map +1 -1
  24. package/dist/narrix/narrixUnitExecution.js +9 -2
  25. package/dist/narrix/narrixUnitExecution.js.map +1 -1
  26. package/dist/narrix/webContextMarkdown.d.ts +4 -25
  27. package/dist/narrix/webContextMarkdown.d.ts.map +1 -1
  28. package/dist/narrix/webContextMarkdown.js +42 -57
  29. package/dist/narrix/webContextMarkdown.js.map +1 -1
  30. package/dist/node-execution/createNodeExecutionHost.js +1 -1
  31. package/dist/node-execution/createNodeExecutionHost.js.map +1 -1
  32. package/dist/node-execution/executePlaygroundElement.d.ts +23 -0
  33. package/dist/node-execution/executePlaygroundElement.d.ts.map +1 -0
  34. package/dist/node-execution/executePlaygroundElement.js +80 -0
  35. package/dist/node-execution/executePlaygroundElement.js.map +1 -0
  36. package/dist/node-execution/index.d.ts +2 -0
  37. package/dist/node-execution/index.d.ts.map +1 -1
  38. package/dist/node-execution/index.js +1 -0
  39. package/dist/node-execution/index.js.map +1 -1
  40. package/dist/types/task-types.d.ts +17 -34
  41. package/dist/types/task-types.d.ts.map +1 -1
  42. package/dist/types/task-types.js.map +1 -1
  43. package/dist/types/web-scope-types.d.ts +22 -0
  44. package/dist/types/web-scope-types.d.ts.map +1 -0
  45. package/dist/types/web-scope-types.js +2 -0
  46. package/dist/types/web-scope-types.js.map +1 -0
  47. package/dist/web-scope/applyWebScopeToRequest.d.ts +11 -0
  48. package/dist/web-scope/applyWebScopeToRequest.d.ts.map +1 -0
  49. package/dist/web-scope/applyWebScopeToRequest.js +75 -0
  50. package/dist/web-scope/applyWebScopeToRequest.js.map +1 -0
  51. package/dist/web-scope/buildSearchInput.d.ts +19 -0
  52. package/dist/web-scope/buildSearchInput.d.ts.map +1 -0
  53. package/dist/web-scope/buildSearchInput.js +178 -0
  54. package/dist/web-scope/buildSearchInput.js.map +1 -0
  55. package/dist/web-scope/client.d.ts +12 -0
  56. package/dist/web-scope/client.d.ts.map +1 -0
  57. package/dist/web-scope/client.js +72 -0
  58. package/dist/web-scope/client.js.map +1 -0
  59. package/documenations/web-scoping-in-ai-tasks.md +101 -424
  60. package/package.json +7 -7
@@ -1,503 +1,180 @@
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 natural-language question (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/` compiles graph/task fields into `{ question, record?, options? }` or a multi-question pack. No Graphenix, Activix, or NARRIX CNI fields are sent to the scoper.
12
+ - **Triggers**:
13
+ - **`RunTaskRequest.narrix.enableWebScope === true`** — after a successful NARRIX preprocessor run.
14
+ - **Standalone PRE unit** — `externalPreUtility` with `strategyKey: "webScope"` (Graphenix node plans, ai-tasks playground).
15
+ - **Result**:
11
16
 
12
17
  ```ts
13
- RunTaskRequest.narrix = {
14
- datasetId: "...",
15
- enableWebScope: true, // ← opt-in; default is false/absent
16
- };
18
+ ctx.executionMemory.webContext: WebScopeResult | WebScopePackResult
17
19
  ```
18
20
 
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**:
21
+ Always set when web scoping is enabled for that hop, including miss/error shapes (`ok: false`).
23
22
 
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.
23
+ - **Failure stance**: **Lenient.** Web scoping failures do not abort NARRIX or MAIN execution.
31
24
 
32
25
  ---
33
26
 
34
- ## 2. Request contract: enabling web scoping
27
+ ## 2. Scoper contract (`@x12i/web-scoper`)
35
28
 
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:
29
+ ### 2.1. Single search
41
30
 
42
31
  ```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)
64
-
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:
79
-
80
- ```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
- }
32
+ await scoper.search({
33
+ question: "Is CVE-2021-44228 patched in OpenSSL 3.0?",
34
+ record?: { cveId: "CVE-2021-44228", product: "openssl" },
35
+ options?: { maxQueries?: 3, freshnessDays?: 30, /* … */ },
36
+ });
37
+ // → { ok: true, context?: WebContext } | { ok: false, error?: { reason, message? } }
113
38
  ```
114
39
 
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:
40
+ ### 2.2. Question pack
150
41
 
151
42
  ```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 */,
43
+ await scoper.searchMany({
44
+ questions: [{ id: "q1", question: "Patch status?" }, { id: "q2", question: "Workarounds?" }],
45
+ record?: { /* shared facts */ },
46
+ options?: { maxQueries?: 2 },
161
47
  });
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;
48
+ // → WebScopePackResult with results + pack.scopes
176
49
  ```
177
50
 
178
- The **NARRIX output attachment** (`executionMemory._narrix` or the configured `attachToField`) happens independently of web scoping and is not affected by web scoping failures.
179
-
180
- ---
181
-
182
- ## 4. `runWebScope` and `WebScoperResult`
51
+ ### 2.3. WebContext shape
183
52
 
184
- ### 4.1. The `runWebScope` helper
53
+ - **`context.findings[]`**, **`context.sources[]`** (each source requires **`retrievalStage`**: `"discovered" | "fetched" | "extracted"`).
54
+ - **`context.meta`**: `queriesUsed`, `retrievedAt`, `sourceCount`, `evidenceCount`, **`discoveredCount`**, optional intent/strategy/shape.
185
55
 
186
- `src/narrix/webScoper.ts` provides a thin, testable wrapper around `@exellix/narrix-web-scoper`:
56
+ ---
187
57
 
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`.
58
+ ## 3. How ai-tasks compiles the payload
191
59
 
192
- The tests in `test/narrix/webScoper.test.ts` show its behavior:
60
+ Implementation: **`src/web-scope/buildSearchInput.ts`**, **`src/web-scope/applyWebScopeToRequest.ts`**, **`src/web-scope/client.ts`**.
193
61
 
194
- - **Happy path**:
195
- - When the injected scoper returns `{ available: true, context, cached: false }`,
196
- - `runWebScope` forwards that as-is, and tests assert:
62
+ ### 3.1. Question resolution order
197
63
 
198
- ```ts
199
- assert.strictEqual(result.available, true);
200
- assert.deepStrictEqual(result.context, webContext);
201
- ```
64
+ 1. `RunTaskRequest.input.question` or string `input`
65
+ 2. `nodePlan.invokeContract.taskVariable.question`
66
+ 3. `executionMemory.taskVariables.question`
202
67
 
203
- - **Miss / ineligibility**:
204
- - When scoper returns `{ available: false, reason: "notEligible" }`,
205
- - `runWebScope` passes this through without throwing:
68
+ ### 3.2. Record resolution
206
69
 
207
- ```ts
208
- assert.strictEqual(result.available, false);
209
- assert.strictEqual(result.reason, "notEligible");
210
- ```
70
+ Merged from (when present):
211
71
 
212
- - **Error handling**:
213
- - When the scoper throws (e.g. network error, adapter unavailable),
214
- - `runWebScope` **catches** the error and returns:
72
+ - `input.record` or non-question fields on object `input`
73
+ - `executionMemory.input` / `executionMemory.input.raw`
74
+ - CNI `knownFacts` (when NARRIX ran first and CNI is passed into the web-scope hop)
215
75
 
216
- ```ts
217
- { available: false, reason: "error", error: "<message>" }
218
- ```
76
+ ### 3.3. Options / legacy graphenix fields
219
77
 
220
- - Tests assert:
78
+ | Source | Maps to |
79
+ |--------|---------|
80
+ | `unitParams.options` | `WebScopeOptions` (preferred) |
81
+ | `unitParams.webScopeOptions` | same |
82
+ | `narrix.webScopeOptions` | same |
83
+ | `narrix.webScoping` / `unitParams.webScoping` | `mapLegacyWebScopingToOptions()` — `maxQueries`, `freshnessDays`, domain filters, **`webScopeTemplates` → `options.queryTemplates`**, etc. |
84
+ | `narrix.webScopeQuestions` / `unitParams.webScopeQuestions` / `unitParams.questions` | `searchMany` pack |
221
85
 
222
- ```ts
223
- assert.strictEqual(result.available, false);
224
- assert.strictEqual(result.reason, "error");
225
- assert.ok(result.error?.includes("search timed out"));
226
- ```
86
+ When a non-empty question list is configured, ai-tasks calls **`searchMany`**; otherwise **`search`** with a single `question`. If neither a question nor pack questions are available, web scoping stores `{ ok: false, error: { reason: "no_queries", … } }`.
227
87
 
228
- ### 4.2. Result contract
88
+ ### 3.4. Standalone PRE web-scope unit
229
89
 
230
- The effective contract, as used by ai-tasks, is:
90
+ Graphenix / playground unit:
231
91
 
232
- ```ts
233
- // Hit
92
+ ```json
234
93
  {
235
- available: true;
236
- context: WebContext; // rich object with findings, queriesUsed, sources, timestamps, ...
237
- cached: boolean;
238
- }
239
-
240
- // Miss or error
241
- {
242
- available: false;
243
- reason: WebScoperMissReason | "error";
244
- error?: string; // present when reason === "error"
94
+ "unitKind": "externalPreUtility",
95
+ "strategyKey": "webScope",
96
+ "unitParams": {
97
+ "enableWebScope": true,
98
+ "options": { "maxQueries": 3 }
99
+ }
245
100
  }
246
101
  ```
247
102
 
248
- This shape is **stable from the perspective of local tasks**: they only need to check `available` and, optionally, `reason`/`error`.
103
+ Playground input:
249
104
 
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:
301
-
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
- });
105
+ ```json
106
+ {
107
+ "question": "What is the patch status for CVE-2021-44228?",
108
+ "record": { "cveId": "CVE-2021-44228", "vendor": "apache" }
109
+ }
334
110
  ```
335
111
 
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.
339
-
340
112
  ---
341
113
 
342
- ## 6. Env and configuration
343
-
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.
114
+ ## 4. NARRIX + web scope
349
115
 
350
- Behavior:
116
+ When **`narrix.enableWebScope === true`**, the NARRIX preprocessor runs first; on success, ai-tasks calls the same web-scope adapter with optional CNI facts merged into `record`.
351
117
 
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.
118
+ Optional **`RunTaskRequest.narrix`** fields (backward compatible):
356
119
 
357
- ### 6.2. Other relevant flags
120
+ - **`webScopeQuestions`** explicit pack (see §3.3)
121
+ - **`webScopeOptions`** — preferred options object
122
+ - **`webScoping`**, **`webScopeTemplates`**, **`webScopeQuestionTemplate`**, **`webScopeObjects`** — legacy; mapped at invoke time where supported
123
+ - **`skipWebScopeWhenExternalWebMarkdownPresent`** — skip when `executionMemory.webContextMarkdown` is already non-empty
358
124
 
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`**.
125
+ NARRIX attachment (`executionMemory._narrix` or `attachToField`) is independent of web scoping.
364
126
 
365
127
  ---
366
128
 
367
- ## 7. Error handling and observability
368
-
369
- Web scoping is explicitly designed to **never crash the task**:
129
+ ## 5. Environment
370
130
 
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.
131
+ - **`TAVILY_API_KEY`** required for live search. When missing, `webContext.ok === false` with an appropriate error; the task continues.
375
132
 
376
- This gives you:
133
+ Optional:
377
134
 
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.
135
+ - **`WEB_CONTEXT_MARKDOWN_MAX_CHARS`** caps markdown built from web hits for LLM context (`src/narrix/webContextMarkdown.ts`).
383
136
 
384
137
  ---
385
138
 
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`:
139
+ ## 6. Consuming `webContext` in tasks
389
140
 
390
141
  ```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>;
142
+ const web = ctx.executionMemory?.webContext;
143
+ if (web && "ok" in web && web.ok === true && web.context) {
144
+ // use web.context.sources, findings, summary
145
+ }
402
146
  ```
403
147
 
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`**:
148
+ 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).
416
149
 
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
- ```
150
+ Legacy **`available`** field from `@exellix/narrix-web-scoper` is **not** used; check **`ok`**.
425
151
 
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.
152
+ ---
429
153
 
430
- ### 8.2. Example usage (pseudocode)
154
+ ## 7. Testing
431
155
 
432
- ```ts
433
- registerLocalTask("skills/skill.local:example", async ({ input, ctx }) => {
434
- const narrix = ctx.executionMemory?._narrix;
435
- const web = ctx.executionMemory?.webContext;
156
+ - **`test/narrix/webScoper.test.ts`** — payload compilation, client error handling, `applyWebScopeToRequest`
157
+ - **`test/narrix/webContextMarkdown.test.ts`** markdown from `{ ok, context }`
158
+ - **`test/synthesis/resolveSourceMaterial.test.ts`** web evidence in source material
159
+ - **`test/utils/collectEvidence.test.ts`** — local handler uses web scoper for query discovery
436
160
 
437
- if (web?.available) {
438
- // Combine NARRIX narrative with fresh web evidence
439
- return buildExplanationWithWeb(narrix, web.context);
440
- }
161
+ Run targeted tests:
441
162
 
442
- // Fallback: only NARRIX narrative
443
- return buildExplanationFromNarrixOnly(narrix);
444
- });
163
+ ```bash
164
+ cd ai-tasks && npm run build && npx tsc -p tsconfig.test.json && node --test dist-test/test/narrix/webScoper.test.js dist-test/test/narrix/webContextMarkdown.test.js
445
165
  ```
446
166
 
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.
167
+ Live Tavily: **`npm run test:e2e:webScope`** (requires `TAVILY_API_KEY`).
450
168
 
451
169
  ---
452
170
 
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.
480
-
481
- ---
482
-
483
- ## 10. Summary of contracts
484
-
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:
171
+ ## 8. Migration from `@exellix/narrix-web-scoper`
499
172
 
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.
173
+ | Before (narrix-web-scoper) | After (`@x12i/web-scoper`) |
174
+ |----------------------------|----------------------------|
175
+ | `ScopeInput` with `datasetId`, `entity`, `cni` | `{ question, record?, options? }` only |
176
+ | `WebScoperResult.available` | `WebScopeResult.ok` |
177
+ | `runWebScope()` / `scopeQuestionPack()` | `search()` / `searchMany()` |
178
+ | `src/narrix/webScoper.ts` | `src/web-scope/` |
503
179
 
180
+ Graph/node authoring should pass **question + record** at the ai-tasks boundary; NARRIX/CNI enrichment stays in the adapter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exellix/ai-tasks",
3
- "version": "10.0.9",
3
+ "version": "10.0.10",
4
4
  "description": "Task orchestration for the Exellix stack: runTask() with local handlers or LLM-backed execution, task-scoped memory/context enrichment, and executor dispatch via @exellix/ai-skills. ERC-compliant.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -66,6 +66,11 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@exellix/ai-skills": "^6.12.3",
69
+ "@exellix/fact-guard-adapters": "file:../fact-guard/packages/adapters",
70
+ "@exellix/fact-guard-enrichment": "file:../fact-guard/packages/enrichment",
71
+ "@exellix/fact-guard-policies": "file:../fact-guard/packages/policies",
72
+ "@exellix/fact-guard-types": "file:../fact-guard/packages/types",
73
+ "@exellix/fact-guard-validation": "file:../fact-guard/packages/validation",
69
74
  "@exellix/memorix-narrix-adapter": "^2.0.0",
70
75
  "@exellix/narrix-adapter-chat": "^2.0.0",
71
76
  "@exellix/narrix-adapter-docs": "^2.0.0",
@@ -76,16 +81,10 @@
76
81
  "@exellix/narrix-cni": "^2.0.0",
77
82
  "@exellix/narrix-ingest": "^2.0.0",
78
83
  "@exellix/narrix-runner": "^2.0.0",
79
- "@exellix/narrix-web-scoper": "^2.0.0",
80
84
  "@exellix/xynthesis": "^4.8.4",
81
85
  "@x12i/activix": "^8.6.3",
82
86
  "@x12i/ai-profiles": "^3.4.0",
83
87
  "@x12i/catalox": "^5.9.7",
84
- "@exellix/fact-guard-adapters": "^1.0.1",
85
- "@exellix/fact-guard-enrichment": "^1.0.1",
86
- "@exellix/fact-guard-policies": "^1.0.1",
87
- "@exellix/fact-guard-types": "^1.0.1",
88
- "@exellix/fact-guard-validation": "^1.0.1",
89
88
  "@x12i/execution-memory-manager": "^1.2.0",
90
89
  "@x12i/funcx": "^4.9.13",
91
90
  "@x12i/graphenix-core": "^2.7.0",
@@ -96,6 +95,7 @@
96
95
  "@x12i/optimixer": "^3.5.2",
97
96
  "@x12i/rendrix": "^4.3.0",
98
97
  "@x12i/search-adapter": "^1.5.1",
98
+ "@x12i/web-scoper": "^1.1.1",
99
99
  "handlebars": "^4.7.8",
100
100
  "nx-cache": "^1.0.2"
101
101
  },