smart-context-mcp 1.6.2 → 1.7.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 +1 -1
- package/scripts/report-metrics.js +2 -2
- package/src/analytics/product-quality.js +176 -1
- package/src/hooks/claude-hooks.js +1 -423
- package/src/hooks/cursor-hooks.js +1 -0
- package/src/orchestration/adapters/claude-adapter.js +426 -0
- package/src/orchestration/adapters/cursor-adapter.js +429 -0
- package/src/orchestration/base-orchestrator.js +242 -0
- package/src/orchestration/headless-wrapper.js +33 -192
- package/src/orchestration/policy/event-policy.js +297 -0
- package/src/task-runner.js +24 -241
|
@@ -1,123 +1,12 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const END_MAX_TOKENS = 350;
|
|
11
|
-
const SAFE_CONTINUITY_STATES = new Set(['aligned', 'resume']);
|
|
12
|
-
|
|
13
|
-
const normalizeWhitespace = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
14
|
-
|
|
15
|
-
const truncate = (value, maxLength = 160) => {
|
|
16
|
-
const normalized = normalizeWhitespace(value);
|
|
17
|
-
if (normalized.length <= maxLength) {
|
|
18
|
-
return normalized;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (maxLength <= 3) {
|
|
22
|
-
return '';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const extractNextStep = (value) => {
|
|
29
|
-
const normalized = normalizeWhitespace(value);
|
|
30
|
-
if (!normalized) {
|
|
31
|
-
return '';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const explicitMatch = normalized.match(/(?:next step|siguiente paso)\s*[:\-]\s*([^.;\n]{12,180})/i);
|
|
35
|
-
if (explicitMatch?.[1]) {
|
|
36
|
-
return truncate(explicitMatch[1], 150);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return '';
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const buildContextLines = (startResult) => {
|
|
43
|
-
const context = buildOperationalContextLines(startResult, {
|
|
44
|
-
sessionStart: false,
|
|
45
|
-
maxLineLength: 120,
|
|
46
|
-
maxLines: 8,
|
|
47
|
-
maxChars: 560,
|
|
48
|
-
});
|
|
49
|
-
return context ? context.split('\n') : [];
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const buildWrappedPrompt = ({ prompt, startResult }) => {
|
|
53
|
-
const lines = buildContextLines(startResult);
|
|
54
|
-
if (lines.length === 0) {
|
|
55
|
-
return prompt;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return [
|
|
59
|
-
'Use the persisted devctx project context below only if it is relevant to the user request.',
|
|
60
|
-
...lines.map((line) => `- ${line}`),
|
|
61
|
-
'',
|
|
62
|
-
'User request:',
|
|
63
|
-
prompt,
|
|
64
|
-
].join('\n');
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const buildFreshSessionUpdate = (prompt) => {
|
|
68
|
-
const preview = truncate(prompt, 140);
|
|
69
|
-
return {
|
|
70
|
-
goal: truncate(prompt, 120),
|
|
71
|
-
status: 'planning',
|
|
72
|
-
currentFocus: preview,
|
|
73
|
-
pinnedContext: [preview],
|
|
74
|
-
nextStep: 'Inspect the relevant code, validate task boundaries, and checkpoint the first concrete milestone.',
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const ensureIsolatedSession = async ({ prompt, sessionId, startResult }) => {
|
|
79
|
-
if (sessionId || !startResult?.sessionId) {
|
|
80
|
-
return {
|
|
81
|
-
startResult,
|
|
82
|
-
isolated: Boolean(startResult?.isolatedSession),
|
|
83
|
-
previousSessionId: startResult?.previousSessionId ?? null,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (startResult?.isolatedSession) {
|
|
88
|
-
return {
|
|
89
|
-
startResult,
|
|
90
|
-
isolated: true,
|
|
91
|
-
previousSessionId: startResult.previousSessionId ?? null,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (SAFE_CONTINUITY_STATES.has(startResult.continuity?.state ?? '')) {
|
|
96
|
-
return {
|
|
97
|
-
startResult,
|
|
98
|
-
isolated: false,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const created = await smartSummary({
|
|
103
|
-
action: 'update',
|
|
104
|
-
update: buildFreshSessionUpdate(prompt),
|
|
105
|
-
maxTokens: START_MAX_TOKENS,
|
|
106
|
-
});
|
|
107
|
-
const isolatedStart = await smartTurn({
|
|
108
|
-
phase: 'start',
|
|
109
|
-
sessionId: created.sessionId,
|
|
110
|
-
prompt,
|
|
111
|
-
ensureSession: false,
|
|
112
|
-
maxTokens: START_MAX_TOKENS,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
startResult: isolatedStart,
|
|
117
|
-
isolated: true,
|
|
118
|
-
previousSessionId: startResult.sessionId,
|
|
119
|
-
};
|
|
120
|
-
};
|
|
2
|
+
import {
|
|
3
|
+
buildWrappedPrompt,
|
|
4
|
+
computeContextOverhead,
|
|
5
|
+
finalizeManagedRun,
|
|
6
|
+
recordAgentWrapperMetric,
|
|
7
|
+
resolveManagedStart,
|
|
8
|
+
} from './base-orchestrator.js';
|
|
9
|
+
import { normalizeWhitespace } from './policy/event-policy.js';
|
|
121
10
|
|
|
122
11
|
const runChildProcess = ({ command, args, env, stdinText, streamOutput }) => new Promise((resolve, reject) => {
|
|
123
12
|
const child = spawn(command, args, {
|
|
@@ -154,34 +43,6 @@ const runChildProcess = ({ command, args, env, stdinText, streamOutput }) => new
|
|
|
154
43
|
}
|
|
155
44
|
});
|
|
156
45
|
|
|
157
|
-
const buildEndUpdate = ({ prompt, childResult }) => {
|
|
158
|
-
const combinedOutput = [childResult.stdout, childResult.stderr].filter(Boolean).join('\n');
|
|
159
|
-
const nextStep = extractNextStep(combinedOutput);
|
|
160
|
-
const update = {
|
|
161
|
-
currentFocus: truncate(prompt, 140),
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
if (nextStep) {
|
|
165
|
-
update.nextStep = nextStep;
|
|
166
|
-
} else if (childResult.exitCode === 0) {
|
|
167
|
-
update.nextStep = 'Review the latest headless agent output and checkpoint any concrete file changes before continuing.';
|
|
168
|
-
} else {
|
|
169
|
-
update.status = 'blocked';
|
|
170
|
-
update.whyBlocked = `Headless agent command exited with code ${childResult.exitCode}.`;
|
|
171
|
-
update.nextStep = 'Review the headless agent stderr/output and rerun the command once the issue is fixed.';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return update;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const inferEndEvent = ({ requestedEvent, childResult }) => {
|
|
178
|
-
if (requestedEvent) {
|
|
179
|
-
return requestedEvent;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return childResult.exitCode === 0 ? DEFAULT_EVENT : 'blocker';
|
|
183
|
-
};
|
|
184
|
-
|
|
185
46
|
export const runHeadlessWrapper = async ({
|
|
186
47
|
client = 'generic',
|
|
187
48
|
prompt,
|
|
@@ -203,35 +64,26 @@ export const runHeadlessWrapper = async ({
|
|
|
203
64
|
throw new Error('command is required unless dryRun=true');
|
|
204
65
|
}
|
|
205
66
|
|
|
206
|
-
const
|
|
207
|
-
phase: 'start',
|
|
208
|
-
sessionId,
|
|
67
|
+
const sessionResolution = await resolveManagedStart({
|
|
209
68
|
prompt,
|
|
69
|
+
sessionId,
|
|
70
|
+
preparedStartResult,
|
|
210
71
|
ensureSession: true,
|
|
211
|
-
|
|
72
|
+
allowIsolation: true,
|
|
212
73
|
});
|
|
213
|
-
const sessionResolution = await ensureIsolatedSession({ prompt, sessionId, startResult: start });
|
|
214
74
|
const effectiveStart = sessionResolution.startResult;
|
|
215
75
|
const wrappedPrompt = buildWrappedPrompt({ prompt, startResult: effectiveStart });
|
|
216
|
-
const overheadTokens =
|
|
76
|
+
const overheadTokens = computeContextOverhead({ prompt, wrappedPrompt });
|
|
217
77
|
|
|
218
|
-
await
|
|
219
|
-
|
|
220
|
-
|
|
78
|
+
await recordAgentWrapperMetric({
|
|
79
|
+
phase: 'start',
|
|
80
|
+
client,
|
|
221
81
|
sessionId: effectiveStart.sessionId ?? null,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
isContextOverhead: overheadTokens > 0,
|
|
228
|
-
overheadTokens,
|
|
229
|
-
client,
|
|
230
|
-
dryRun,
|
|
231
|
-
isolatedSession: sessionResolution.isolated,
|
|
232
|
-
previousSessionId: sessionResolution.previousSessionId ?? null,
|
|
233
|
-
},
|
|
234
|
-
timestamp: new Date().toISOString(),
|
|
82
|
+
dryRun,
|
|
83
|
+
overheadTokens,
|
|
84
|
+
isolatedSession: sessionResolution.isolated,
|
|
85
|
+
previousSessionId: sessionResolution.previousSessionId ?? null,
|
|
86
|
+
autoStarted: sessionResolution.autoStarted,
|
|
235
87
|
});
|
|
236
88
|
|
|
237
89
|
const finalArgs = stdinPrompt ? [...args] : [...args, wrappedPrompt];
|
|
@@ -262,32 +114,21 @@ export const runHeadlessWrapper = async ({
|
|
|
262
114
|
streamOutput,
|
|
263
115
|
});
|
|
264
116
|
|
|
265
|
-
const resolvedEvent =
|
|
266
|
-
|
|
267
|
-
|
|
117
|
+
const { resolvedEvent, endResult } = await finalizeManagedRun({
|
|
118
|
+
prompt,
|
|
119
|
+
childResult,
|
|
268
120
|
sessionId: effectiveStart.sessionId ?? sessionId ?? undefined,
|
|
269
|
-
|
|
270
|
-
update: buildEndUpdate({ prompt, childResult }),
|
|
271
|
-
maxTokens: END_MAX_TOKENS,
|
|
121
|
+
requestedEvent: event,
|
|
272
122
|
});
|
|
273
123
|
|
|
274
|
-
await
|
|
275
|
-
|
|
276
|
-
|
|
124
|
+
await recordAgentWrapperMetric({
|
|
125
|
+
phase: 'end',
|
|
126
|
+
client,
|
|
277
127
|
sessionId: effectiveStart.sessionId ?? null,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
metadata: {
|
|
283
|
-
client,
|
|
284
|
-
exitCode: childResult.exitCode,
|
|
285
|
-
event: resolvedEvent,
|
|
286
|
-
isContextOverhead: false,
|
|
287
|
-
overheadTokens: 0,
|
|
288
|
-
isolatedSession: sessionResolution.isolated,
|
|
289
|
-
},
|
|
290
|
-
timestamp: new Date().toISOString(),
|
|
128
|
+
isolatedSession: sessionResolution.isolated,
|
|
129
|
+
exitCode: childResult.exitCode,
|
|
130
|
+
event: resolvedEvent,
|
|
131
|
+
autoStarted: sessionResolution.autoStarted,
|
|
291
132
|
});
|
|
292
133
|
|
|
293
134
|
return {
|
|
@@ -301,7 +142,7 @@ export const runHeadlessWrapper = async ({
|
|
|
301
142
|
stdout: childResult.stdout,
|
|
302
143
|
stderr: childResult.stderr,
|
|
303
144
|
start: effectiveStart,
|
|
304
|
-
end,
|
|
145
|
+
end: endResult,
|
|
305
146
|
sessionId: effectiveStart.sessionId ?? sessionId ?? null,
|
|
306
147
|
isolatedSession: sessionResolution.isolated,
|
|
307
148
|
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { smartContext } from '../../tools/smart-context.js';
|
|
2
|
+
import { smartSearch } from '../../tools/smart-search.js';
|
|
3
|
+
|
|
4
|
+
export const SAFE_CONTINUITY_STATES = new Set(['aligned', 'resume']);
|
|
5
|
+
|
|
6
|
+
export const MAX_TOP_FILES = 3;
|
|
7
|
+
export const MAX_PREFLIGHT_HINTS = 2;
|
|
8
|
+
export const MAX_FOCUS_LENGTH = 140;
|
|
9
|
+
export const MAX_GOAL_LENGTH = 120;
|
|
10
|
+
export const MAX_NEXT_STEP_LENGTH = 150;
|
|
11
|
+
export const DEFAULT_TRUNCATE_LENGTH = 160;
|
|
12
|
+
export const MIN_NEXT_STEP_LENGTH = 12;
|
|
13
|
+
export const MAX_NEXT_STEP_CAPTURE_LENGTH = 180;
|
|
14
|
+
export const MAX_RECOMMENDED_TOOLS = 3;
|
|
15
|
+
|
|
16
|
+
export const normalizeWhitespace = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
17
|
+
|
|
18
|
+
export const truncate = (value, maxLength = DEFAULT_TRUNCATE_LENGTH) => {
|
|
19
|
+
const normalized = normalizeWhitespace(value);
|
|
20
|
+
if (normalized.length <= maxLength) {
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (maxLength <= 3) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const asArray = (value) => Array.isArray(value) ? value : [];
|
|
32
|
+
|
|
33
|
+
export const uniqueCompact = (values) => [...new Set(
|
|
34
|
+
asArray(values)
|
|
35
|
+
.map((value) => normalizeWhitespace(value))
|
|
36
|
+
.filter(Boolean),
|
|
37
|
+
)];
|
|
38
|
+
|
|
39
|
+
export const extractContextTopFiles = (topFiles) => uniqueCompact(asArray(topFiles).map((item) => {
|
|
40
|
+
if (typeof item === 'string') {
|
|
41
|
+
return item;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return item?.file ?? item?.path ?? '';
|
|
45
|
+
})).slice(0, MAX_TOP_FILES);
|
|
46
|
+
|
|
47
|
+
export const extractPreflightTopFiles = (preflightResult) => {
|
|
48
|
+
if (!preflightResult) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (preflightResult.tool === 'smart_context') {
|
|
53
|
+
return uniqueCompact(asArray(preflightResult.result?.context).map((item) => item?.file).filter(Boolean)).slice(0, MAX_TOP_FILES);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (preflightResult.tool === 'smart_search') {
|
|
57
|
+
return extractContextTopFiles(preflightResult.result?.topFiles);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const extractPreflightHints = (preflightResult) => {
|
|
64
|
+
if (!preflightResult) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (preflightResult.tool === 'smart_context') {
|
|
69
|
+
return uniqueCompact(preflightResult.result?.hints).slice(0, MAX_PREFLIGHT_HINTS);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (preflightResult.tool === 'smart_search') {
|
|
73
|
+
const totalMatches = Number(preflightResult.result?.totalMatches ?? 0);
|
|
74
|
+
if (totalMatches > 0) {
|
|
75
|
+
return [`${totalMatches} search match(es) surfaced for the workflow target`];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return [];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const buildPreflightSummary = (preflightResult) => {
|
|
83
|
+
if (!preflightResult) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
tool: preflightResult.tool,
|
|
89
|
+
topFiles: extractPreflightTopFiles(preflightResult),
|
|
90
|
+
hints: extractPreflightHints(preflightResult),
|
|
91
|
+
totalMatches: Number(preflightResult.result?.totalMatches ?? 0),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const buildPreflightTask = ({ workflowProfile, prompt, startResult }) => {
|
|
96
|
+
if (!workflowProfile || typeof workflowProfile !== 'object') {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const normalizedPrompt = normalizeWhitespace(prompt);
|
|
101
|
+
const persistedNextStep = normalizeWhitespace(startResult?.summary?.nextStep);
|
|
102
|
+
const currentFocus = normalizeWhitespace(startResult?.summary?.currentFocus);
|
|
103
|
+
const refreshedTopFiles = extractContextTopFiles(startResult?.refreshedContext?.topFiles);
|
|
104
|
+
|
|
105
|
+
if (workflowProfile.commandName === 'continue' || workflowProfile.commandName === 'resume') {
|
|
106
|
+
if (persistedNextStep) {
|
|
107
|
+
return persistedNextStep;
|
|
108
|
+
}
|
|
109
|
+
if (currentFocus) {
|
|
110
|
+
return currentFocus;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (workflowProfile.commandName === 'task' && currentFocus && persistedNextStep) {
|
|
115
|
+
return `${currentFocus}. ${persistedNextStep}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (normalizedPrompt) {
|
|
119
|
+
return normalizedPrompt;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (refreshedTopFiles.length > 0) {
|
|
123
|
+
return `Inspect ${refreshedTopFiles.join(', ')} and continue the persisted task`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return workflowProfile.label;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const runWorkflowPreflight = async ({
|
|
130
|
+
workflowProfile,
|
|
131
|
+
prompt,
|
|
132
|
+
startResult,
|
|
133
|
+
contextTool = smartContext,
|
|
134
|
+
searchTool = smartSearch,
|
|
135
|
+
}) => {
|
|
136
|
+
const preflight = workflowProfile.preflight;
|
|
137
|
+
if (!preflight) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const preflightTask = buildPreflightTask({ workflowProfile, prompt, startResult });
|
|
142
|
+
|
|
143
|
+
if (preflight.tool === 'smart_context') {
|
|
144
|
+
const request = {
|
|
145
|
+
task: preflightTask,
|
|
146
|
+
detail: preflight.detail ?? 'minimal',
|
|
147
|
+
include: preflight.include ?? ['hints'],
|
|
148
|
+
maxTokens: preflight.maxTokens ?? 1200,
|
|
149
|
+
};
|
|
150
|
+
const result = await contextTool(request);
|
|
151
|
+
return {
|
|
152
|
+
tool: 'smart_context',
|
|
153
|
+
request,
|
|
154
|
+
result,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (preflight.tool === 'smart_search') {
|
|
159
|
+
const request = {
|
|
160
|
+
query: preflightTask,
|
|
161
|
+
intent: preflight.intent ?? workflowProfile.workflowIntent,
|
|
162
|
+
};
|
|
163
|
+
const result = await searchTool(request);
|
|
164
|
+
return {
|
|
165
|
+
tool: 'smart_search',
|
|
166
|
+
request,
|
|
167
|
+
result,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return null;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const buildContinuityGuidance = ({ startResult }) => {
|
|
175
|
+
const continuityState = startResult?.continuity?.state ?? 'unknown';
|
|
176
|
+
const lines = [`- Continuity: ${continuityState}`];
|
|
177
|
+
const nextStep = normalizeWhitespace(startResult?.summary?.nextStep);
|
|
178
|
+
const currentFocus = normalizeWhitespace(startResult?.summary?.currentFocus);
|
|
179
|
+
const refreshedTopFiles = extractContextTopFiles(startResult?.refreshedContext?.topFiles);
|
|
180
|
+
const recommendedNextTools = asArray(startResult?.recommendedPath?.nextTools)
|
|
181
|
+
.map((tool) => normalizeWhitespace(tool))
|
|
182
|
+
.filter(Boolean)
|
|
183
|
+
.slice(0, MAX_RECOMMENDED_TOOLS);
|
|
184
|
+
|
|
185
|
+
if (currentFocus) {
|
|
186
|
+
lines.push(`- Persisted focus: ${truncate(currentFocus, MAX_FOCUS_LENGTH)}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (nextStep) {
|
|
190
|
+
lines.push(`- Persisted next step: ${truncate(nextStep, MAX_FOCUS_LENGTH)}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (refreshedTopFiles.length > 0) {
|
|
194
|
+
lines.push(`- Refreshed top files: ${refreshedTopFiles.join(', ')}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (recommendedNextTools.length > 0) {
|
|
198
|
+
lines.push(`- smart_turn suggested: ${recommendedNextTools.join(' -> ')}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (startResult?.isolatedSession) {
|
|
202
|
+
lines.push('- Session handling: smart_turn already isolated this work from the previous session; revalidate before assuming old focus.');
|
|
203
|
+
} else if (continuityState === 'aligned' || continuityState === 'resume') {
|
|
204
|
+
lines.push('- Session handling: reuse the active session context and stay close to the persisted next step unless the task proves otherwise.');
|
|
205
|
+
} else if (continuityState === 'possible_shift' || continuityState === 'context_mismatch') {
|
|
206
|
+
lines.push('- Session handling: treat this as a shifted slice, validate the working set early, and avoid silent context reuse.');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return lines;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const buildWorkflowPromptWithPolicy = ({
|
|
213
|
+
prompt,
|
|
214
|
+
workflowProfile,
|
|
215
|
+
preflightSummary,
|
|
216
|
+
startResult,
|
|
217
|
+
}) => {
|
|
218
|
+
const lines = [
|
|
219
|
+
prompt,
|
|
220
|
+
'',
|
|
221
|
+
'Workflow policy:',
|
|
222
|
+
`- Mode: ${workflowProfile.policyMode}`,
|
|
223
|
+
`- Intent: ${workflowProfile.workflowIntent}`,
|
|
224
|
+
`- Prefer this tool order: ${workflowProfile.nextTools.join(' -> ')}`,
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
if (workflowProfile.checkpointStrategy) {
|
|
228
|
+
lines.push(`- Checkpoint rule: ${workflowProfile.checkpointStrategy}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
lines.push(...buildContinuityGuidance({ startResult }));
|
|
232
|
+
|
|
233
|
+
if (preflightSummary?.tool) {
|
|
234
|
+
lines.push(`- Preflight: ${preflightSummary.tool}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (preflightSummary?.topFiles?.length) {
|
|
238
|
+
lines.push(`- Focus files: ${preflightSummary.topFiles.join(', ')}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (preflightSummary?.hints?.length) {
|
|
242
|
+
lines.push(`- Signals: ${preflightSummary.hints.map((hint) => truncate(hint, 120)).join(' | ')}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return lines.join('\n');
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const buildWorkflowPolicyPayload = ({ commandName, workflowProfile, preflightSummary }) => ({
|
|
249
|
+
commandName,
|
|
250
|
+
label: workflowProfile.label,
|
|
251
|
+
policyMode: workflowProfile.policyMode,
|
|
252
|
+
intent: workflowProfile.workflowIntent,
|
|
253
|
+
specialized: workflowProfile.specialized,
|
|
254
|
+
nextTools: [...workflowProfile.nextTools],
|
|
255
|
+
checkpointStrategy: workflowProfile.checkpointStrategy,
|
|
256
|
+
preflight: preflightSummary,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export const extractNextStep = (value) => {
|
|
260
|
+
const normalized = normalizeWhitespace(value);
|
|
261
|
+
if (!normalized) {
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const explicitMatch = normalized.match(new RegExp(
|
|
266
|
+
`(?:next step|siguiente paso)\\s*[:\\-]\\s*([^.;\\n]{${MIN_NEXT_STEP_LENGTH},${MAX_NEXT_STEP_CAPTURE_LENGTH}})`,
|
|
267
|
+
'i',
|
|
268
|
+
));
|
|
269
|
+
if (explicitMatch?.[1]) {
|
|
270
|
+
return truncate(explicitMatch[1], MAX_NEXT_STEP_LENGTH);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return '';
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const buildTaskRunnerAutomaticity = ({
|
|
277
|
+
isWorkflowCommand = false,
|
|
278
|
+
startResult = null,
|
|
279
|
+
endResult = null,
|
|
280
|
+
workflowPolicy = null,
|
|
281
|
+
usedWrapper = false,
|
|
282
|
+
overheadTokens = 0,
|
|
283
|
+
managedByBaseOrchestrator = false,
|
|
284
|
+
}) => {
|
|
285
|
+
const safeOverheadTokens = Number.isFinite(overheadTokens) ? Math.max(0, overheadTokens) : 0;
|
|
286
|
+
const checkpointPersisted = Boolean(endResult && !endResult.checkpoint?.skipped && !endResult.checkpoint?.blocked);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
managedByBaseOrchestrator,
|
|
290
|
+
autoStartTriggered: isWorkflowCommand && Boolean(startResult),
|
|
291
|
+
autoPreflightTriggered: isWorkflowCommand && Boolean(workflowPolicy?.preflight?.tool),
|
|
292
|
+
autoCheckpointTriggered: checkpointPersisted,
|
|
293
|
+
autoWrappedPrompt: usedWrapper && safeOverheadTokens > 0,
|
|
294
|
+
isolatedSession: Boolean(startResult?.isolatedSession),
|
|
295
|
+
contextOverheadTokens: safeOverheadTokens,
|
|
296
|
+
};
|
|
297
|
+
};
|