clementine-agent 1.18.22 → 1.18.24
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/agent/assistant.d.ts +4 -0
- package/dist/agent/assistant.js +54 -17
- package/dist/agent/background-tasks.d.ts +5 -2
- package/dist/agent/background-tasks.js +12 -2
- package/dist/agent/local-turn.js +8 -3
- package/dist/agent/prompt-overrides/loader.js +4 -1
- package/dist/agent/turn-policy.js +13 -0
- package/dist/agent/workflow-runner.js +8 -0
- package/dist/cli/dashboard.js +1086 -154
- package/dist/config.d.ts +7 -3
- package/dist/config.js +44 -12
- package/dist/gateway/cron-scheduler.js +134 -12
- package/dist/gateway/failure-diagnostics.d.ts +3 -1
- package/dist/gateway/failure-diagnostics.js +31 -3
- package/dist/gateway/failure-monitor.js +1 -1
- package/dist/gateway/fix-verification.js +13 -3
- package/dist/gateway/job-health.js +9 -0
- package/dist/gateway/long-task-preflight.d.ts +21 -0
- package/dist/gateway/long-task-preflight.js +203 -0
- package/dist/gateway/router.d.ts +13 -2
- package/dist/gateway/router.js +198 -31
- package/dist/gateway/unleashed-status.d.ts +12 -0
- package/dist/gateway/unleashed-status.js +40 -0
- package/dist/types.d.ts +19 -0
- package/package.json +1 -1
|
@@ -291,6 +291,10 @@ export declare class PersonalAssistant {
|
|
|
291
291
|
};
|
|
292
292
|
delegateProfile?: AgentProfile;
|
|
293
293
|
abortSignal?: AbortSignal;
|
|
294
|
+
usageSource?: string;
|
|
295
|
+
usageSessionKey?: string;
|
|
296
|
+
usageLabel?: string;
|
|
297
|
+
usageAgentSlug?: string;
|
|
294
298
|
}): Promise<string>;
|
|
295
299
|
runCronJob(jobName: string, jobPrompt: string, tier?: number, maxTurns?: number, model?: string, workDir?: string, timeoutMs?: number, successCriteria?: string[], agentSlug?: string, opts?: {
|
|
296
300
|
disableAllTools?: boolean;
|
package/dist/agent/assistant.js
CHANGED
|
@@ -179,6 +179,27 @@ export function looksLikeContextThrashText(value) {
|
|
|
179
179
|
const text = String(value ?? '');
|
|
180
180
|
return /autocompact\s+is\s+thrashing|context\s+refilled\s+to\s+the\s+limit|refilled\s+to\s+the\s+limit\s+within/i.test(text);
|
|
181
181
|
}
|
|
182
|
+
function inferTerminalReasonFromFailure(value) {
|
|
183
|
+
const text = String(value ?? '').toLowerCase();
|
|
184
|
+
if (looksLikeContextThrashText(text) || /rapid_refill_breaker|maximum context|context.?length/.test(text)) {
|
|
185
|
+
return 'rapid_refill_breaker';
|
|
186
|
+
}
|
|
187
|
+
if (/prompt is too long|prompt too long|input is too long|request too large/.test(text)) {
|
|
188
|
+
return 'prompt_too_long';
|
|
189
|
+
}
|
|
190
|
+
if (/maximum number of turns|max_turns/.test(text)) {
|
|
191
|
+
return 'max_turns';
|
|
192
|
+
}
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
class UnleashedTaskFailedError extends Error {
|
|
196
|
+
terminalReason;
|
|
197
|
+
constructor(message, terminalReason) {
|
|
198
|
+
super(message);
|
|
199
|
+
this.terminalReason = terminalReason;
|
|
200
|
+
this.name = 'UnleashedTaskFailedError';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
182
203
|
export function contextThrashRecoveryNotice() {
|
|
183
204
|
return [
|
|
184
205
|
'I hit a context-size recovery issue while working on that.',
|
|
@@ -4987,7 +5008,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
4987
5008
|
}
|
|
4988
5009
|
// ── Plan Step Execution ───────────────────────────────────────────
|
|
4989
5010
|
async runPlanStep(stepId, prompt, opts = {}) {
|
|
4990
|
-
const { tier = 2, maxTurns = 15, model, disableTools = false, outputFormat, delegateProfile, abortSignal } = opts;
|
|
5011
|
+
const { tier = 2, maxTurns = 15, model, disableTools = false, outputFormat, delegateProfile, abortSignal, usageSource = 'plan_step', usageSessionKey, usageLabel, usageAgentSlug, } = opts;
|
|
4991
5012
|
// Don't mutate the global — pass source through the closure instead
|
|
4992
5013
|
// Per-step stall guard so concurrent steps don't cross-contaminate
|
|
4993
5014
|
const stepGuard = new StallGuard();
|
|
@@ -5034,7 +5055,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5034
5055
|
}
|
|
5035
5056
|
}
|
|
5036
5057
|
else if (message.type === 'result') {
|
|
5037
|
-
this.logQueryResult(message,
|
|
5058
|
+
this.logQueryResult(message, usageSource, usageSessionKey ?? `plan:${stepId}`, usageLabel ?? stepId, usageAgentSlug);
|
|
5038
5059
|
}
|
|
5039
5060
|
}
|
|
5040
5061
|
return extractDeliverable(trace) ||
|
|
@@ -5529,6 +5550,12 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5529
5550
|
let lastOutput = '';
|
|
5530
5551
|
let consecutiveErrors = 0;
|
|
5531
5552
|
const MAX_CONSECUTIVE_ERRORS = 3;
|
|
5553
|
+
const unleashedContextSafety = [
|
|
5554
|
+
'CONTEXT SAFETY:',
|
|
5555
|
+
'- Keep each phase bounded. Do not read full run logs, full CRON.md, raw exports, or large integration responses.',
|
|
5556
|
+
'- Pull records in small batches, summarize IDs/counts/statuses, and write bulky intermediate data to files instead of pasting it into the conversation.',
|
|
5557
|
+
'- If the task looks too broad for the remaining context, stop with a compact status summary and pending list rather than retrying broader reads.',
|
|
5558
|
+
].join('\n');
|
|
5532
5559
|
while (phase < UNLEASHED_MAX_PHASES) {
|
|
5533
5560
|
// Check cancellation
|
|
5534
5561
|
if (fs.existsSync(cancelFile)) {
|
|
@@ -5644,6 +5671,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5644
5671
|
`After each phase completes, your session will be resumed with fresh context.\n\n` +
|
|
5645
5672
|
`TASK:\n${jobPrompt}\n\n` +
|
|
5646
5673
|
unleashedSkillContext +
|
|
5674
|
+
`${unleashedContextSafety}\n\n` +
|
|
5647
5675
|
`IMPORTANT:\n` +
|
|
5648
5676
|
`- Work methodically through the task in phases\n` +
|
|
5649
5677
|
`- At the end of this phase, output a STATUS SUMMARY of what you accomplished and what remains\n` +
|
|
@@ -5682,6 +5710,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5682
5710
|
`Continuing unleashed task. This is phase ${phase}.\n` +
|
|
5683
5711
|
`Time remaining: ${remainingHours} hours. You have ${turnsPerPhase} turns this phase.\n` +
|
|
5684
5712
|
checkpointContext +
|
|
5713
|
+
`\n${unleashedContextSafety}\n` +
|
|
5685
5714
|
`\nContinue working on the task. Pick up where you left off.\n` +
|
|
5686
5715
|
`If the task is COMPLETE, output "TASK_COMPLETE:" followed by a final summary.\n\n` +
|
|
5687
5716
|
`IMPORTANT: Output a STATUS SUMMARY at the end of this phase.`;
|
|
@@ -5695,6 +5724,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5695
5724
|
`Previous phases encountered an error and the session was reset.\n\n` +
|
|
5696
5725
|
`TASK:\n${jobPrompt}\n` +
|
|
5697
5726
|
checkpointContext +
|
|
5727
|
+
`\n${unleashedContextSafety}\n` +
|
|
5698
5728
|
`\nCheck any files or progress from prior phases, then continue the work.\n` +
|
|
5699
5729
|
`If the task is COMPLETE, output "TASK_COMPLETE:" followed by a final summary.\n\n` +
|
|
5700
5730
|
`IMPORTANT: Output a STATUS SUMMARY at the end of this phase.`;
|
|
@@ -5827,8 +5857,27 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5827
5857
|
catch (err) {
|
|
5828
5858
|
clearTimeout(phaseTimer);
|
|
5829
5859
|
clearInterval(beaconTimer);
|
|
5860
|
+
const terminalReason = inferTerminalReasonFromFailure(err);
|
|
5861
|
+
if (terminalReason && !this._lastTerminalReason) {
|
|
5862
|
+
this._lastTerminalReason = terminalReason;
|
|
5863
|
+
}
|
|
5830
5864
|
logger.error({ err, jobName, phase }, `Unleashed task phase ${phase} error`);
|
|
5831
|
-
appendProgress({ event: 'phase_error', phase, error: String(err) });
|
|
5865
|
+
appendProgress({ event: 'phase_error', phase, error: String(err), terminalReason });
|
|
5866
|
+
if (terminalReason === 'rapid_refill_breaker' || terminalReason === 'prompt_too_long') {
|
|
5867
|
+
appendProgress({ event: 'aborted', phase, reason: terminalReason });
|
|
5868
|
+
writeStatus({
|
|
5869
|
+
jobName,
|
|
5870
|
+
status: 'error',
|
|
5871
|
+
phase,
|
|
5872
|
+
startedAt,
|
|
5873
|
+
finishedAt: new Date().toISOString(),
|
|
5874
|
+
terminalReason,
|
|
5875
|
+
});
|
|
5876
|
+
const message = (`Task "${jobName}" aborted in phase ${phase}: ${terminalReason}. ` +
|
|
5877
|
+
`The phase exceeded the context window, so Clementine stopped instead of retrying the same broad task shape.`);
|
|
5878
|
+
logger.error({ jobName, phase, terminalReason }, 'Unleashed task aborted on context-size failure');
|
|
5879
|
+
throw new UnleashedTaskFailedError(message, terminalReason);
|
|
5880
|
+
}
|
|
5832
5881
|
consecutiveErrors++;
|
|
5833
5882
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
5834
5883
|
appendProgress({ event: 'aborted', phase, reason: `${MAX_CONSECUTIVE_ERRORS} consecutive phase errors` });
|
|
@@ -5837,13 +5886,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5837
5886
|
const errorResult = lastOutput || (`Task "${jobName}" aborted after ${MAX_CONSECUTIVE_ERRORS} consecutive phase errors. ` +
|
|
5838
5887
|
`Check \`clementine cron runs ${jobName}\` for the failing phase, or retry with ` +
|
|
5839
5888
|
`\`clementine cron run ${jobName}\`.`);
|
|
5840
|
-
|
|
5841
|
-
try {
|
|
5842
|
-
this.onUnleashedComplete(jobName, errorResult);
|
|
5843
|
-
}
|
|
5844
|
-
catch { /* non-fatal */ }
|
|
5845
|
-
}
|
|
5846
|
-
return errorResult;
|
|
5889
|
+
throw new UnleashedTaskFailedError(errorResult, this._lastTerminalReason);
|
|
5847
5890
|
}
|
|
5848
5891
|
// On error, try to continue with a fresh session
|
|
5849
5892
|
sessionId = '';
|
|
@@ -5930,13 +5973,7 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5930
5973
|
writeStatus({ jobName, status: 'max_phases', phase, startedAt, finishedAt: new Date().toISOString() });
|
|
5931
5974
|
logger.warn(`Unleashed task ${jobName} hit max phases (${UNLEASHED_MAX_PHASES})`);
|
|
5932
5975
|
const maxPhasesResult = lastOutput || `Task "${jobName}" reached maximum phase limit (${UNLEASHED_MAX_PHASES}).`;
|
|
5933
|
-
|
|
5934
|
-
try {
|
|
5935
|
-
this.onUnleashedComplete(jobName, maxPhasesResult);
|
|
5936
|
-
}
|
|
5937
|
-
catch { /* non-fatal */ }
|
|
5938
|
-
}
|
|
5939
|
-
return maxPhasesResult;
|
|
5976
|
+
throw new UnleashedTaskFailedError(maxPhasesResult, this._lastTerminalReason);
|
|
5940
5977
|
}
|
|
5941
5978
|
// ── Team Task Execution (Unleashed for Team Messages) ────────────
|
|
5942
5979
|
/**
|
|
@@ -31,6 +31,7 @@ export declare function createBackgroundTask(input: {
|
|
|
31
31
|
fromAgent: string;
|
|
32
32
|
prompt: string;
|
|
33
33
|
maxMinutes: number;
|
|
34
|
+
sessionKey?: string;
|
|
34
35
|
}, opts?: BackgroundTaskOptions): BackgroundTask;
|
|
35
36
|
/** Load a task by id, or null if not found / malformed. */
|
|
36
37
|
export declare function loadBackgroundTask(id: string, opts?: BackgroundTaskOptions): BackgroundTask | null;
|
|
@@ -51,6 +52,8 @@ export declare function markFailed(id: string, error: string, reason?: 'failed'
|
|
|
51
52
|
* Returns the count of tasks aborted.
|
|
52
53
|
*/
|
|
53
54
|
export declare function abortStaleRunningTasks(opts?: BackgroundTaskOptions): number;
|
|
54
|
-
/**
|
|
55
|
-
export declare function
|
|
55
|
+
/** Delete a task file. Callers should avoid deleting active tasks. */
|
|
56
|
+
export declare function deleteBackgroundTask(id: string, opts?: BackgroundTaskOptions): void;
|
|
57
|
+
/** Backward-compatible test helper alias. */
|
|
58
|
+
export declare const _deleteBackgroundTask: typeof deleteBackgroundTask;
|
|
56
59
|
//# sourceMappingURL=background-tasks.d.ts.map
|
|
@@ -56,6 +56,8 @@ export function createBackgroundTask(input, opts) {
|
|
|
56
56
|
status: 'pending',
|
|
57
57
|
createdAt: now.toISOString(),
|
|
58
58
|
};
|
|
59
|
+
if (input.sessionKey)
|
|
60
|
+
task.sessionKey = input.sessionKey;
|
|
59
61
|
safeWrite(pathFor(task.id, opts), task);
|
|
60
62
|
return task;
|
|
61
63
|
}
|
|
@@ -104,6 +106,8 @@ export function markRunning(id, opts) {
|
|
|
104
106
|
const task = loadBackgroundTask(id, opts);
|
|
105
107
|
if (!task)
|
|
106
108
|
return null;
|
|
109
|
+
if (task.status !== 'pending')
|
|
110
|
+
return null;
|
|
107
111
|
task.status = 'running';
|
|
108
112
|
task.startedAt = new Date().toISOString();
|
|
109
113
|
safeWrite(pathFor(id, opts), task);
|
|
@@ -114,6 +118,8 @@ export function markDone(id, result, deliverableNote, opts) {
|
|
|
114
118
|
const task = loadBackgroundTask(id, opts);
|
|
115
119
|
if (!task)
|
|
116
120
|
return null;
|
|
121
|
+
if (task.status !== 'running')
|
|
122
|
+
return task;
|
|
117
123
|
task.status = 'done';
|
|
118
124
|
task.completedAt = new Date().toISOString();
|
|
119
125
|
task.result = result;
|
|
@@ -127,6 +133,8 @@ export function markFailed(id, error, reason = 'failed', opts) {
|
|
|
127
133
|
const task = loadBackgroundTask(id, opts);
|
|
128
134
|
if (!task)
|
|
129
135
|
return null;
|
|
136
|
+
if (task.status === 'done' || task.status === 'failed' || task.status === 'aborted')
|
|
137
|
+
return task;
|
|
130
138
|
task.status = reason;
|
|
131
139
|
task.completedAt = new Date().toISOString();
|
|
132
140
|
task.error = error.slice(0, 1000);
|
|
@@ -147,8 +155,8 @@ export function abortStaleRunningTasks(opts) {
|
|
|
147
155
|
}
|
|
148
156
|
return aborted;
|
|
149
157
|
}
|
|
150
|
-
/**
|
|
151
|
-
export function
|
|
158
|
+
/** Delete a task file. Callers should avoid deleting active tasks. */
|
|
159
|
+
export function deleteBackgroundTask(id, opts) {
|
|
152
160
|
try {
|
|
153
161
|
const file = pathFor(id, opts);
|
|
154
162
|
if (existsSync(file))
|
|
@@ -156,4 +164,6 @@ export function _deleteBackgroundTask(id, opts) {
|
|
|
156
164
|
}
|
|
157
165
|
catch { /* ignore */ }
|
|
158
166
|
}
|
|
167
|
+
/** Backward-compatible test helper alias. */
|
|
168
|
+
export const _deleteBackgroundTask = deleteBackgroundTask;
|
|
159
169
|
//# sourceMappingURL=background-tasks.js.map
|
package/dist/agent/local-turn.js
CHANGED
|
@@ -14,15 +14,20 @@ function wordCount(text) {
|
|
|
14
14
|
}
|
|
15
15
|
export function isStopRequest(text) {
|
|
16
16
|
const n = normalize(text);
|
|
17
|
+
if (/\bbg-[a-z0-9]+-[a-f0-9]{6}\b/i.test(n) && /^(stop|cancel|abort)\b/.test(n))
|
|
18
|
+
return true;
|
|
17
19
|
if (wordCount(n) > 5)
|
|
18
20
|
return false;
|
|
19
|
-
return /^(stop|cancel|abort|halt|pause|nevermind|never mind|wait stop|stop please|cancel that|stop that)$/.test(n);
|
|
21
|
+
return /^(stop|cancel|abort|halt|pause|nevermind|never mind|wait stop|stop please|cancel that|stop that|cancel it|stop it|cancel task|stop task|cancel the task|stop the task|cancel background|stop background)$/.test(n);
|
|
20
22
|
}
|
|
21
23
|
export function isStatusRequest(text) {
|
|
22
24
|
const n = normalize(text);
|
|
23
|
-
if (wordCount(n) >
|
|
25
|
+
if (wordCount(n) > 12)
|
|
24
26
|
return false;
|
|
25
|
-
|
|
27
|
+
if (/\bbg-[a-z0-9]+-[a-f0-9]{6}\b/i.test(n) && /\b(status|progress|check|update|running|done|finished)\b/.test(n)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return /^(status|task status|deep status|progress|progress update|what'?s happening|what'?s going on|what are you doing|what are you working on|what are you running|are you working|anything running|what'?s runnin?g?(?: now| right now)?|what is runnin?g?(?: now| right now)?|background status|check status|where are we|any update|any updates|can i get an update|do you have an update|update me|is it done|is it done yet|is it finished|is it finished yet|done yet|did it finish|still running|is it still running|are we done|how'?s (?:it|that|this|the task|the job|the run|the background task) (?:coming along|progressing)|how is (?:it|that|this|the task|the job|the run|the background task) (?:coming along|progressing)|how'?s (?:the task|the job|the run|the background task) going|how is (?:the task|the job|the run|the background task) going)$/.test(n);
|
|
26
31
|
}
|
|
27
32
|
export function isLastActionRequest(text) {
|
|
28
33
|
const n = normalize(text);
|
|
@@ -105,7 +105,7 @@ export function loadPromptOverridesForJob(jobName, agentSlug, opts) {
|
|
|
105
105
|
if (o.scope === 'agent')
|
|
106
106
|
return agentSlug != null && o.scopeKey === agentSlug;
|
|
107
107
|
if (o.scope === 'job')
|
|
108
|
-
return o.scopeKey === jobName;
|
|
108
|
+
return o.scopeKey === jobName || o.scopeKey === bareJobName(jobName);
|
|
109
109
|
return false;
|
|
110
110
|
});
|
|
111
111
|
if (applicable.length === 0)
|
|
@@ -113,6 +113,9 @@ export function loadPromptOverridesForJob(jobName, agentSlug, opts) {
|
|
|
113
113
|
applicable.sort((a, b) => a.priority - b.priority);
|
|
114
114
|
return applicable.map(o => o.body).join('\n\n');
|
|
115
115
|
}
|
|
116
|
+
function bareJobName(jobName) {
|
|
117
|
+
return jobName.includes(':') ? jobName.split(':').slice(1).join(':') : jobName;
|
|
118
|
+
}
|
|
116
119
|
/** Install fs.watch on the overrides directory tree. Safe to call multiple times. */
|
|
117
120
|
export function watchPromptOverrides(opts) {
|
|
118
121
|
if (watcherInstalled)
|
|
@@ -12,6 +12,7 @@ const GOAL_REF_RE = /\b(goal|goals|objective|objectives|blocker|next action|next
|
|
|
12
12
|
const LOCAL_TOOL_RE = /\b(repo|repository|code|file|files|folder|directory|path|log|logs|config|build|test|typecheck|lint|npm|git|commit|push|pull|branch|diff|patch|edit|write|implement|fix|refactor|run|diagnose|investigate|troubleshoot|cron|scheduler|lease)\b/i;
|
|
13
13
|
const COMPLEX_RE = /\b(multiple|several|many|bulk|batch|parallel|deep mode|background|research|analyze|audit|review|across|end to end|entire)\b/i;
|
|
14
14
|
const ADMIN_RE = /\b(self[- ]?update|restart|daemon|npm publish|publish to npm|doctor|integration|credential|env var|environment variable|set up|setup|configure)\b/i;
|
|
15
|
+
const BACKGROUND_STATUS_FOLLOWUP_RE = /\bbg-[a-z0-9]+-[a-f0-9]{6}\b|\b(status|progress|progress update|any updates?|done yet|did it finish|still running|coming along|background status)\b/i;
|
|
15
16
|
const STANDALONE_GREETINGS = new Set([
|
|
16
17
|
'hi',
|
|
17
18
|
'hey',
|
|
@@ -90,6 +91,18 @@ export function decideTurnPolicy(input) {
|
|
|
90
91
|
reason: 'explicit-full-surface',
|
|
91
92
|
};
|
|
92
93
|
}
|
|
94
|
+
if (input.hasRecentContext && BACKGROUND_STATUS_FOLLOWUP_RE.test(text)) {
|
|
95
|
+
return {
|
|
96
|
+
retrievalTier: 'search',
|
|
97
|
+
disableAllTools: false,
|
|
98
|
+
enableTeams: false,
|
|
99
|
+
maxTurns: Math.min(intent.suggestedMaxTurns, 6),
|
|
100
|
+
effort: 'low',
|
|
101
|
+
allowProactiveGoals: false,
|
|
102
|
+
fetchLinks: false,
|
|
103
|
+
reason: 'background-status-followup',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
93
106
|
if (isStandaloneGreeting(text)) {
|
|
94
107
|
return {
|
|
95
108
|
retrievalTier: 'none',
|
|
@@ -230,6 +230,10 @@ export class WorkflowRunner {
|
|
|
230
230
|
tier: resolvedStep.tier,
|
|
231
231
|
maxTurns: resolvedStep.maxTurns,
|
|
232
232
|
model: resolvedStep.model,
|
|
233
|
+
usageSource: 'workflow_step',
|
|
234
|
+
usageSessionKey: `workflow:${workflow.name}:${step.id}`,
|
|
235
|
+
usageLabel: `${workflow.name}:${step.id}`,
|
|
236
|
+
usageAgentSlug: workflow.agentSlug,
|
|
233
237
|
});
|
|
234
238
|
return { stepId: step.id, result, durationMs: Date.now() - stepStart };
|
|
235
239
|
}), MAX_CONCURRENT_STEPS);
|
|
@@ -269,6 +273,10 @@ export class WorkflowRunner {
|
|
|
269
273
|
try {
|
|
270
274
|
finalOutput = await this.assistant.runPlanStep('__synthesis__', synthPrompt, {
|
|
271
275
|
tier: 2, maxTurns: 5, disableTools: true,
|
|
276
|
+
usageSource: 'workflow_step',
|
|
277
|
+
usageSessionKey: `workflow:${workflow.name}:__synthesis__`,
|
|
278
|
+
usageLabel: `${workflow.name}:__synthesis__`,
|
|
279
|
+
usageAgentSlug: workflow.agentSlug,
|
|
272
280
|
});
|
|
273
281
|
}
|
|
274
282
|
catch (err) {
|