@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.
- package/CHANGELOG.md +12 -0
- package/README.md +15 -15
- package/RUNTASK_REQUEST.md +1 -1
- package/dist/core/task-sdk.d.ts.map +1 -1
- package/dist/core/task-sdk.js +26 -12
- package/dist/core/task-sdk.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/localTasks/collectEvidence.d.ts.map +1 -1
- package/dist/localTasks/collectEvidence.js +12 -22
- package/dist/localTasks/collectEvidence.js.map +1 -1
- package/dist/localTasks/decideWebScope.js +1 -1
- package/dist/localTasks/decideWebScope.js.map +1 -1
- package/dist/localTasks/index.d.ts.map +1 -1
- package/dist/localTasks/index.js +2 -0
- package/dist/localTasks/index.js.map +1 -1
- package/dist/localTasks/playgroundSeedMain.d.ts +4 -0
- package/dist/localTasks/playgroundSeedMain.d.ts.map +1 -0
- package/dist/localTasks/playgroundSeedMain.js +26 -0
- package/dist/localTasks/playgroundSeedMain.js.map +1 -0
- package/dist/narrix/narrixUnitExecution.d.ts.map +1 -1
- package/dist/narrix/narrixUnitExecution.js +9 -2
- package/dist/narrix/narrixUnitExecution.js.map +1 -1
- package/dist/narrix/webContextMarkdown.d.ts +4 -25
- package/dist/narrix/webContextMarkdown.d.ts.map +1 -1
- package/dist/narrix/webContextMarkdown.js +42 -57
- package/dist/narrix/webContextMarkdown.js.map +1 -1
- package/dist/node-execution/createNodeExecutionHost.js +1 -1
- package/dist/node-execution/createNodeExecutionHost.js.map +1 -1
- package/dist/node-execution/executePlaygroundElement.d.ts +23 -0
- package/dist/node-execution/executePlaygroundElement.d.ts.map +1 -0
- package/dist/node-execution/executePlaygroundElement.js +80 -0
- package/dist/node-execution/executePlaygroundElement.js.map +1 -0
- package/dist/node-execution/index.d.ts +2 -0
- package/dist/node-execution/index.d.ts.map +1 -1
- package/dist/node-execution/index.js +1 -0
- package/dist/node-execution/index.js.map +1 -1
- package/dist/types/task-types.d.ts +17 -34
- package/dist/types/task-types.d.ts.map +1 -1
- package/dist/types/task-types.js.map +1 -1
- package/dist/types/web-scope-types.d.ts +22 -0
- package/dist/types/web-scope-types.d.ts.map +1 -0
- package/dist/types/web-scope-types.js +2 -0
- package/dist/types/web-scope-types.js.map +1 -0
- package/dist/web-scope/applyWebScopeToRequest.d.ts +11 -0
- package/dist/web-scope/applyWebScopeToRequest.d.ts.map +1 -0
- package/dist/web-scope/applyWebScopeToRequest.js +75 -0
- package/dist/web-scope/applyWebScopeToRequest.js.map +1 -0
- package/dist/web-scope/buildSearchInput.d.ts +19 -0
- package/dist/web-scope/buildSearchInput.d.ts.map +1 -0
- package/dist/web-scope/buildSearchInput.js +178 -0
- package/dist/web-scope/buildSearchInput.js.map +1 -0
- package/dist/web-scope/client.d.ts +12 -0
- package/dist/web-scope/client.d.ts.map +1 -0
- package/dist/web-scope/client.js +72 -0
- package/dist/web-scope/client.js.map +1 -0
- package/documenations/web-scoping-in-ai-tasks.md +101 -424
- package/package.json +7 -7
|
@@ -1,503 +1,180 @@
|
|
|
1
1
|
## Web scoping in `@exellix/ai-tasks`
|
|
2
2
|
|
|
3
|
-
This document explains
|
|
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**:
|
|
10
|
-
- **
|
|
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
|
-
|
|
14
|
-
datasetId: "...",
|
|
15
|
-
enableWebScope: true, // ← opt-in; default is false/absent
|
|
16
|
-
};
|
|
18
|
+
ctx.executionMemory.webContext: WebScopeResult | WebScopePackResult
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
|
|
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
|
-
|
|
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.
|
|
27
|
+
## 2. Scoper contract (`@x12i/web-scoper`)
|
|
35
28
|
|
|
36
|
-
### 2.1.
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## 4. `runWebScope` and `WebScoperResult`
|
|
51
|
+
### 2.3. WebContext shape
|
|
183
52
|
|
|
184
|
-
|
|
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
|
-
|
|
56
|
+
---
|
|
187
57
|
|
|
188
|
-
|
|
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
|
-
|
|
60
|
+
Implementation: **`src/web-scope/buildSearchInput.ts`**, **`src/web-scope/applyWebScopeToRequest.ts`**, **`src/web-scope/client.ts`**.
|
|
193
61
|
|
|
194
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
```
|
|
64
|
+
1. `RunTaskRequest.input.question` or string `input`
|
|
65
|
+
2. `nodePlan.invokeContract.taskVariable.question`
|
|
66
|
+
3. `executionMemory.taskVariables.question`
|
|
202
67
|
|
|
203
|
-
|
|
204
|
-
- When scoper returns `{ available: false, reason: "notEligible" }`,
|
|
205
|
-
- `runWebScope` passes this through without throwing:
|
|
68
|
+
### 3.2. Record resolution
|
|
206
69
|
|
|
207
|
-
|
|
208
|
-
assert.strictEqual(result.available, false);
|
|
209
|
-
assert.strictEqual(result.reason, "notEligible");
|
|
210
|
-
```
|
|
70
|
+
Merged from (when present):
|
|
211
71
|
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
{ available: false, reason: "error", error: "<message>" }
|
|
218
|
-
```
|
|
76
|
+
### 3.3. Options / legacy graphenix fields
|
|
219
77
|
|
|
220
|
-
|
|
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
|
-
|
|
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.
|
|
88
|
+
### 3.4. Standalone PRE web-scope unit
|
|
229
89
|
|
|
230
|
-
|
|
90
|
+
Graphenix / playground unit:
|
|
231
91
|
|
|
232
|
-
```
|
|
233
|
-
// Hit
|
|
92
|
+
```json
|
|
234
93
|
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
103
|
+
Playground input:
|
|
249
104
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
368
|
-
|
|
369
|
-
Web scoping is explicitly designed to **never crash the task**:
|
|
129
|
+
## 5. Environment
|
|
370
130
|
|
|
371
|
-
-
|
|
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
|
-
|
|
133
|
+
Optional:
|
|
377
134
|
|
|
378
|
-
-
|
|
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
|
-
##
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
+
## 7. Testing
|
|
431
155
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
438
|
-
// Combine NARRIX narrative with fresh web evidence
|
|
439
|
-
return buildExplanationWithWeb(narrix, web.context);
|
|
440
|
-
}
|
|
161
|
+
Run targeted tests:
|
|
441
162
|
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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.
|
|
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
|
},
|