@exellix/graphs-studio-data-flow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +33 -0
- package/src/graphContractMetadata.js +484 -0
- package/src/index.js +8 -0
- package/src/informationFlow.js +225 -0
- package/src/informationFlowFocus.js +332 -0
- package/src/informationFlowLayerDocument.js +69 -0
- package/src/informationFlowOutputSurface.js +339 -0
- package/src/ioLinking.js +236 -0
- package/src/lib/flatRuntimeInput.js +38 -0
- package/src/lib/memorixEntityContentTypes.js +116 -0
- package/src/lib/memorixScopedConfig.js +108 -0
- package/src/lib/nodeMetadataAccessors.js +59 -0
- package/src/lib/recordEligibilityRules.js +542 -0
- package/src/lib/recordFiltersJsonConditionsBridge.js +97 -0
- package/src/lib/taskNodeConfiguration.js +117 -0
- package/src/lib/webQueryTemplate.js +277 -0
- package/src/pathClassification.js +133 -0
- package/src/planning.js +3 -0
- package/src/planningSourceFamily.js +109 -0
- package/src/types.ts +246 -0
- package/src/webScopingPlanning.js +131 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task-node execution authoring — graph-engine 5.0+ (`taskConfiguration`), not `metadata`.
|
|
3
|
+
*
|
|
4
|
+
* Pure metadata on task nodes is allowlisted in `@exellix/graph-engine` (graphReadability,
|
|
5
|
+
* catalogBinding, …); execution wiring lives here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {unknown} node
|
|
10
|
+
* @returns {Record<string, unknown> | null}
|
|
11
|
+
*/
|
|
12
|
+
export function readTaskConfiguration(node) {
|
|
13
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return null;
|
|
14
|
+
const n = /** @type {Record<string, unknown>} */ (node);
|
|
15
|
+
const tc = n.taskConfiguration;
|
|
16
|
+
if (tc && typeof tc === 'object' && !Array.isArray(tc)) {
|
|
17
|
+
return /** @type {Record<string, unknown>} */ (tc);
|
|
18
|
+
}
|
|
19
|
+
const params = n.parameters;
|
|
20
|
+
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
|
21
|
+
const ptc = /** @type {Record<string, unknown>} */ (params).taskConfiguration;
|
|
22
|
+
if (ptc && typeof ptc === 'object' && !Array.isArray(ptc)) {
|
|
23
|
+
return /** @type {Record<string, unknown>} */ (ptc);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Shallow node copy with an isolated `taskConfiguration` bag (avoids cross-node mutation when
|
|
31
|
+
* template/import code reused the same `taskConfiguration` object on multiple nodes).
|
|
32
|
+
*
|
|
33
|
+
* @template {Record<string, unknown>} T
|
|
34
|
+
* @param {T} node
|
|
35
|
+
* @returns {T}
|
|
36
|
+
*/
|
|
37
|
+
export function cloneNodeForTaskConfigurationEdit(node) {
|
|
38
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
39
|
+
return /** @type {T} */ (node);
|
|
40
|
+
}
|
|
41
|
+
const copy = { ...node };
|
|
42
|
+
const tc = node.taskConfiguration;
|
|
43
|
+
if (tc && typeof tc === 'object' && !Array.isArray(tc)) {
|
|
44
|
+
try {
|
|
45
|
+
if (typeof structuredClone === 'function') {
|
|
46
|
+
copy.taskConfiguration = structuredClone(tc);
|
|
47
|
+
} else {
|
|
48
|
+
copy.taskConfiguration = JSON.parse(JSON.stringify(tc));
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
copy.taskConfiguration = { .../** @type {Record<string, unknown>} */ (tc) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if ('modelConfig' in copy) delete copy.modelConfig;
|
|
55
|
+
return /** @type {T} */ (copy);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Record<string, unknown>} node mutates
|
|
60
|
+
* @returns {Record<string, unknown>}
|
|
61
|
+
*/
|
|
62
|
+
export function ensureTaskConfiguration(node) {
|
|
63
|
+
if (
|
|
64
|
+
!node.taskConfiguration ||
|
|
65
|
+
typeof node.taskConfiguration !== 'object' ||
|
|
66
|
+
Array.isArray(node.taskConfiguration)
|
|
67
|
+
) {
|
|
68
|
+
node.taskConfiguration = {};
|
|
69
|
+
}
|
|
70
|
+
return /** @type {Record<string, unknown>} */ (node.taskConfiguration);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Record<string, unknown>} node mutates
|
|
75
|
+
* @param {(input: Record<string, unknown>) => Record<string, unknown> | undefined} update
|
|
76
|
+
*/
|
|
77
|
+
export function patchInputSynthesisOnNode(node, update) {
|
|
78
|
+
const tc = ensureTaskConfiguration(node);
|
|
79
|
+
const profileRaw = tc.aiTaskProfile;
|
|
80
|
+
const profile =
|
|
81
|
+
profileRaw && typeof profileRaw === 'object' && !Array.isArray(profileRaw)
|
|
82
|
+
? /** @type {Record<string, unknown>} */ ({ ...profileRaw })
|
|
83
|
+
: {};
|
|
84
|
+
const isRaw = profile.inputSynthesis;
|
|
85
|
+
const current =
|
|
86
|
+
isRaw && typeof isRaw === 'object' && !Array.isArray(isRaw)
|
|
87
|
+
? /** @type {Record<string, unknown>} */ ({ ...isRaw })
|
|
88
|
+
: {};
|
|
89
|
+
const next = update(current);
|
|
90
|
+
if (!next || Object.keys(next).length === 0) {
|
|
91
|
+
delete profile.inputSynthesis;
|
|
92
|
+
} else {
|
|
93
|
+
profile.inputSynthesis = next;
|
|
94
|
+
}
|
|
95
|
+
if (Object.keys(profile).length === 0) {
|
|
96
|
+
delete tc.aiTaskProfile;
|
|
97
|
+
} else {
|
|
98
|
+
tc.aiTaskProfile = profile;
|
|
99
|
+
}
|
|
100
|
+
pruneEmptyTaskConfiguration(node);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { patchWebQueryTemplateOnNode, patchWebScopingOnNode } from './webQueryTemplate.js';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Drop empty `taskConfiguration` after edits.
|
|
107
|
+
*
|
|
108
|
+
* @param {unknown} node mutates
|
|
109
|
+
*/
|
|
110
|
+
export function pruneEmptyTaskConfiguration(node) {
|
|
111
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return;
|
|
112
|
+
const tc = /** @type {Record<string, unknown>} */ (node).taskConfiguration;
|
|
113
|
+
if (!tc || typeof tc !== 'object' || Array.isArray(tc)) return;
|
|
114
|
+
if (Object.keys(tc).length === 0) {
|
|
115
|
+
delete /** @type {Record<string, unknown>} */ (node).taskConfiguration;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphenix 2.7.3+ web scope authoring on `taskConfiguration.aiTaskProfile.webQueryTemplate`.
|
|
3
|
+
* Replaces legacy `aiTaskProfile.webScoping` and narrix web keys.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readTaskConfiguration, ensureTaskConfiguration, pruneEmptyTaskConfiguration } from './taskNodeConfiguration.js';
|
|
7
|
+
|
|
8
|
+
/** Narrix keys forbidden under `taskConfiguration.narrix` (graph-engine 8.6). */
|
|
9
|
+
export const FORBIDDEN_NARRIX_WEB_KEYS = Object.freeze([
|
|
10
|
+
'enableWebScope',
|
|
11
|
+
'forceWebScope',
|
|
12
|
+
'webScopeQuestions',
|
|
13
|
+
'webScoping',
|
|
14
|
+
'webScopeTemplates',
|
|
15
|
+
'webScopeQuestionTemplate',
|
|
16
|
+
'webScopeObjects',
|
|
17
|
+
'webScopeEntityIdPath',
|
|
18
|
+
'webScopeEntityTypePath',
|
|
19
|
+
'enrichWebScopeQuestionFromExecutionRaw',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {unknown} node
|
|
24
|
+
* @returns {Record<string, unknown> | null}
|
|
25
|
+
*/
|
|
26
|
+
export function readAiTaskProfile(node) {
|
|
27
|
+
const tc = readTaskConfiguration(node);
|
|
28
|
+
if (!tc) return null;
|
|
29
|
+
const profile = tc.aiTaskProfile;
|
|
30
|
+
if (!profile || typeof profile !== 'object' || Array.isArray(profile)) return null;
|
|
31
|
+
return /** @type {Record<string, unknown>} */ (profile);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cleanString(value) {
|
|
35
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {unknown} profile
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function hasWebScopeAuthoring(profile) {
|
|
43
|
+
if (!profile || typeof profile !== 'object' || Array.isArray(profile)) return false;
|
|
44
|
+
const p = /** @type {Record<string, unknown>} */ (profile);
|
|
45
|
+
if (cleanString(p.webQueryTemplate)) return true;
|
|
46
|
+
const pack = p.webQueryTemplates;
|
|
47
|
+
return (
|
|
48
|
+
Array.isArray(pack) &&
|
|
49
|
+
pack.some((entry) => typeof entry === 'string' && entry.trim().length > 0)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {unknown} node
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
export function readNodeWebQueryTemplate(node) {
|
|
58
|
+
const profile = readAiTaskProfile(node);
|
|
59
|
+
return profile ? cleanString(profile.webQueryTemplate) : '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {unknown} node
|
|
64
|
+
* @returns {string[]}
|
|
65
|
+
*/
|
|
66
|
+
export function readNodeWebQueryTemplates(node) {
|
|
67
|
+
const profile = readAiTaskProfile(node);
|
|
68
|
+
if (!profile) return [];
|
|
69
|
+
const pack = profile.webQueryTemplates;
|
|
70
|
+
if (!Array.isArray(pack)) return [];
|
|
71
|
+
return pack
|
|
72
|
+
.filter((q) => typeof q === 'string' && q.trim().length > 0)
|
|
73
|
+
.map((q) => /** @type {string} */ (q).trim());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {unknown} node
|
|
78
|
+
* @returns {Record<string, unknown>}
|
|
79
|
+
*/
|
|
80
|
+
export function readNodeWebScopeOptions(node) {
|
|
81
|
+
const profile = readAiTaskProfile(node);
|
|
82
|
+
if (!profile) return {};
|
|
83
|
+
const opts = profile.webScopeOptions;
|
|
84
|
+
if (opts && typeof opts === 'object' && !Array.isArray(opts)) {
|
|
85
|
+
return /** @type {Record<string, unknown>} */ ({ ...opts });
|
|
86
|
+
}
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Web scope PRE unit is active when a non-empty template (or pack) is authored.
|
|
92
|
+
*
|
|
93
|
+
* @param {unknown} node
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
export function readNodeWebScopeEnabled(node) {
|
|
97
|
+
return hasWebScopeAuthoring(readAiTaskProfile(node));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* All authored query strings (primary + pack) for display.
|
|
102
|
+
*
|
|
103
|
+
* @param {unknown} node
|
|
104
|
+
* @returns {string[]}
|
|
105
|
+
*/
|
|
106
|
+
export function readNodeWebQueryStrings(node) {
|
|
107
|
+
const primary = readNodeWebQueryTemplate(node);
|
|
108
|
+
const pack = readNodeWebQueryTemplates(node);
|
|
109
|
+
if (primary && !pack.includes(primary)) return [primary, ...pack];
|
|
110
|
+
if (primary) return [primary, ...pack.filter((q) => q !== primary)];
|
|
111
|
+
return pack;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @deprecated Use {@link readNodeWebScopeOptions} for entity path hints. */
|
|
115
|
+
export function readWebScoping(node) {
|
|
116
|
+
const opts = readNodeWebScopeOptions(node);
|
|
117
|
+
const legacy = readAiTaskProfile(node)?.webScoping;
|
|
118
|
+
if (legacy && typeof legacy === 'object' && !Array.isArray(legacy)) {
|
|
119
|
+
return { .../** @type {Record<string, unknown>} */ (legacy), ...opts };
|
|
120
|
+
}
|
|
121
|
+
return Object.keys(opts).length > 0 ? opts : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @deprecated Alias for {@link readNodeWebScopeEnabled}. */
|
|
125
|
+
export function readNodeWebScopingEnabled(node) {
|
|
126
|
+
return readNodeWebScopeEnabled(node);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** @deprecated Use {@link readNodeWebQueryStrings}. */
|
|
130
|
+
export function readNodeWebScopingQuestions(node) {
|
|
131
|
+
return readNodeWebQueryStrings(node);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {Record<string, unknown>} narrix mutates
|
|
136
|
+
*/
|
|
137
|
+
export function stripForbiddenNarrixWebKeys(narrix) {
|
|
138
|
+
for (const k of FORBIDDEN_NARRIX_WEB_KEYS) {
|
|
139
|
+
if (k in narrix) delete narrix[k];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Migrate legacy `aiTaskProfile.webScoping` → `webQueryTemplate` on a profile object.
|
|
145
|
+
*
|
|
146
|
+
* @param {Record<string, unknown>} profile mutates
|
|
147
|
+
* @returns {boolean} true when migration changed the profile
|
|
148
|
+
*/
|
|
149
|
+
export function migrateLegacyWebScopingOnProfile(profile) {
|
|
150
|
+
const ws = profile.webScoping;
|
|
151
|
+
if (!ws || typeof ws !== 'object' || Array.isArray(ws)) {
|
|
152
|
+
if ('webScoping' in profile) {
|
|
153
|
+
delete profile.webScoping;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const legacy = /** @type {Record<string, unknown>} */ (ws);
|
|
159
|
+
let changed = false;
|
|
160
|
+
|
|
161
|
+
const fromQueryTemplate = cleanString(legacy.queryTemplate);
|
|
162
|
+
const questions = Array.isArray(legacy.questions)
|
|
163
|
+
? legacy.questions.filter((q) => typeof q === 'string' && q.trim()).map((q) => String(q).trim())
|
|
164
|
+
: [];
|
|
165
|
+
|
|
166
|
+
if (!cleanString(profile.webQueryTemplate)) {
|
|
167
|
+
if (fromQueryTemplate) {
|
|
168
|
+
profile.webQueryTemplate = fromQueryTemplate;
|
|
169
|
+
changed = true;
|
|
170
|
+
} else if (questions.length > 0) {
|
|
171
|
+
profile.webQueryTemplate = questions[0];
|
|
172
|
+
if (questions.length > 1) {
|
|
173
|
+
profile.webQueryTemplates = questions.slice(1);
|
|
174
|
+
}
|
|
175
|
+
changed = true;
|
|
176
|
+
} else if (legacy.enabled === true) {
|
|
177
|
+
profile.webQueryTemplate = '{{input.question}}';
|
|
178
|
+
changed = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
delete profile.webScoping;
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {Record<string, unknown>} node mutates
|
|
188
|
+
* @returns {boolean}
|
|
189
|
+
*/
|
|
190
|
+
export function migrateTaskNodeWebQueryTemplate(node) {
|
|
191
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) return false;
|
|
192
|
+
if (node.type === 'finalizer') return false;
|
|
193
|
+
let changed = false;
|
|
194
|
+
|
|
195
|
+
const tc = ensureTaskConfiguration(node);
|
|
196
|
+
const profileRaw = tc.aiTaskProfile;
|
|
197
|
+
const profile =
|
|
198
|
+
profileRaw && typeof profileRaw === 'object' && !Array.isArray(profileRaw)
|
|
199
|
+
? /** @type {Record<string, unknown>} */ ({ ...profileRaw })
|
|
200
|
+
: null;
|
|
201
|
+
|
|
202
|
+
if (profile) {
|
|
203
|
+
if (migrateLegacyWebScopingOnProfile(profile)) changed = true;
|
|
204
|
+
tc.aiTaskProfile = profile;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const narrixRaw = tc.narrix;
|
|
208
|
+
if (narrixRaw && typeof narrixRaw === 'object' && !Array.isArray(narrixRaw)) {
|
|
209
|
+
const narrix = /** @type {Record<string, unknown>} */ ({ ...narrixRaw });
|
|
210
|
+
const before = JSON.stringify(narrix);
|
|
211
|
+
stripForbiddenNarrixWebKeys(narrix);
|
|
212
|
+
if (JSON.stringify(narrix) !== before) {
|
|
213
|
+
tc.narrix = Object.keys(narrix).length > 0 ? narrix : undefined;
|
|
214
|
+
if (!tc.narrix) delete tc.narrix;
|
|
215
|
+
changed = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
pruneEmptyTaskConfiguration(node);
|
|
220
|
+
return changed;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param {Record<string, unknown>} doc mutates
|
|
225
|
+
*/
|
|
226
|
+
export function migrateAuthoringDocWebQueryTemplate(doc) {
|
|
227
|
+
const nodes = doc.nodes ?? doc.graph?.nodes;
|
|
228
|
+
if (!Array.isArray(nodes)) return;
|
|
229
|
+
for (const node of nodes) {
|
|
230
|
+
if (node && typeof node === 'object' && !Array.isArray(node)) {
|
|
231
|
+
migrateTaskNodeWebQueryTemplate(/** @type {Record<string, unknown>} */ (node));
|
|
232
|
+
const params = /** @type {Record<string, unknown>} */ (node).parameters;
|
|
233
|
+
if (params && typeof params === 'object' && !Array.isArray(params)) {
|
|
234
|
+
migrateTaskNodeWebQueryTemplate(params);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {Record<string, unknown>} node mutates
|
|
242
|
+
* @param {(profile: Record<string, unknown>) => Record<string, unknown> | undefined} update
|
|
243
|
+
*/
|
|
244
|
+
export function patchWebQueryTemplateOnNode(node, update) {
|
|
245
|
+
const tc = ensureTaskConfiguration(node);
|
|
246
|
+
const profileRaw = tc.aiTaskProfile;
|
|
247
|
+
const profile =
|
|
248
|
+
profileRaw && typeof profileRaw === 'object' && !Array.isArray(profileRaw)
|
|
249
|
+
? /** @type {Record<string, unknown>} */ ({ ...profileRaw })
|
|
250
|
+
: {};
|
|
251
|
+
migrateLegacyWebScopingOnProfile(profile);
|
|
252
|
+
const next = update(profile);
|
|
253
|
+
if (!next || Object.keys(next).length === 0) {
|
|
254
|
+
delete tc.aiTaskProfile;
|
|
255
|
+
} else {
|
|
256
|
+
migrateLegacyWebScopingOnProfile(next);
|
|
257
|
+
tc.aiTaskProfile = next;
|
|
258
|
+
}
|
|
259
|
+
pruneEmptyTaskConfiguration(node);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** @deprecated Use {@link patchWebQueryTemplateOnNode}. */
|
|
263
|
+
export function patchWebScopingOnNode(node, update) {
|
|
264
|
+
return patchWebQueryTemplateOnNode(node, (profile) => {
|
|
265
|
+
const ws = profile.webScoping;
|
|
266
|
+
const current =
|
|
267
|
+
ws && typeof ws === 'object' && !Array.isArray(ws)
|
|
268
|
+
? /** @type {Record<string, unknown>} */ ({ ...ws })
|
|
269
|
+
: {};
|
|
270
|
+
const next = update(current);
|
|
271
|
+
if (!next || Object.keys(next).length === 0) {
|
|
272
|
+
const { webScoping: _drop, ...rest } = profile;
|
|
273
|
+
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
274
|
+
}
|
|
275
|
+
return { ...profile, webScoping: next };
|
|
276
|
+
});
|
|
277
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify execution-memory paths for Information Flow grouping and display.
|
|
3
|
+
*/
|
|
4
|
+
import { isDebugResponsePath } from './graphContractMetadata.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {'input'|'scoped'|'inference'|'triage'|'scopedAnswer'|'response'|'trace'|'external'|'meta'|'job'|'task'|'xynthesized'|'xynthesis'|'other'} PathClassification
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} path
|
|
12
|
+
* @returns {{ namespace: string, classification: PathClassification, displayPath: string }}
|
|
13
|
+
*/
|
|
14
|
+
export function classifyExecutionPath(path) {
|
|
15
|
+
const raw = String(path || '').trim();
|
|
16
|
+
if (!raw) {
|
|
17
|
+
return { namespace: 'other', classification: 'other', displayPath: '' };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (raw.startsWith('xmemory-op:')) {
|
|
21
|
+
return {
|
|
22
|
+
namespace: 'xmemory-op',
|
|
23
|
+
classification: 'external',
|
|
24
|
+
displayPath: raw,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (raw.startsWith('xmemory-meta:')) {
|
|
28
|
+
return {
|
|
29
|
+
namespace: 'xmemory-meta',
|
|
30
|
+
classification: 'meta',
|
|
31
|
+
displayPath: raw,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (raw === 'finalOutput') {
|
|
36
|
+
return { namespace: 'finalOutput', classification: 'response', displayPath: 'finalOutput' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (raw.startsWith('execution._trace') || raw.includes('._trace.') || isDebugResponsePath(raw)) {
|
|
40
|
+
return {
|
|
41
|
+
namespace: '_trace',
|
|
42
|
+
classification: 'trace',
|
|
43
|
+
displayPath: stripExecutionPrefix(raw),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Distinguish synthesis-context memory snapshot (xynthesized) from PRE/POST utility outputs (xynthesis).
|
|
48
|
+
if (raw.startsWith('execution.xynthesized.')) {
|
|
49
|
+
return {
|
|
50
|
+
namespace: 'xynthesized',
|
|
51
|
+
classification: 'xynthesized',
|
|
52
|
+
displayPath: stripExecutionPrefix(raw),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (raw.startsWith('execution.xynthesis.')) {
|
|
56
|
+
return {
|
|
57
|
+
namespace: 'xynthesis',
|
|
58
|
+
classification: 'xynthesis',
|
|
59
|
+
displayPath: stripExecutionPrefix(raw),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let rest = raw;
|
|
64
|
+
if (rest.startsWith('execution.')) {
|
|
65
|
+
rest = rest.slice('execution.'.length);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const first = rest.split('.')[0] || '';
|
|
69
|
+
|
|
70
|
+
if (first === 'input' || raw.startsWith('execution.input.')) {
|
|
71
|
+
return {
|
|
72
|
+
namespace: 'input',
|
|
73
|
+
classification: 'input',
|
|
74
|
+
displayPath: rest.startsWith('input.') ? rest.slice('input.'.length) : rest,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (first === 'scoped' || rest.startsWith('scoped.')) {
|
|
79
|
+
return {
|
|
80
|
+
namespace: 'scoped',
|
|
81
|
+
classification: 'scoped',
|
|
82
|
+
displayPath: rest,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (first === 'inference' || rest.startsWith('inference.')) {
|
|
86
|
+
return {
|
|
87
|
+
namespace: 'inference',
|
|
88
|
+
classification: 'inference',
|
|
89
|
+
displayPath: rest,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (first === 'triage' || rest.startsWith('triage.')) {
|
|
93
|
+
return {
|
|
94
|
+
namespace: 'triage',
|
|
95
|
+
classification: 'triage',
|
|
96
|
+
displayPath: rest,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (first === 'scopedAnswer' || rest.startsWith('scopedAnswer.')) {
|
|
100
|
+
return {
|
|
101
|
+
namespace: 'scopedAnswer',
|
|
102
|
+
classification: 'scopedAnswer',
|
|
103
|
+
displayPath: rest,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (rest.startsWith('jobMemory.') || rest.startsWith('jobContext.') || raw.startsWith('jobMemory.')) {
|
|
108
|
+
return {
|
|
109
|
+
namespace: 'job',
|
|
110
|
+
classification: 'job',
|
|
111
|
+
displayPath: rest,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (rest.startsWith('taskMemory.') || raw.startsWith('taskMemory.')) {
|
|
115
|
+
return {
|
|
116
|
+
namespace: 'task',
|
|
117
|
+
classification: 'task',
|
|
118
|
+
displayPath: rest,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
namespace: first || 'other',
|
|
124
|
+
classification: 'other',
|
|
125
|
+
displayPath: rest,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Prefer short labels: input.* instead of execution.input.* */
|
|
130
|
+
function stripExecutionPrefix(p) {
|
|
131
|
+
if (p.startsWith('execution.')) return p.slice('execution.'.length);
|
|
132
|
+
return p;
|
|
133
|
+
}
|
package/src/planning.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planning source families for Information Flow — editor-only classification.
|
|
3
|
+
* @typedef {'scoped-read'|'web-scope'|'other'|'none'} PlanningSourceFamily
|
|
4
|
+
*/
|
|
5
|
+
import { readNodeWebScopeEnabled, readNodeWebQueryTemplate } from './lib/nodeMetadataAccessors.js';
|
|
6
|
+
import { readTaskConfiguration } from './lib/taskNodeConfiguration.js';
|
|
7
|
+
|
|
8
|
+
/** Future dedicated web retrieval / search skills; aiTaskProfile.webQueryTemplate is primary. */
|
|
9
|
+
export const WEB_SCOPE_SKILL_KEYS = [];
|
|
10
|
+
|
|
11
|
+
const FAMILY_LABEL = {
|
|
12
|
+
'scoped-read': 'Scoped read',
|
|
13
|
+
'web-scope': 'Web scope',
|
|
14
|
+
other: 'Other',
|
|
15
|
+
none: '—',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const SECTION_ORDER = ['scoped-read', 'web-scope'];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {object|null|undefined} node
|
|
22
|
+
* @returns {PlanningSourceFamily}
|
|
23
|
+
*/
|
|
24
|
+
export function getPlanningSourceFamily(node) {
|
|
25
|
+
if (!node || typeof node !== 'object') return 'none';
|
|
26
|
+
if (node.skillKey === 'scoped-data-reader') return 'scoped-read';
|
|
27
|
+
if (node.skillKey && WEB_SCOPE_SKILL_KEYS.includes(node.skillKey)) return 'web-scope';
|
|
28
|
+
if (readNodeWebScopeEnabled(node)) return 'web-scope';
|
|
29
|
+
return 'none';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {PlanningSourceFamily} family
|
|
34
|
+
*/
|
|
35
|
+
export function getPlanningSourceFamilyLabel(family) {
|
|
36
|
+
return FAMILY_LABEL[family] || family;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Stable section order for Sources column (families only). */
|
|
40
|
+
export function planningSourceFamilySectionOrder() {
|
|
41
|
+
return [...SECTION_ORDER];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {object} node - graph node
|
|
46
|
+
* @returns {{ lines: string[], searchText: string }}
|
|
47
|
+
*/
|
|
48
|
+
export function buildWebScopeCardSummary(node) {
|
|
49
|
+
if (readNodeWebScopeEnabled(node)) {
|
|
50
|
+
const narrix = node?.taskConfiguration?.narrix || node?.metadata?.narrix || {};
|
|
51
|
+
const template = readNodeWebQueryTemplate(node);
|
|
52
|
+
const lines = [];
|
|
53
|
+
const parts = [];
|
|
54
|
+
|
|
55
|
+
if (template) {
|
|
56
|
+
const s = `Template: ${template}`;
|
|
57
|
+
lines.push(s);
|
|
58
|
+
parts.push(s, template);
|
|
59
|
+
}
|
|
60
|
+
if (narrix.questionId) {
|
|
61
|
+
const s = `Question: ${narrix.questionId}`;
|
|
62
|
+
lines.push(s);
|
|
63
|
+
parts.push(s, narrix.questionId);
|
|
64
|
+
}
|
|
65
|
+
if (narrix.datasetId) {
|
|
66
|
+
const s = `Dataset: ${narrix.datasetId}`;
|
|
67
|
+
lines.push(s);
|
|
68
|
+
parts.push(s, narrix.datasetId);
|
|
69
|
+
}
|
|
70
|
+
if (narrix.layer != null && narrix.layer !== '') {
|
|
71
|
+
const s = `Layer: ${narrix.layer}`;
|
|
72
|
+
lines.push(s);
|
|
73
|
+
parts.push(s, String(narrix.layer));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (Array.isArray(narrix.narrativeTypeIds) && narrix.narrativeTypeIds.length > 0) {
|
|
77
|
+
const short = narrix.narrativeTypeIds.slice(0, 4).join(', ');
|
|
78
|
+
const more = narrix.narrativeTypeIds.length > 4 ? ` (+${narrix.narrativeTypeIds.length - 4} more)` : '';
|
|
79
|
+
const s = `Narrative types: ${short}${more}`;
|
|
80
|
+
lines.push(s);
|
|
81
|
+
parts.push(...narrix.narrativeTypeIds.map(String));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
lines,
|
|
86
|
+
searchText: parts.join(' '),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (node?.skillKey && WEB_SCOPE_SKILL_KEYS.includes(node.skillKey)) {
|
|
91
|
+
const sk = node.skillKey;
|
|
92
|
+
return { lines: [`Web skill: ${sk}`], searchText: sk };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { lines: [], searchText: '' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sort key for web-scope flow nodes (stable, deterministic).
|
|
100
|
+
* @param {object} node - raw graph node
|
|
101
|
+
* @param {string} title - graphReadability title or id
|
|
102
|
+
*/
|
|
103
|
+
export function webScopeSortKey(node, title) {
|
|
104
|
+
const tcNar = readTaskConfiguration(node)?.narrix;
|
|
105
|
+
const narrix = tcNar && typeof tcNar === 'object' ? tcNar : {};
|
|
106
|
+
const q = narrix.questionId;
|
|
107
|
+
const d = narrix.datasetId;
|
|
108
|
+
return [q || '', d || '', title || '', node?.id || ''].join('\0');
|
|
109
|
+
}
|