nexus-prime 7.9.8 → 7.9.9
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/dist/agents/adapters/mcp/definitions.js +13 -0
- package/dist/agents/adapters/mcp/handlers/orchestration.d.ts +2 -0
- package/dist/agents/adapters/mcp/handlers/orchestration.js +34 -5
- package/dist/agents/adapters/mcp/handlers/runtime.js +36 -1
- package/dist/agents/adapters/mcp/helpers.js +1 -1
- package/dist/dashboard/routes/runtime.js +9 -0
- package/dist/dashboard/selectors/assets-selector.js +2 -0
- package/dist/dashboard/types.d.ts +1 -0
- package/dist/engines/orchestrator.js +36 -8
- package/dist/engines/pattern-registry.d.ts +12 -0
- package/dist/engines/pattern-registry.js +65 -0
- package/dist/engines/specialist-roster.js +16 -8
- package/dist/engines/user-workflow-trace.d.ts +47 -0
- package/dist/engines/user-workflow-trace.js +281 -0
- package/dist/phantom/runtime.d.ts +4 -0
- package/dist/phantom/runtime.js +31 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ export const FIRST_CLASS_TOOLS = new Set([
|
|
|
15
15
|
'nexus_search',
|
|
16
16
|
'nexus_runtime_health',
|
|
17
17
|
'nexus_run_status',
|
|
18
|
+
'nexus_user_workflow_trace',
|
|
18
19
|
'kernel_run_step',
|
|
19
20
|
'kernel_run_verify',
|
|
20
21
|
'kernel_run_merge',
|
|
@@ -881,6 +882,18 @@ export function buildMcpToolDefinitions() {
|
|
|
881
882
|
required: ['runId'],
|
|
882
883
|
},
|
|
883
884
|
},
|
|
885
|
+
{
|
|
886
|
+
name: 'nexus_user_workflow_trace',
|
|
887
|
+
description: 'Return the local user-agent workflow trace for a run: user goal, agent actions, context, verification, failure signals, and next actions.',
|
|
888
|
+
inputSchema: {
|
|
889
|
+
type: 'object',
|
|
890
|
+
properties: {
|
|
891
|
+
runId: { type: 'string', description: 'Execution run ID' },
|
|
892
|
+
format: { type: 'string', enum: ['text', 'json'], description: 'Response format' },
|
|
893
|
+
},
|
|
894
|
+
required: ['runId'],
|
|
895
|
+
},
|
|
896
|
+
},
|
|
884
897
|
// ── Darwin Loop ──────────────────────────────────────────────────
|
|
885
898
|
{
|
|
886
899
|
name: 'nexus_darwin_propose',
|
|
@@ -10,5 +10,7 @@ type McpResult = {
|
|
|
10
10
|
text: string;
|
|
11
11
|
}>;
|
|
12
12
|
};
|
|
13
|
+
export declare function extractSkillSelectorsFromPrompt(prompt: string): string[];
|
|
14
|
+
export declare function inferSpawnWorkersIntent(actions: unknown[]): 'plan' | 'mutate';
|
|
13
15
|
export declare function handleOrchestrationGroup(toolName: string, hctx: McpHandlerCtx, request: any, args: Record<string, unknown>, ctx?: any): Promise<McpResult | undefined>;
|
|
14
16
|
export {};
|
|
@@ -20,6 +20,25 @@ import { getSharedTelemetry } from '../../../../engines/telemetry-remote.js';
|
|
|
20
20
|
import { requireRuntime } from '../util/require-runtime.js';
|
|
21
21
|
import { ensureCrGraphBuilt } from '../../../../engines/code-review-graph-client.js';
|
|
22
22
|
import { recordFirstBootstrap } from '../../../../engines/telemetry.js';
|
|
23
|
+
export function extractSkillSelectorsFromPrompt(prompt) {
|
|
24
|
+
const selectors = new Set();
|
|
25
|
+
const add = (value) => {
|
|
26
|
+
const cleaned = value?.trim().replace(/^[$@]+/, '');
|
|
27
|
+
if (cleaned && /^[a-z0-9][a-z0-9._-]{1,120}$/i.test(cleaned)) {
|
|
28
|
+
selectors.add(cleaned);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
for (const match of prompt.matchAll(/\[[$@]?([a-z0-9][a-z0-9._-]{1,120})\]\([^)]*\/\.agents?\/skills\/[^)]*\/SKILL\.md\)/gi)) {
|
|
32
|
+
add(match[1]);
|
|
33
|
+
}
|
|
34
|
+
for (const match of prompt.matchAll(/\/\.agents?\/skills\/([^/\s)]+)\/SKILL\.md/gi)) {
|
|
35
|
+
add(match[1]);
|
|
36
|
+
}
|
|
37
|
+
return [...selectors];
|
|
38
|
+
}
|
|
39
|
+
export function inferSpawnWorkersIntent(actions) {
|
|
40
|
+
return actions.length > 0 ? 'mutate' : 'plan';
|
|
41
|
+
}
|
|
23
42
|
export async function handleOrchestrationGroup(toolName, hctx, request, args, ctx) {
|
|
24
43
|
const runtimeError = requireRuntime(hctx);
|
|
25
44
|
if (runtimeError)
|
|
@@ -315,9 +334,11 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
315
334
|
const workers = request.params.arguments?.workers == null
|
|
316
335
|
? undefined
|
|
317
336
|
: Number(request.params.arguments.workers);
|
|
318
|
-
const
|
|
337
|
+
const explicitSkills = Array.isArray(request.params.arguments?.skills)
|
|
319
338
|
? request.params.arguments.skills.map(String)
|
|
320
|
-
:
|
|
339
|
+
: [];
|
|
340
|
+
const promptSkills = extractSkillSelectorsFromPrompt(prompt);
|
|
341
|
+
const skills = [...new Set([...explicitSkills, ...promptSkills])];
|
|
321
342
|
const workflows = Array.isArray(request.params.arguments?.workflows)
|
|
322
343
|
? request.params.arguments.workflows.map(String)
|
|
323
344
|
: undefined;
|
|
@@ -374,7 +395,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
374
395
|
const execution = await hctx.nexusRef.orchestrate(prompt, applyExecutionPreset({
|
|
375
396
|
files,
|
|
376
397
|
workers,
|
|
377
|
-
skillNames: skills,
|
|
398
|
+
skillNames: skills.length > 0 ? skills : undefined,
|
|
378
399
|
workflowSelectors: workflows,
|
|
379
400
|
hookSelectors: hooks,
|
|
380
401
|
automationSelectors: automations,
|
|
@@ -769,6 +790,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
769
790
|
? String(request.params.arguments.executionPreset)
|
|
770
791
|
: undefined;
|
|
771
792
|
const preset = resolveExecutionPreset(executionPreset);
|
|
793
|
+
const inferredIntent = inferSpawnWorkersIntent(actions);
|
|
772
794
|
// Surface worker-plan intent on the SSE feed without persisting it as a memory.
|
|
773
795
|
try {
|
|
774
796
|
nexusEventBus.emit('memory.snapshot', {
|
|
@@ -796,11 +818,18 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
796
818
|
backendSelectors: { memoryBackend, compressionBackend, dslCompiler },
|
|
797
819
|
backendMode,
|
|
798
820
|
optimizationProfile,
|
|
821
|
+
intent: inferredIntent,
|
|
799
822
|
executionMode: 'manual-low-level',
|
|
800
823
|
manualOverrides: ['nexus_spawn_workers'],
|
|
801
824
|
}, executionPreset));
|
|
802
825
|
const verifiedWorkers = execution.workerResults.filter(result => result.verified).length;
|
|
803
826
|
const modifiedFiles = execution.workerResults.reduce((sum, result) => sum + result.modifiedFiles.length, 0);
|
|
827
|
+
const noDiffInspected = execution.state === 'inspected'
|
|
828
|
+
&& modifiedFiles === 0
|
|
829
|
+
&& !execution.workerResults.some((result) => result.diff?.trim());
|
|
830
|
+
const displayedDecision = noDiffInspected
|
|
831
|
+
? 'no-applicable-diff'
|
|
832
|
+
: execution.finalDecision?.action ?? 'none';
|
|
804
833
|
execution.activeSkills.forEach(skill => {
|
|
805
834
|
hctx.sessionDNA.recordSkill(skill.name);
|
|
806
835
|
if (skill.scope === 'global' || skill.rolloutStatus === 'promoted') {
|
|
@@ -826,7 +855,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
826
855
|
`Run: ${execution.runId.padEnd(28, ' ')} State: ${execution.state.padEnd(18, ' ')}`,
|
|
827
856
|
`Workers: ${execution.workerResults.length.toString().padEnd(5, ' ')} Verified: ${verifiedWorkers.toString().padEnd(10, ' ')} Files: ${String(modifiedFiles).padEnd(12, ' ')}`,
|
|
828
857
|
`${`Preset: ${preset?.name ?? 'manual'}`.padEnd(61, ' ')}`,
|
|
829
|
-
`Decision: ${
|
|
858
|
+
`Decision: ${displayedDecision.padEnd(52, ' ')}`
|
|
830
859
|
], execution.state === 'merged' || execution.state === 'inspected' ? '32' : execution.state === 'rolled_back' ? '33' : '31');
|
|
831
860
|
return {
|
|
832
861
|
content: [{
|
|
@@ -841,7 +870,7 @@ export async function handleOrchestrationGroup(toolName, hctx, request, args, ct
|
|
|
841
870
|
preset ? `Preset: ${preset.name} (${preset.id})` : null,
|
|
842
871
|
`Verified Workers: ${verifiedWorkers}`,
|
|
843
872
|
`Modified Files: ${modifiedFiles}`,
|
|
844
|
-
`Decision: ${
|
|
873
|
+
`Decision: ${displayedDecision}`,
|
|
845
874
|
`Recommended Strategy: ${execution.finalDecision?.recommendedStrategy ?? 'n/a'}`,
|
|
846
875
|
`Planner: ${execution.plannerResult?.summary ?? 'n/a'}`,
|
|
847
876
|
`Crew: ${execution.plannerResult?.selectedCrew?.name ?? 'n/a'}`,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* federation_status, run_status.
|
|
5
5
|
* Extracted from mcp.ts (Phase 3 split).
|
|
6
6
|
*/
|
|
7
|
-
import { formatBullets, formatJsonDetails, buildAutoMemorySummary, } from '../helpers.js';
|
|
7
|
+
import { formatBullets, formatJsonDetails, truncateText, buildAutoMemorySummary, } from '../helpers.js';
|
|
8
8
|
import { nexusEventBus } from '../../../../engines/event-bus.js';
|
|
9
9
|
import { getSharedLicenseManager, snapshotPCU, formatPCUStatus } from '../../../../licensing/index.js';
|
|
10
10
|
import { TokenAnalyticsEngine } from '../../../../engines/token-analytics.js';
|
|
@@ -13,6 +13,7 @@ import { requireRuntime } from '../util/require-runtime.js';
|
|
|
13
13
|
import { getAsyncGate } from '../async-gate.js';
|
|
14
14
|
import { getRun as getOrchRun } from '../../../../engines/orchestrator/store.js';
|
|
15
15
|
import { nxlResult } from '../../../../nxl/index.js';
|
|
16
|
+
import { summarizeUserWorkflowTrace } from '../../../../engines/user-workflow-trace.js';
|
|
16
17
|
export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
|
|
17
18
|
const runtimeError = requireRuntime(hctx);
|
|
18
19
|
if (runtimeError)
|
|
@@ -226,6 +227,40 @@ export async function handleRuntimeGroup(toolName, hctx, request, args, ctx) {
|
|
|
226
227
|
}],
|
|
227
228
|
};
|
|
228
229
|
}
|
|
230
|
+
case 'nexus_user_workflow_trace': {
|
|
231
|
+
const runId = String(request.params.arguments?.runId ?? '');
|
|
232
|
+
const fmt = String(request.params.arguments?.format ?? 'text');
|
|
233
|
+
const trace = await hctx.getRuntime().getUserWorkflowTrace?.(runId);
|
|
234
|
+
if (!trace) {
|
|
235
|
+
return { content: [{ type: 'text', text: `User workflow trace not found: ${runId}` }] };
|
|
236
|
+
}
|
|
237
|
+
if (fmt === 'json') {
|
|
238
|
+
return { content: [{ type: 'text', text: JSON.stringify(trace, null, 2) }] };
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
content: [{
|
|
242
|
+
type: 'text',
|
|
243
|
+
text: [
|
|
244
|
+
summarizeUserWorkflowTrace(trace),
|
|
245
|
+
'',
|
|
246
|
+
formatBullets([
|
|
247
|
+
`Run: ${trace.runId}`,
|
|
248
|
+
`Goal: ${truncateText(trace.goal, 120)}`,
|
|
249
|
+
`Actions: ${trace.userJourney.actions.length ? trace.userJourney.actions.slice(0, 5).join(' | ') : 'none recorded'}`,
|
|
250
|
+
`Files: ${trace.userJourney.selectedFiles.length ? trace.userJourney.selectedFiles.slice(0, 5).join(', ') : 'none selected'}`,
|
|
251
|
+
`Agents: ${trace.userJourney.selectedSpecialists.length ? trace.userJourney.selectedSpecialists.slice(0, 5).join(', ') : 'none selected'}`,
|
|
252
|
+
`Signals: ${trace.signals.length ? trace.signals.map(signal => `${signal.severity}:${signal.code}`).slice(0, 8).join(', ') : 'none'}`,
|
|
253
|
+
]),
|
|
254
|
+
trace.signals.length
|
|
255
|
+
? `Failure signals\n${formatBullets(trace.signals.map(signal => `${signal.code}: ${signal.summary}`))}`
|
|
256
|
+
: '',
|
|
257
|
+
trace.nextActions.length
|
|
258
|
+
? `Next actions\n${formatBullets(trace.nextActions)}`
|
|
259
|
+
: '',
|
|
260
|
+
].filter(Boolean).join('\n\n'),
|
|
261
|
+
}],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
229
264
|
case 'nexus_run_status': {
|
|
230
265
|
const runId = String(request.params.arguments?.runId ?? '');
|
|
231
266
|
const fmt = String(request.params.arguments?.format ?? 'text');
|
|
@@ -190,7 +190,7 @@ export function isReadOnlyMcpTool(toolName) {
|
|
|
190
190
|
'nexus_list_', 'nexus_describe_', 'nexus_runtime_health',
|
|
191
191
|
'nexus_doctor', 'nexus_token_report', 'nexus_recall_memory',
|
|
192
192
|
'nexus_temporal_query', 'nexus_memory_stats', 'nexus_federation_status',
|
|
193
|
-
'nexus_license_usage', 'nexus_run_status', 'nexus_selection_ledger',
|
|
193
|
+
'nexus_license_usage', 'nexus_run_status', 'nexus_user_workflow_trace', 'nexus_selection_ledger',
|
|
194
194
|
'nexus_operative_journal',
|
|
195
195
|
];
|
|
196
196
|
return skipPrefixes.some(p => toolName.startsWith(p));
|
|
@@ -136,6 +136,15 @@ export const handleRuntimeRoutes = async (ctx, req, res, url) => {
|
|
|
136
136
|
ctx.respondJson(res, entry ?? { error: 'run-token-telemetry-not-found', runId }, entry ? 200 : 404);
|
|
137
137
|
return true;
|
|
138
138
|
}
|
|
139
|
+
const userWorkflowMatch = req.method === 'GET'
|
|
140
|
+
? /^\/api\/runs\/(.+)\/user-workflow$/.exec(url.pathname)
|
|
141
|
+
: null;
|
|
142
|
+
if (userWorkflowMatch) {
|
|
143
|
+
const runId = decodeURIComponent(userWorkflowMatch[1]);
|
|
144
|
+
const trace = await ctx.getRuntime()?.getUserWorkflowTrace?.(runId);
|
|
145
|
+
ctx.respondJson(res, trace ?? { error: 'user-workflow-trace-not-found', runId }, trace ? 200 : 404);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
139
148
|
if (req.method === 'GET' && url.pathname.startsWith('/api/runs/')) {
|
|
140
149
|
const runId = decodeURIComponent(url.pathname.replace('/api/runs/', ''));
|
|
141
150
|
const run = await ctx.getRuntime()?.getRun?.(runId);
|
|
@@ -23,11 +23,13 @@ export function serializeSpecialistsCatalog(specialists) {
|
|
|
23
23
|
export async function buildAssetsSurface(ctx, url) {
|
|
24
24
|
const state = collectDashboardBaseState(ctx, url);
|
|
25
25
|
const runtimeId = state.runtime?.getRuntimeId?.() ?? 'default';
|
|
26
|
+
const patterns = ctx.getOrchestrator?.()?.listPatterns?.() ?? [];
|
|
26
27
|
return {
|
|
27
28
|
repoIdentity: state.snapshot?.repoIdentity ?? ctx.getSelectedRepoIdentity(url),
|
|
28
29
|
usage: state.usage,
|
|
29
30
|
selectionAudit: serializeSelectionAudit(state.usage),
|
|
30
31
|
featureRegistry: await ctx.getCachedValue('feature-registry', 30_000, () => Promise.resolve(buildFeatureRegistry(ctx.repoRoot))),
|
|
32
|
+
patternPacks: patterns.filter((pattern) => Array.isArray(pattern.tags) && pattern.tags.includes('pattern-pack')),
|
|
31
33
|
skills: state.runtime?.listSkills?.() ?? [],
|
|
32
34
|
specialists: await ctx.getCachedValue(`catalog:specialists:${runtimeId}`, 15_000, () => Promise.resolve(serializeSpecialistsCatalog(state.runtime?.listSpecialists?.() ?? []))),
|
|
33
35
|
crews: state.runtime?.listCrews?.() ?? [],
|
|
@@ -2299,7 +2299,7 @@ export class OrchestratorEngine {
|
|
|
2299
2299
|
elapsedMs: Date.now() - funnelStart,
|
|
2300
2300
|
});
|
|
2301
2301
|
// ── Funnel Stage 2: vote ─────────────────────────────────────────────────
|
|
2302
|
-
const skillSelection = this.resolveCatalogVotes('skill', task, intent, skillItems, {
|
|
2302
|
+
const skillSelection = this.resolveCatalogVotes('skill', task, intent, options.skillNames?.length ? allSkillItems : skillItems, {
|
|
2303
2303
|
explicit: options.skillNames,
|
|
2304
2304
|
planner: planner.selectedSkills,
|
|
2305
2305
|
knowledge: knowledgeFabric.recommendations.skills,
|
|
@@ -2308,7 +2308,7 @@ export class OrchestratorEngine {
|
|
|
2308
2308
|
limit: 5,
|
|
2309
2309
|
selector: 'name',
|
|
2310
2310
|
});
|
|
2311
|
-
const workflowSelection = this.resolveCatalogVotes('workflow', task, intent, workflowItems, {
|
|
2311
|
+
const workflowSelection = this.resolveCatalogVotes('workflow', task, intent, options.workflowSelectors?.length ? allWorkflowItems : workflowItems, {
|
|
2312
2312
|
explicit: options.workflowSelectors,
|
|
2313
2313
|
planner: planner.selectedWorkflows,
|
|
2314
2314
|
knowledge: knowledgeFabric.recommendations.workflows,
|
|
@@ -2333,7 +2333,7 @@ export class OrchestratorEngine {
|
|
|
2333
2333
|
authorityTier: this.getAuthorityTier('workflow'),
|
|
2334
2334
|
})),
|
|
2335
2335
|
];
|
|
2336
|
-
const hookSelection = this.resolveCatalogVotes('hook', task, intent, hookItems, {
|
|
2336
|
+
const hookSelection = this.resolveCatalogVotes('hook', task, intent, options.hookSelectors?.length ? allHookItems : hookItems, {
|
|
2337
2337
|
explicit: options.hookSelectors,
|
|
2338
2338
|
planner: [],
|
|
2339
2339
|
knowledge: knowledgeFabric.recommendations.hooks,
|
|
@@ -2341,7 +2341,7 @@ export class OrchestratorEngine {
|
|
|
2341
2341
|
limit: intent.riskClass === 'high' ? 3 : 2,
|
|
2342
2342
|
selector: 'name',
|
|
2343
2343
|
});
|
|
2344
|
-
const automationSelection = this.resolveCatalogVotes('automation', task, intent, automationItems, {
|
|
2344
|
+
const automationSelection = this.resolveCatalogVotes('automation', task, intent, options.automationSelectors?.length ? allAutomationItems : automationItems, {
|
|
2345
2345
|
explicit: options.automationSelectors,
|
|
2346
2346
|
planner: [],
|
|
2347
2347
|
knowledge: knowledgeFabric.recommendations.automations,
|
|
@@ -2349,7 +2349,7 @@ export class OrchestratorEngine {
|
|
|
2349
2349
|
limit: intent.taskType === 'release' || this.sessionState.repeatedFailures > 0 ? 3 : 2,
|
|
2350
2350
|
selector: 'name',
|
|
2351
2351
|
});
|
|
2352
|
-
const specialistSelection = this.resolveCatalogVotes('specialist', task, intent, specialistItems, {
|
|
2352
|
+
const specialistSelection = this.resolveCatalogVotes('specialist', task, intent, options.specialistSelectors?.length ? allSpecialistItems : specialistItems, {
|
|
2353
2353
|
explicit: options.specialistSelectors,
|
|
2354
2354
|
planner: planner.selectedSpecialists.map((specialist) => specialist.specialistId),
|
|
2355
2355
|
knowledge: knowledgeFabric.recommendations.specialists,
|
|
@@ -2357,7 +2357,7 @@ export class OrchestratorEngine {
|
|
|
2357
2357
|
limit: 4,
|
|
2358
2358
|
selector: 'id',
|
|
2359
2359
|
});
|
|
2360
|
-
const crewSelection = this.resolveCatalogVotes('crew', task, intent, crewItems, {
|
|
2360
|
+
const crewSelection = this.resolveCatalogVotes('crew', task, intent, options.crewSelectors?.length ? allCrewItems : crewItems, {
|
|
2361
2361
|
explicit: options.crewSelectors,
|
|
2362
2362
|
planner: planner.selectedCrew ? [planner.selectedCrew.crewId] : [],
|
|
2363
2363
|
knowledge: knowledgeFabric.recommendations.crews,
|
|
@@ -2509,7 +2509,19 @@ export class OrchestratorEngine {
|
|
|
2509
2509
|
resolveCatalogVotes(kind, task, intent, items, input) {
|
|
2510
2510
|
const explicit = dedupeStrings(input.explicit ?? []);
|
|
2511
2511
|
if (explicit.length > 0) {
|
|
2512
|
-
const
|
|
2512
|
+
const resolved = explicit.map((value) => {
|
|
2513
|
+
const item = items.find((entry) => entry.id === value || entry.name === value);
|
|
2514
|
+
return {
|
|
2515
|
+
value,
|
|
2516
|
+
item,
|
|
2517
|
+
selectedValue: input.selector === 'id'
|
|
2518
|
+
? (item?.id ?? value)
|
|
2519
|
+
: (item?.name ?? value),
|
|
2520
|
+
};
|
|
2521
|
+
});
|
|
2522
|
+
const selectedEntries = resolved
|
|
2523
|
+
.filter((entry) => entry.item)
|
|
2524
|
+
.map((entry) => this.toArtifactAuditEntry(kind, entry.selectedValue, items, {
|
|
2513
2525
|
score: 1,
|
|
2514
2526
|
source: 'explicit',
|
|
2515
2527
|
confidence: 'high',
|
|
@@ -2517,7 +2529,23 @@ export class OrchestratorEngine {
|
|
|
2517
2529
|
selector: input.selector,
|
|
2518
2530
|
selectionPolicy: 'explicit-only',
|
|
2519
2531
|
}));
|
|
2520
|
-
|
|
2532
|
+
const rejectedEntries = resolved
|
|
2533
|
+
.filter((entry) => !entry.item)
|
|
2534
|
+
.map((entry) => this.toArtifactAuditEntry(kind, entry.value, items, {
|
|
2535
|
+
score: 1,
|
|
2536
|
+
source: 'explicit',
|
|
2537
|
+
confidence: 'high',
|
|
2538
|
+
reason: 'User provided a hard constraint, but the selector was not found in the runtime catalog.',
|
|
2539
|
+
selector: input.selector,
|
|
2540
|
+
selectionPolicy: 'explicit-only',
|
|
2541
|
+
blockedReason: 'Explicit selector is not present in the runtime catalog.',
|
|
2542
|
+
authorityTier: this.getAuthorityTier(kind),
|
|
2543
|
+
}, false));
|
|
2544
|
+
return {
|
|
2545
|
+
selectedValues: selectedEntries.map((entry) => input.selector === 'id' ? entry.id : entry.name),
|
|
2546
|
+
selectedEntries,
|
|
2547
|
+
rejectedEntries,
|
|
2548
|
+
};
|
|
2521
2549
|
}
|
|
2522
2550
|
const votes = new Map();
|
|
2523
2551
|
const applyVote = (value, source, score, confidence, reason) => {
|
|
@@ -16,6 +16,18 @@ export interface PatternCard {
|
|
|
16
16
|
successCount: number;
|
|
17
17
|
failureCount: number;
|
|
18
18
|
lastUsedAt?: number;
|
|
19
|
+
patternPack?: {
|
|
20
|
+
kind: 'flow-kit';
|
|
21
|
+
flows: string[];
|
|
22
|
+
starterFiles: Array<{
|
|
23
|
+
path: string;
|
|
24
|
+
purpose: string;
|
|
25
|
+
}>;
|
|
26
|
+
memorySeeds: string[];
|
|
27
|
+
lifecycle: string[];
|
|
28
|
+
acceptanceChecks: string[];
|
|
29
|
+
antiPatterns: string[];
|
|
30
|
+
};
|
|
19
31
|
}
|
|
20
32
|
export interface PatternSearchResult extends PatternCard {
|
|
21
33
|
score: number;
|
|
@@ -93,6 +93,71 @@ const BUILTIN_PATTERNS = [
|
|
|
93
93
|
successCount: 4,
|
|
94
94
|
failureCount: 0,
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
patternId: 'pattern_pack_auth_signup_flow',
|
|
98
|
+
name: 'Auth and Signup Flow Pack',
|
|
99
|
+
category: 'workflow',
|
|
100
|
+
summary: 'Ground common auth, signup, onboarding, and session flows in reusable security-aware defaults without generating stale boilerplate.',
|
|
101
|
+
instructions: 'Use this pack when a task mentions auth, login, signup, registration, onboarding, sessions, passwords, magic links, OAuth, or account creation. Start from the lifecycle and acceptance checks, inspect the target framework, then generate only framework-appropriate code with tests and security review. Never paste secrets, never invent provider config, and never bypass existing auth libraries.',
|
|
102
|
+
tags: ['pattern-pack', 'auth', 'signup', 'registration', 'login', 'onboarding', 'security', 'session'],
|
|
103
|
+
stages: ['bootstrap', 'planning', 'mutation', 'verification'],
|
|
104
|
+
suggestedSkills: ['broken-authentication', 'backend-dev-guidelines', 'frontend-dev-guidelines', 'security-auditor'],
|
|
105
|
+
suggestedWorkflows: ['backend-execution-loop', 'frontend-execution-loop'],
|
|
106
|
+
suggestedHooks: ['before-verify-approval'],
|
|
107
|
+
suggestedAutomations: [],
|
|
108
|
+
suggestedCrews: ['crew_implementation'],
|
|
109
|
+
suggestedSpecialists: [
|
|
110
|
+
'specialist_engineering-engineering-backend-architect',
|
|
111
|
+
'specialist_engineering-engineering-frontend-developer',
|
|
112
|
+
'specialist_engineering-engineering-security-engineer',
|
|
113
|
+
],
|
|
114
|
+
confidence: 0.81,
|
|
115
|
+
successCount: 0,
|
|
116
|
+
failureCount: 0,
|
|
117
|
+
patternPack: {
|
|
118
|
+
kind: 'flow-kit',
|
|
119
|
+
flows: [
|
|
120
|
+
'email/password signup',
|
|
121
|
+
'login/logout',
|
|
122
|
+
'session refresh',
|
|
123
|
+
'password reset',
|
|
124
|
+
'email verification',
|
|
125
|
+
'OAuth handoff',
|
|
126
|
+
'first-run onboarding',
|
|
127
|
+
],
|
|
128
|
+
starterFiles: [
|
|
129
|
+
{ path: 'auth/provider', purpose: 'Wrap the project auth provider or framework-native session adapter.' },
|
|
130
|
+
{ path: 'auth/routes', purpose: 'Handle signup, login, logout, callback, and reset endpoints.' },
|
|
131
|
+
{ path: 'auth/ui', purpose: 'Expose signup, login, reset, and verification states.' },
|
|
132
|
+
{ path: 'auth/tests', purpose: 'Cover happy path, invalid credentials, session expiry, and access-control regressions.' },
|
|
133
|
+
],
|
|
134
|
+
memorySeeds: [
|
|
135
|
+
'Auth work must reuse project-native libraries and existing session boundaries before adding new dependencies.',
|
|
136
|
+
'Signup flows need explicit abuse, rate-limit, email verification, and password-reset checks.',
|
|
137
|
+
'Never store provider secrets, reset tokens, or session material in Nexus memory.',
|
|
138
|
+
],
|
|
139
|
+
lifecycle: [
|
|
140
|
+
'Detect framework and existing auth surface.',
|
|
141
|
+
'Select provider pattern and security constraints.',
|
|
142
|
+
'Patch smallest route, UI, and persistence surface.',
|
|
143
|
+
'Verify with unit, integration, and access-control checks.',
|
|
144
|
+
'Store only sanitized implementation decisions as memory.',
|
|
145
|
+
],
|
|
146
|
+
acceptanceChecks: [
|
|
147
|
+
'Unauthenticated users cannot access protected routes.',
|
|
148
|
+
'Signup and login errors are explicit without leaking account existence.',
|
|
149
|
+
'Sessions expire or refresh according to project policy.',
|
|
150
|
+
'Password reset and verification tokens are one-time and scoped.',
|
|
151
|
+
'Tests cover success, failure, and privilege-boundary cases.',
|
|
152
|
+
],
|
|
153
|
+
antiPatterns: [
|
|
154
|
+
'Rolling custom crypto.',
|
|
155
|
+
'Saving secrets or tokens into memory.',
|
|
156
|
+
'Adding auth dependencies before checking existing stack.',
|
|
157
|
+
'Generating UI-only signup without backend/session proof.',
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
96
161
|
];
|
|
97
162
|
export class PatternRegistry {
|
|
98
163
|
statePath;
|
|
@@ -41,25 +41,27 @@ export function getCrewTemplate(crewId) {
|
|
|
41
41
|
export function planSpecialists(input) {
|
|
42
42
|
const requestedCrews = input.requestedCrews ?? [];
|
|
43
43
|
const requestedSpecialists = input.requestedSpecialists ?? [];
|
|
44
|
+
const requestedSkills = input.requestedSkills ?? [];
|
|
45
|
+
const requestedWorkflows = input.requestedWorkflows ?? [];
|
|
44
46
|
const goal = input.goal;
|
|
45
47
|
const matchedDomains = detectDomains(goal, [
|
|
46
48
|
...(input.files ?? []),
|
|
47
49
|
...requestedCrews,
|
|
48
50
|
...requestedSpecialists,
|
|
49
|
-
...
|
|
50
|
-
...
|
|
51
|
+
...requestedSkills,
|
|
52
|
+
...requestedWorkflows,
|
|
51
53
|
]);
|
|
52
54
|
const taskSignals = analyzeTaskContext(goal, matchedDomains, input.files ?? []);
|
|
53
55
|
const selectedCrew = selectCrew(goal, matchedDomains, requestedCrews);
|
|
54
56
|
const selectedSpecialists = rankSpecialists(goal, matchedDomains, selectedCrew, requestedSpecialists, input.optimizationProfile ?? 'standard');
|
|
55
|
-
const selectedSkills = rankAssetSelectors(goal, matchedDomains, input.files ?? [], dedupeStrings([
|
|
56
|
-
...
|
|
57
|
+
const selectedSkills = pinRequestedSelectors(requestedSkills, rankAssetSelectors(goal, matchedDomains, input.files ?? [], dedupeStrings([
|
|
58
|
+
...requestedSkills,
|
|
57
59
|
...selectedSpecialists.flatMap((entry) => getSpecialist(entry.specialistId)?.recommendedSkills ?? []),
|
|
58
|
-
]), input.optimizationProfile ?? 'standard');
|
|
59
|
-
const selectedWorkflows = rankAssetSelectors(goal, matchedDomains, input.files ?? [], dedupeStrings([
|
|
60
|
-
...
|
|
60
|
+
]), input.optimizationProfile ?? 'standard'));
|
|
61
|
+
const selectedWorkflows = pinRequestedSelectors(requestedWorkflows, rankAssetSelectors(goal, matchedDomains, input.files ?? [], dedupeStrings([
|
|
62
|
+
...requestedWorkflows,
|
|
61
63
|
...selectedSpecialists.flatMap((entry) => getSpecialist(entry.specialistId)?.recommendedWorkflows ?? []),
|
|
62
|
-
]), input.optimizationProfile ?? 'standard');
|
|
64
|
+
]), input.optimizationProfile ?? 'standard'));
|
|
63
65
|
const toolPolicy = selectToolPolicy(selectedSpecialists);
|
|
64
66
|
const fallbackPlan = {
|
|
65
67
|
summary: 'Fallback to current runtime domain-pack execution if specialist confidence or crew coverage is weak.',
|
|
@@ -533,6 +535,12 @@ function rankAssetSelectors(goal, matchedDomains, files, values, optimizationPro
|
|
|
533
535
|
.slice(0, optimizationProfile === 'max' ? 6 : 4)
|
|
534
536
|
.map((entry) => entry.value);
|
|
535
537
|
}
|
|
538
|
+
function pinRequestedSelectors(requested, ranked) {
|
|
539
|
+
return dedupeStrings([
|
|
540
|
+
...requested,
|
|
541
|
+
...ranked,
|
|
542
|
+
]);
|
|
543
|
+
}
|
|
536
544
|
function dedupeStrings(values) {
|
|
537
545
|
return [...new Set(values.filter(Boolean))];
|
|
538
546
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const USER_WORKFLOW_TRACE_FILE = "user-workflow-trace.json";
|
|
2
|
+
export type UserWorkflowSpanKind = 'user_goal' | 'planning' | 'context' | 'agent_action' | 'verification' | 'decision' | 'outcome';
|
|
3
|
+
export type UserWorkflowStatus = 'ok' | 'warn' | 'failed' | 'skipped';
|
|
4
|
+
export interface UserWorkflowSpan {
|
|
5
|
+
id: string;
|
|
6
|
+
parentId?: string;
|
|
7
|
+
kind: UserWorkflowSpanKind;
|
|
8
|
+
label: string;
|
|
9
|
+
status: UserWorkflowStatus;
|
|
10
|
+
summary: string;
|
|
11
|
+
attributes?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export interface UserWorkflowSignal {
|
|
14
|
+
code: string;
|
|
15
|
+
severity: 'info' | 'warn' | 'high';
|
|
16
|
+
summary: string;
|
|
17
|
+
evidence?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface UserWorkflowTrace {
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
scope: 'user-agent-workflow';
|
|
22
|
+
traceId: string;
|
|
23
|
+
runId: string;
|
|
24
|
+
generatedAt: number;
|
|
25
|
+
goal: string;
|
|
26
|
+
state: string;
|
|
27
|
+
result: string;
|
|
28
|
+
artifactsPath?: string;
|
|
29
|
+
userJourney: {
|
|
30
|
+
goal: string;
|
|
31
|
+
selectedFiles: string[];
|
|
32
|
+
selectedSkills: string[];
|
|
33
|
+
selectedWorkflows: string[];
|
|
34
|
+
selectedSpecialists: string[];
|
|
35
|
+
actions: string[];
|
|
36
|
+
verifyCommands: string[];
|
|
37
|
+
};
|
|
38
|
+
spans: UserWorkflowSpan[];
|
|
39
|
+
signals: UserWorkflowSignal[];
|
|
40
|
+
nextActions: string[];
|
|
41
|
+
}
|
|
42
|
+
type RunLike = Record<string, any>;
|
|
43
|
+
export declare function buildUserWorkflowTrace(run: RunLike): UserWorkflowTrace;
|
|
44
|
+
export declare function resolveUserWorkflowTracePath(artifactsPath: string): string;
|
|
45
|
+
export declare function readUserWorkflowTrace(artifactsPath: string): Promise<UserWorkflowTrace | undefined>;
|
|
46
|
+
export declare function summarizeUserWorkflowTrace(trace: UserWorkflowTrace): string;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export const USER_WORKFLOW_TRACE_FILE = 'user-workflow-trace.json';
|
|
4
|
+
function short(value, limit = 220) {
|
|
5
|
+
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
6
|
+
return text.length > limit ? `${text.slice(0, Math.max(0, limit - 1))}...` : text;
|
|
7
|
+
}
|
|
8
|
+
function uniqueStrings(values) {
|
|
9
|
+
return [...new Set(values.map((value) => String(value ?? '').trim()).filter(Boolean))];
|
|
10
|
+
}
|
|
11
|
+
function workerStatus(worker) {
|
|
12
|
+
if (worker.budgetExceeded)
|
|
13
|
+
return 'warn';
|
|
14
|
+
if (worker.verified)
|
|
15
|
+
return 'ok';
|
|
16
|
+
if (worker.outcome === 'failed')
|
|
17
|
+
return 'failed';
|
|
18
|
+
if (worker.outcome === 'partial')
|
|
19
|
+
return 'warn';
|
|
20
|
+
if (!String(worker.diff ?? '').trim() && !(worker.commandRecords ?? []).length)
|
|
21
|
+
return 'skipped';
|
|
22
|
+
return 'warn';
|
|
23
|
+
}
|
|
24
|
+
function verificationStatus(entry) {
|
|
25
|
+
if (entry.status === 'passed' || entry.passed === true)
|
|
26
|
+
return 'ok';
|
|
27
|
+
if (entry.status === 'skipped')
|
|
28
|
+
return 'skipped';
|
|
29
|
+
return 'failed';
|
|
30
|
+
}
|
|
31
|
+
function extractActions(run) {
|
|
32
|
+
const manifestActions = (run.workerManifests ?? [])
|
|
33
|
+
.flatMap((manifest) => manifest.actions ?? [])
|
|
34
|
+
.map((action) => action.command ?? action.type ?? action.name ?? action.tool ?? JSON.stringify(action));
|
|
35
|
+
const commandRecords = (run.workerResults ?? [])
|
|
36
|
+
.flatMap((worker) => worker.commandRecords ?? [])
|
|
37
|
+
.map((record) => record.command);
|
|
38
|
+
return uniqueStrings([...manifestActions, ...commandRecords]).slice(0, 30);
|
|
39
|
+
}
|
|
40
|
+
function extractVerifyCommands(run) {
|
|
41
|
+
const manifestCommands = (run.workerManifests ?? []).flatMap((manifest) => manifest.verifyCommands ?? []);
|
|
42
|
+
const verificationCommands = (run.verificationResults ?? [])
|
|
43
|
+
.flatMap((entry) => entry.commands ?? [])
|
|
44
|
+
.map((record) => record.command);
|
|
45
|
+
return uniqueStrings([...manifestCommands, ...verificationCommands]).slice(0, 30);
|
|
46
|
+
}
|
|
47
|
+
function buildSignals(run) {
|
|
48
|
+
const signals = [];
|
|
49
|
+
const workers = run.workerResults ?? [];
|
|
50
|
+
const verifications = run.verificationResults ?? [];
|
|
51
|
+
const modifiedFiles = workers.reduce((sum, worker) => sum + (worker.modifiedFiles?.length ?? 0), 0);
|
|
52
|
+
const hasDiff = workers.some((worker) => String(worker.diff ?? '').trim().length > 0);
|
|
53
|
+
const commandOnly = workers.some((worker) => (worker.commandRecords ?? []).length > 0);
|
|
54
|
+
const failedWorkers = workers.filter((worker) => workerStatus(worker) === 'failed');
|
|
55
|
+
const skippedVerifications = verifications.filter((entry) => entry.status === 'skipped');
|
|
56
|
+
const failedVerifications = verifications.filter((entry) => verificationStatus(entry) === 'failed');
|
|
57
|
+
if (String(run.state) === 'failed') {
|
|
58
|
+
signals.push({
|
|
59
|
+
code: 'workflow_failed',
|
|
60
|
+
severity: 'high',
|
|
61
|
+
summary: short(run.result || 'Agent workflow failed before a stable user outcome was produced.'),
|
|
62
|
+
evidence: { state: run.state },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (workers.length === 0) {
|
|
66
|
+
signals.push({
|
|
67
|
+
code: 'no_agent_workers',
|
|
68
|
+
severity: 'warn',
|
|
69
|
+
summary: 'No worker results were recorded for this user workflow.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (failedWorkers.length > 0) {
|
|
73
|
+
signals.push({
|
|
74
|
+
code: 'agent_worker_failed',
|
|
75
|
+
severity: 'high',
|
|
76
|
+
summary: `${failedWorkers.length} worker(s) failed or returned unusable output.`,
|
|
77
|
+
evidence: { workers: failedWorkers.map((worker) => worker.workerId).slice(0, 10) },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (failedVerifications.length > 0) {
|
|
81
|
+
signals.push({
|
|
82
|
+
code: 'verification_failed',
|
|
83
|
+
severity: 'high',
|
|
84
|
+
summary: `${failedVerifications.length} verification step(s) failed.`,
|
|
85
|
+
evidence: { workers: failedVerifications.map((entry) => entry.workerId).slice(0, 10) },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (!hasDiff && modifiedFiles === 0 && (skippedVerifications.length > 0 || commandOnly)) {
|
|
89
|
+
signals.push({
|
|
90
|
+
code: 'no_applicable_diff',
|
|
91
|
+
severity: String(run.state) === 'failed' ? 'warn' : 'info',
|
|
92
|
+
summary: 'Agents produced no repository patch; this is expected for read-only or command-only workflows.',
|
|
93
|
+
evidence: { skippedVerifications: skippedVerifications.length, commandOnly },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const tokenTelemetry = run.tokenTelemetry ?? {};
|
|
97
|
+
const grossTokens = Number(tokenTelemetry.grossInputTokens ?? 0);
|
|
98
|
+
const savedTokens = Number(tokenTelemetry.savedTokens ?? 0);
|
|
99
|
+
if (grossTokens > 80_000 && savedTokens < grossTokens * 0.2) {
|
|
100
|
+
signals.push({
|
|
101
|
+
code: 'token_pressure',
|
|
102
|
+
severity: 'warn',
|
|
103
|
+
summary: 'Workflow used a large context budget with low compression savings.',
|
|
104
|
+
evidence: { grossInputTokens: grossTokens, savedTokens },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return signals;
|
|
108
|
+
}
|
|
109
|
+
function buildNextActions(trace) {
|
|
110
|
+
if (trace.signals.length === 0) {
|
|
111
|
+
return ['Use this trace as the stable replay/eval baseline for similar user workflows.'];
|
|
112
|
+
}
|
|
113
|
+
const actions = new Set();
|
|
114
|
+
for (const signal of trace.signals) {
|
|
115
|
+
if (signal.code === 'workflow_failed' || signal.code === 'agent_worker_failed') {
|
|
116
|
+
actions.add('Inspect the failed worker span, then replay with narrower files, clearer success criteria, or a better specialist.');
|
|
117
|
+
}
|
|
118
|
+
if (signal.code === 'verification_failed') {
|
|
119
|
+
actions.add('Promote the failing verification commands into a regression eval before rerunning.');
|
|
120
|
+
}
|
|
121
|
+
if (signal.code === 'no_applicable_diff') {
|
|
122
|
+
actions.add('If a patch was expected, rerun with mutate intent and explicit target files; otherwise treat this as a read-only trace.');
|
|
123
|
+
}
|
|
124
|
+
if (signal.code === 'token_pressure') {
|
|
125
|
+
actions.add('Run token optimization first and replay with a smaller context packet.');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return [...actions].slice(0, 5);
|
|
129
|
+
}
|
|
130
|
+
export function buildUserWorkflowTrace(run) {
|
|
131
|
+
const planner = run.plannerResult ?? {};
|
|
132
|
+
const selectedFiles = uniqueStrings(planner.selectedFiles ?? []);
|
|
133
|
+
const selectedSkills = uniqueStrings([
|
|
134
|
+
...(planner.selectedSkills ?? []),
|
|
135
|
+
...(run.activeSkills ?? []).map((skill) => skill.name),
|
|
136
|
+
]);
|
|
137
|
+
const selectedWorkflows = uniqueStrings([
|
|
138
|
+
...(planner.selectedWorkflows ?? []),
|
|
139
|
+
...(run.activeWorkflows ?? []).map((workflow) => workflow.name),
|
|
140
|
+
]);
|
|
141
|
+
const selectedSpecialists = uniqueStrings([
|
|
142
|
+
...(planner.selectedSpecialists ?? []).map((specialist) => specialist.name ?? specialist.specialistId),
|
|
143
|
+
...(run.workerManifests ?? []).map((manifest) => manifest.specialistName ?? manifest.specialistId),
|
|
144
|
+
]);
|
|
145
|
+
const actions = extractActions(run);
|
|
146
|
+
const verifyCommands = extractVerifyCommands(run);
|
|
147
|
+
const spans = [
|
|
148
|
+
{
|
|
149
|
+
id: 'user-goal',
|
|
150
|
+
kind: 'user_goal',
|
|
151
|
+
label: 'User goal',
|
|
152
|
+
status: 'ok',
|
|
153
|
+
summary: short(run.goal, 320),
|
|
154
|
+
attributes: {
|
|
155
|
+
parentRunId: run.parentRunId ?? null,
|
|
156
|
+
sourceAutomationId: run.sourceAutomationId ?? null,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'planning',
|
|
161
|
+
parentId: 'user-goal',
|
|
162
|
+
kind: 'planning',
|
|
163
|
+
label: 'Agent plan',
|
|
164
|
+
status: selectedSpecialists.length > 0 || selectedSkills.length > 0 ? 'ok' : 'warn',
|
|
165
|
+
summary: short(planner.summary ?? 'Planner selected agents, skills, workflows, and files for the user workflow.'),
|
|
166
|
+
attributes: { selectedSkills, selectedWorkflows, selectedSpecialists },
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'context',
|
|
170
|
+
parentId: 'planning',
|
|
171
|
+
kind: 'context',
|
|
172
|
+
label: 'Context packet',
|
|
173
|
+
status: selectedFiles.length > 0 ? 'ok' : 'warn',
|
|
174
|
+
summary: `${selectedFiles.length} file(s), ${selectedSkills.length} skill(s), ${selectedWorkflows.length} workflow(s) routed into the agent workflow.`,
|
|
175
|
+
attributes: {
|
|
176
|
+
selectedFiles,
|
|
177
|
+
memoryChecks: run.memoryChecks?.length ?? 0,
|
|
178
|
+
tokenTelemetry: run.tokenTelemetry ?? null,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
...(run.workerResults ?? []).map((worker) => ({
|
|
182
|
+
id: `worker-${worker.workerId ?? 'unknown'}`,
|
|
183
|
+
parentId: 'planning',
|
|
184
|
+
kind: 'agent_action',
|
|
185
|
+
label: `${worker.role ?? 'worker'} ${worker.workerId ?? 'unknown'}`,
|
|
186
|
+
status: workerStatus(worker),
|
|
187
|
+
summary: short(worker.summary ?? worker.learned ?? worker.outcome ?? 'Agent worker completed.'),
|
|
188
|
+
attributes: {
|
|
189
|
+
workerId: worker.workerId,
|
|
190
|
+
role: worker.role,
|
|
191
|
+
outcome: worker.outcome,
|
|
192
|
+
verified: Boolean(worker.verified),
|
|
193
|
+
modifiedFiles: worker.modifiedFiles ?? [],
|
|
194
|
+
commandCount: worker.commandRecords?.length ?? 0,
|
|
195
|
+
tokensUsed: worker.tokensUsed ?? null,
|
|
196
|
+
},
|
|
197
|
+
})),
|
|
198
|
+
...(run.verificationResults ?? []).map((entry) => ({
|
|
199
|
+
id: `verification-${entry.verifierId ?? entry.workerId ?? 'unknown'}`,
|
|
200
|
+
parentId: `worker-${entry.workerId ?? 'unknown'}`,
|
|
201
|
+
kind: 'verification',
|
|
202
|
+
label: `Verification for ${entry.workerId ?? 'worker'}`,
|
|
203
|
+
status: verificationStatus(entry),
|
|
204
|
+
summary: short(entry.summary ?? 'Verification completed.'),
|
|
205
|
+
attributes: {
|
|
206
|
+
workerId: entry.workerId,
|
|
207
|
+
verifierId: entry.verifierId,
|
|
208
|
+
commandCount: entry.commands?.length ?? 0,
|
|
209
|
+
skippedReason: entry.skippedReason ?? null,
|
|
210
|
+
},
|
|
211
|
+
})),
|
|
212
|
+
{
|
|
213
|
+
id: 'decision',
|
|
214
|
+
parentId: 'planning',
|
|
215
|
+
kind: 'decision',
|
|
216
|
+
label: 'Merge decision',
|
|
217
|
+
status: run.finalDecision?.action === 'apply' ? 'ok' : run.finalDecision?.action ? 'warn' : 'skipped',
|
|
218
|
+
summary: short(run.finalDecision?.recommendedStrategy ?? run.finalDecision?.action ?? 'No merge decision recorded.'),
|
|
219
|
+
attributes: {
|
|
220
|
+
action: run.finalDecision?.action ?? null,
|
|
221
|
+
winner: run.finalDecision?.winner?.workerId ?? null,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'outcome',
|
|
226
|
+
parentId: 'decision',
|
|
227
|
+
kind: 'outcome',
|
|
228
|
+
label: 'User workflow outcome',
|
|
229
|
+
status: String(run.state) === 'failed' ? 'failed' : 'ok',
|
|
230
|
+
summary: short(run.result ?? ''),
|
|
231
|
+
attributes: {
|
|
232
|
+
state: run.state,
|
|
233
|
+
promotions: run.promotionDecisions?.length ?? 0,
|
|
234
|
+
continuationChildren: run.continuationChildren?.length ?? 0,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
const signals = buildSignals(run);
|
|
239
|
+
const trace = {
|
|
240
|
+
schemaVersion: 1,
|
|
241
|
+
scope: 'user-agent-workflow',
|
|
242
|
+
traceId: `uwt_${run.runId}`,
|
|
243
|
+
runId: String(run.runId ?? ''),
|
|
244
|
+
generatedAt: Date.now(),
|
|
245
|
+
goal: String(run.goal ?? ''),
|
|
246
|
+
state: String(run.state ?? ''),
|
|
247
|
+
result: String(run.result ?? ''),
|
|
248
|
+
artifactsPath: run.artifactsPath,
|
|
249
|
+
userJourney: {
|
|
250
|
+
goal: String(run.goal ?? ''),
|
|
251
|
+
selectedFiles,
|
|
252
|
+
selectedSkills,
|
|
253
|
+
selectedWorkflows,
|
|
254
|
+
selectedSpecialists,
|
|
255
|
+
actions,
|
|
256
|
+
verifyCommands,
|
|
257
|
+
},
|
|
258
|
+
spans,
|
|
259
|
+
signals,
|
|
260
|
+
nextActions: [],
|
|
261
|
+
};
|
|
262
|
+
trace.nextActions = buildNextActions(trace);
|
|
263
|
+
return trace;
|
|
264
|
+
}
|
|
265
|
+
export function resolveUserWorkflowTracePath(artifactsPath) {
|
|
266
|
+
return path.join(artifactsPath, USER_WORKFLOW_TRACE_FILE);
|
|
267
|
+
}
|
|
268
|
+
export async function readUserWorkflowTrace(artifactsPath) {
|
|
269
|
+
try {
|
|
270
|
+
const raw = await fs.readFile(resolveUserWorkflowTracePath(artifactsPath), 'utf8');
|
|
271
|
+
return JSON.parse(raw);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export function summarizeUserWorkflowTrace(trace) {
|
|
278
|
+
const high = trace.signals.filter((signal) => signal.severity === 'high').length;
|
|
279
|
+
const warns = trace.signals.filter((signal) => signal.severity === 'warn').length;
|
|
280
|
+
return `${trace.state.toUpperCase()} user workflow · ${trace.spans.length} span(s) · ${high} high signal(s), ${warns} warning(s)`;
|
|
281
|
+
}
|
|
@@ -14,6 +14,7 @@ import { type ContinuationProposal, type FallbackPlan, type OptimizationProfile,
|
|
|
14
14
|
import { type TaskPlannerState } from '../engines/task-planner.js';
|
|
15
15
|
import { type RuntimeArtifactSelectionAudit, type RuntimeArtifactOutcomeSnapshot, type RuntimeCatalogHealthSnapshot, type RuntimeClientInstructionStatus, type RuntimeMemoryHealthSnapshot, type RuntimeMemoryReconciliationSummary, type RuntimeMemoryScopeUsageSnapshot, type RuntimeRagUsageSummary, type RuntimeRegistrySnapshot, type RuntimeOrchestrationSnapshot, type RuntimePrimaryClientSnapshot, type RuntimeSourceAwareTokenBudgetSnapshot, type RuntimeTaskGraphSnapshot, type RuntimeTokenRunSnapshot, type RuntimeTokenSummarySnapshot, type RuntimeWorkerPlanSnapshot } from '../engines/runtime-registry.js';
|
|
16
16
|
import { type ExecutionLedger, type InstructionPacket, type OrchestrationExecutionMode } from '../engines/instruction-gateway.js';
|
|
17
|
+
import { type UserWorkflowTrace } from '../engines/user-workflow-trace.js';
|
|
17
18
|
import type { KnowledgeFabricBundle, KnowledgeFabricSnapshot } from '../engines/knowledge-fabric.js';
|
|
18
19
|
import type { BootstrapManifestStatus } from '../engines/client-bootstrap.js';
|
|
19
20
|
import { type WorktreeHealthSnapshot } from '../engines/worktree-health.js';
|
|
@@ -282,6 +283,7 @@ export interface ExecutionRun {
|
|
|
282
283
|
memoryScopeUsage?: RuntimeMemoryScopeUsageSnapshot;
|
|
283
284
|
memoryReconciliationSummary?: RuntimeMemoryReconciliationSummary;
|
|
284
285
|
autoGhostPass?: GhostReport;
|
|
286
|
+
userWorkflowTrace?: UserWorkflowTrace;
|
|
285
287
|
}
|
|
286
288
|
export interface SubAgentRuntimeOptions {
|
|
287
289
|
repoRoot?: string;
|
|
@@ -328,6 +330,8 @@ export declare class SubAgentRuntime {
|
|
|
328
330
|
runNXL(goal: string, rawScript?: string, useCase?: string): Promise<ExecutionRun>;
|
|
329
331
|
listRuns(limit?: number): ExecutionRun[];
|
|
330
332
|
getRun(runId: string): Promise<ExecutionRun | undefined>;
|
|
333
|
+
getUserWorkflowTrace(runId: string): Promise<UserWorkflowTrace | undefined>;
|
|
334
|
+
private persistUserWorkflowTrace;
|
|
331
335
|
getRuntimeId(): string;
|
|
332
336
|
getUsageSnapshot(): RuntimeRegistrySnapshot;
|
|
333
337
|
getInstructionPacket(): InstructionPacket | undefined;
|
package/dist/phantom/runtime.js
CHANGED
|
@@ -19,6 +19,7 @@ import { planTask } from '../engines/task-planner.js';
|
|
|
19
19
|
import { nexusEventBus } from '../engines/event-bus.js';
|
|
20
20
|
import { RuntimeRegistry, createEmptyUsageState, createEmptyTokenSummary, } from '../engines/runtime-registry.js';
|
|
21
21
|
import { InstructionGateway, createExecutionLedger, markExecutionLedgerStep, renderInstructionPacketMarkdown, } from '../engines/instruction-gateway.js';
|
|
22
|
+
import { buildUserWorkflowTrace, readUserWorkflowTrace, USER_WORKFLOW_TRACE_FILE, } from '../engines/user-workflow-trace.js';
|
|
22
23
|
import { resolveWorkspaceContext } from '../engines/workspace-resolver.js';
|
|
23
24
|
import { WorktreeDoctorError, } from '../engines/worktree-health.js';
|
|
24
25
|
// ─── Sub-module imports ────────────────────────────────────────────────────────
|
|
@@ -363,6 +364,7 @@ export class SubAgentRuntime {
|
|
|
363
364
|
});
|
|
364
365
|
run.executionLedger = task.executionLedger;
|
|
365
366
|
this.syncExecutionMetadata(run);
|
|
367
|
+
this.persistUserWorkflowTrace(run, recorder);
|
|
366
368
|
recorder.writeJson('run.json', run);
|
|
367
369
|
return run;
|
|
368
370
|
}
|
|
@@ -554,6 +556,7 @@ export class SubAgentRuntime {
|
|
|
554
556
|
run.state = 'failed';
|
|
555
557
|
run.result = 'Hooks blocked execution before mutation.';
|
|
556
558
|
this.attachLatestRun(runId, task.goal, run.state);
|
|
559
|
+
this.persistUserWorkflowTrace(run, recorder);
|
|
557
560
|
recorder.writeJson('run.json', run);
|
|
558
561
|
return run;
|
|
559
562
|
}
|
|
@@ -815,6 +818,7 @@ export class SubAgentRuntime {
|
|
|
815
818
|
recorder.writeJson('planner-state.json', run.plannerState);
|
|
816
819
|
recorder.writeJson('planner-result.json', run.plannerResult);
|
|
817
820
|
recorder.writeJson('manifests.json', run.workerManifests);
|
|
821
|
+
this.persistUserWorkflowTrace(run, recorder);
|
|
818
822
|
recorder.writeJson('run.json', run);
|
|
819
823
|
// Persist run-level token telemetry to the global ledger so nexus_token_report
|
|
820
824
|
// and dashboard analytics can aggregate lifetime savings across sessions.
|
|
@@ -883,6 +887,25 @@ export class SubAgentRuntime {
|
|
|
883
887
|
return undefined;
|
|
884
888
|
}
|
|
885
889
|
}
|
|
890
|
+
async getUserWorkflowTrace(runId) {
|
|
891
|
+
const run = await this.getRun(runId);
|
|
892
|
+
if (!run)
|
|
893
|
+
return undefined;
|
|
894
|
+
return (await readUserWorkflowTrace(run.artifactsPath)) ?? buildUserWorkflowTrace(run);
|
|
895
|
+
}
|
|
896
|
+
persistUserWorkflowTrace(run, recorder) {
|
|
897
|
+
try {
|
|
898
|
+
const trace = buildUserWorkflowTrace(run);
|
|
899
|
+
run.userWorkflowTrace = trace;
|
|
900
|
+
if (recorder) {
|
|
901
|
+
recorder.writeJson(USER_WORKFLOW_TRACE_FILE, trace);
|
|
902
|
+
}
|
|
903
|
+
return trace;
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
return undefined;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
886
909
|
getRuntimeId() {
|
|
887
910
|
return this.runtimeId;
|
|
888
911
|
}
|
|
@@ -2247,10 +2270,17 @@ export class SubAgentRuntime {
|
|
|
2247
2270
|
summary: `Operational workflow completed with command evidence only (${records.length} command binding(s) passed); no repository patch was required.`,
|
|
2248
2271
|
};
|
|
2249
2272
|
}
|
|
2273
|
+
if (task.intent === 'inspect' || task.intent === 'plan') {
|
|
2274
|
+
return {
|
|
2275
|
+
applied: false,
|
|
2276
|
+
rolledBack: false,
|
|
2277
|
+
summary: 'Read-only worker swarm completed without a repository patch; no applicable diff was expected.',
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2250
2280
|
return {
|
|
2251
2281
|
applied: false,
|
|
2252
2282
|
rolledBack: false,
|
|
2253
|
-
summary:
|
|
2283
|
+
summary: 'Worker swarm completed without a repository patch; no applicable diff was produced.',
|
|
2254
2284
|
};
|
|
2255
2285
|
}
|
|
2256
2286
|
if (!consensusPolicy.approveRunLevelChange(decision.winner ? [decision.winner.workerId] : ['merge-oracle'])) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-prime",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.9",
|
|
4
4
|
"description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|