smart-context-mcp 1.6.2 → 1.7.1
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/README.md +2 -2
- package/package.json +1 -1
- package/scripts/headless-wrapper.js +2 -1
- package/scripts/report-metrics.js +2 -2
- package/scripts/task-runner.js +2 -1
- 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 +39 -195
- package/src/orchestration/policy/event-policy.js +297 -0
- package/src/task-runner.js +33 -247
- package/src/utils/client-detection.js +33 -0
|
@@ -1,123 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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';
|
|
10
|
+
import { detectClient } from '../utils/client-detection.js';
|
|
121
11
|
|
|
122
12
|
const runChildProcess = ({ command, args, env, stdinText, streamOutput }) => new Promise((resolve, reject) => {
|
|
123
13
|
const child = spawn(command, args, {
|
|
@@ -154,36 +44,8 @@ const runChildProcess = ({ command, args, env, stdinText, streamOutput }) => new
|
|
|
154
44
|
}
|
|
155
45
|
});
|
|
156
46
|
|
|
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
47
|
export const runHeadlessWrapper = async ({
|
|
186
|
-
client =
|
|
48
|
+
client = null,
|
|
187
49
|
prompt,
|
|
188
50
|
command,
|
|
189
51
|
args = [],
|
|
@@ -195,6 +57,8 @@ export const runHeadlessWrapper = async ({
|
|
|
195
57
|
runCommand = runChildProcess,
|
|
196
58
|
preparedStartResult = null,
|
|
197
59
|
} = {}) => {
|
|
60
|
+
const resolvedClient = client ?? detectClient();
|
|
61
|
+
|
|
198
62
|
if (!normalizeWhitespace(prompt)) {
|
|
199
63
|
throw new Error('prompt is required');
|
|
200
64
|
}
|
|
@@ -203,41 +67,32 @@ export const runHeadlessWrapper = async ({
|
|
|
203
67
|
throw new Error('command is required unless dryRun=true');
|
|
204
68
|
}
|
|
205
69
|
|
|
206
|
-
const
|
|
207
|
-
phase: 'start',
|
|
208
|
-
sessionId,
|
|
70
|
+
const sessionResolution = await resolveManagedStart({
|
|
209
71
|
prompt,
|
|
72
|
+
sessionId,
|
|
73
|
+
preparedStartResult,
|
|
210
74
|
ensureSession: true,
|
|
211
|
-
|
|
75
|
+
allowIsolation: true,
|
|
212
76
|
});
|
|
213
|
-
const sessionResolution = await ensureIsolatedSession({ prompt, sessionId, startResult: start });
|
|
214
77
|
const effectiveStart = sessionResolution.startResult;
|
|
215
78
|
const wrappedPrompt = buildWrappedPrompt({ prompt, startResult: effectiveStart });
|
|
216
|
-
const overheadTokens =
|
|
79
|
+
const overheadTokens = computeContextOverhead({ prompt, wrappedPrompt });
|
|
217
80
|
|
|
218
|
-
await
|
|
219
|
-
|
|
220
|
-
|
|
81
|
+
await recordAgentWrapperMetric({
|
|
82
|
+
phase: 'start',
|
|
83
|
+
client: resolvedClient,
|
|
221
84
|
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(),
|
|
85
|
+
dryRun,
|
|
86
|
+
overheadTokens,
|
|
87
|
+
isolatedSession: sessionResolution.isolated,
|
|
88
|
+
previousSessionId: sessionResolution.previousSessionId ?? null,
|
|
89
|
+
autoStarted: sessionResolution.autoStarted,
|
|
235
90
|
});
|
|
236
91
|
|
|
237
92
|
const finalArgs = stdinPrompt ? [...args] : [...args, wrappedPrompt];
|
|
238
93
|
if (dryRun) {
|
|
239
94
|
return {
|
|
240
|
-
client,
|
|
95
|
+
client: resolvedClient,
|
|
241
96
|
dryRun: true,
|
|
242
97
|
command,
|
|
243
98
|
args: finalArgs,
|
|
@@ -262,36 +117,25 @@ export const runHeadlessWrapper = async ({
|
|
|
262
117
|
streamOutput,
|
|
263
118
|
});
|
|
264
119
|
|
|
265
|
-
const resolvedEvent =
|
|
266
|
-
|
|
267
|
-
|
|
120
|
+
const { resolvedEvent, endResult } = await finalizeManagedRun({
|
|
121
|
+
prompt,
|
|
122
|
+
childResult,
|
|
268
123
|
sessionId: effectiveStart.sessionId ?? sessionId ?? undefined,
|
|
269
|
-
|
|
270
|
-
update: buildEndUpdate({ prompt, childResult }),
|
|
271
|
-
maxTokens: END_MAX_TOKENS,
|
|
124
|
+
requestedEvent: event,
|
|
272
125
|
});
|
|
273
126
|
|
|
274
|
-
await
|
|
275
|
-
|
|
276
|
-
|
|
127
|
+
await recordAgentWrapperMetric({
|
|
128
|
+
phase: 'end',
|
|
129
|
+
client: resolvedClient,
|
|
277
130
|
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(),
|
|
131
|
+
isolatedSession: sessionResolution.isolated,
|
|
132
|
+
exitCode: childResult.exitCode,
|
|
133
|
+
event: resolvedEvent,
|
|
134
|
+
autoStarted: sessionResolution.autoStarted,
|
|
291
135
|
});
|
|
292
136
|
|
|
293
137
|
return {
|
|
294
|
-
client,
|
|
138
|
+
client: resolvedClient,
|
|
295
139
|
command,
|
|
296
140
|
args: finalArgs,
|
|
297
141
|
wrappedPrompt,
|
|
@@ -301,7 +145,7 @@ export const runHeadlessWrapper = async ({
|
|
|
301
145
|
stdout: childResult.stdout,
|
|
302
146
|
stderr: childResult.stderr,
|
|
303
147
|
start: effectiveStart,
|
|
304
|
-
end,
|
|
148
|
+
end: endResult,
|
|
305
149
|
sessionId: effectiveStart.sessionId ?? sessionId ?? null,
|
|
306
150
|
isolatedSession: sessionResolution.isolated,
|
|
307
151
|
};
|
|
@@ -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
|
+
};
|