clementine-agent 1.18.48 → 1.18.50

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.
Files changed (36) hide show
  1. package/dist/agent/agent-definitions.js +49 -8
  2. package/dist/agent/assistant.d.ts +1 -110
  3. package/dist/agent/assistant.js +96 -3394
  4. package/dist/agent/route-classifier.js +2 -3
  5. package/dist/agent/run-agent-context.d.ts +17 -0
  6. package/dist/agent/run-agent-context.js +80 -0
  7. package/dist/agent/run-agent-cron.js +17 -15
  8. package/dist/agent/run-agent-heartbeat.js +15 -2
  9. package/dist/agent/run-agent-team-task.js +15 -1
  10. package/dist/agent/run-agent.d.ts +6 -0
  11. package/dist/agent/run-agent.js +41 -9
  12. package/dist/agent/wave-scheduler.d.ts +21 -0
  13. package/dist/agent/wave-scheduler.js +72 -0
  14. package/dist/agent/workflow-runner.d.ts +2 -2
  15. package/dist/agent/workflow-runner.js +3 -3
  16. package/dist/channels/discord-agent-bot.js +1 -80
  17. package/dist/channels/discord.js +4 -108
  18. package/dist/gateway/cron-scheduler.js +4 -129
  19. package/dist/gateway/failure-diagnostics.js +6 -1
  20. package/dist/gateway/heartbeat-scheduler.js +12 -2
  21. package/dist/gateway/outcome-grader.js +2 -3
  22. package/dist/gateway/router.d.ts +5 -23
  23. package/dist/gateway/router.js +173 -893
  24. package/dist/tools/admin-tools.js +10 -43
  25. package/dist/types.d.ts +24 -0
  26. package/package.json +1 -1
  27. package/dist/agent/complexity-classifier.d.ts +0 -37
  28. package/dist/agent/complexity-classifier.js +0 -209
  29. package/dist/agent/fanout-policy.d.ts +0 -92
  30. package/dist/agent/fanout-policy.js +0 -243
  31. package/dist/agent/orchestrator.d.ts +0 -88
  32. package/dist/agent/orchestrator.js +0 -862
  33. package/dist/events/bus.d.ts +0 -43
  34. package/dist/events/bus.js +0 -136
  35. package/dist/gateway/long-task-preflight.d.ts +0 -30
  36. package/dist/gateway/long-task-preflight.js +0 -290
@@ -62,18 +62,41 @@ const CRON_FIXER_PROMPT = [
62
62
  '',
63
63
  'Return: a one-paragraph summary of what you applied (or what is blocking apply), per job.',
64
64
  ].join('\n');
65
+ /** Build a routing-signal description for a hired agent.
66
+ * The SDK uses descriptions for auto-routing — they must be imperative
67
+ * ("Use for: ..."), not narrative prose. Otherwise the main agent
68
+ * has nothing to match user phrasings against. */
69
+ function buildHiredAgentDescription(p) {
70
+ const role = p.role ?? p.description ?? `${p.name}, a hired agent`;
71
+ const slug = p.slug;
72
+ const capabilities = (p.routingHints && p.routingHints.length > 0)
73
+ ? p.routingHints.join(', ')
74
+ : (p.description ?? '').slice(0, 200);
75
+ return [
76
+ `Delegate to ${p.name} (${slug}).`,
77
+ capabilities ? `Use for: ${capabilities}.` : '',
78
+ `Role: ${role}.`,
79
+ 'Spawn this subagent when the user names them, asks a question in their domain, or asks Clementine to "have <name> do X".',
80
+ ].filter(Boolean).join(' ');
81
+ }
65
82
  /** Map a hired-agent profile to an AgentDefinition.
66
83
  * Used when Clementine wants to delegate to Ross/Sasha/Nora etc. */
67
84
  function profileToAgentDefinition(p) {
85
+ // Always include `Agent` so the subagent can further fan out, plus
86
+ // core read tools as a baseline. profile.team.allowedTools narrows
87
+ // beyond this when set.
88
+ const baseline = ['Agent', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch', 'TodoWrite'];
89
+ const tools = p.team?.allowedTools?.length
90
+ ? Array.from(new Set(['Agent', ...p.team.allowedTools]))
91
+ : baseline;
68
92
  return {
69
- description: p.description ?? `${p.name} (hired agent: ${p.slug})`,
93
+ description: buildHiredAgentDescription(p),
70
94
  prompt: p.systemPromptBody ?? `You are ${p.name}.`,
71
- // Honor explicit allowlist when present; otherwise inherit from parent.
72
- ...(p.team?.allowedTools?.length ? { tools: p.team.allowedTools } : {}),
95
+ tools,
73
96
  // Hired agents keep their configured model (Sonnet by default).
74
97
  ...(p.model ? { model: p.model } : { model: 'sonnet' }),
75
- // Effort: hired agents do real work, default medium. Caller can override.
76
- effort: 'medium',
98
+ // Effort: hired agents do real work, default medium. Profile may override.
99
+ ...(p.effort ? { effort: p.effort } : { effort: 'medium' }),
77
100
  };
78
101
  }
79
102
  /**
@@ -89,8 +112,19 @@ export function buildAgentMap(opts = {}) {
89
112
  // ── System subagents ────────────────────────────────────────────
90
113
  // Planner: opus, no tools, single turn. Used when the parent agent
91
114
  // sees a multi-step request and wants a decomposition.
115
+ // Description is imperative + matches real user phrasings — the SDK
116
+ // matches against it for auto-routing, so prose doesn't trigger.
92
117
  map['planner'] = {
93
- description: 'Decompose a multi-step user request into atomic, parallel-safe steps. Use for "research these N items", "build a comprehensive X", "for each Y do Z", or any request that obviously involves multiple distinct sub-tasks. Returns a JSON plan; the parent then executes the steps (often by spawning more subagents per step).',
118
+ description: [
119
+ 'Use this subagent BEFORE doing the work whenever the user request',
120
+ 'involves 3 or more items, multiple distinct subtasks, or a phrase',
121
+ 'like "research my top N", "for each X do Y", "look at all of",',
122
+ '"go through every", "do A, B, and C", or any task that would burn',
123
+ 'context if processed serially. The planner returns a JSON plan',
124
+ 'with parallel-safe steps; you then spawn researcher/cron-fixer/',
125
+ 'hired-agent subagents per step. Always prefer this over doing',
126
+ 'multi-item work yourself in the main conversation.',
127
+ ].join(' '),
94
128
  prompt: PLANNER_PROMPT,
95
129
  model: 'opus',
96
130
  tools: [], // pure reasoning, no tools
@@ -98,11 +132,18 @@ export function buildAgentMap(opts = {}) {
98
132
  maxTurns: 1,
99
133
  };
100
134
  // Researcher: haiku, per-item investigation. Cheap fan-out target.
135
+ // No Bash — researcher is read-only fanout, must not mutate state.
101
136
  map['researcher'] = {
102
- description: 'Investigate ONE specific item (one lead, one account, one file, one topic) and return a one-paragraph summary. Use for per-item parallel work spawned by the planner. Cheap and fast.',
137
+ description: [
138
+ 'Use this subagent to investigate ONE specific item — a single',
139
+ 'lead, account, file, web page, or topic — and return a',
140
+ 'one-paragraph summary. Spawn it in PARALLEL via the Agent tool',
141
+ 'with one subagent per item when the planner returns multiple',
142
+ 'research steps. Read-only: never mutates state. Cheap (Haiku).',
143
+ ].join(' '),
103
144
  prompt: RESEARCHER_PROMPT,
104
145
  model: 'haiku',
105
- tools: ['Read', 'Grep', 'Glob', 'Bash', 'WebSearch', 'WebFetch'],
146
+ tools: ['Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'],
106
147
  effort: 'low',
107
148
  maxTurns: 15,
108
149
  };
@@ -9,9 +9,8 @@
9
9
  * - Session expiry: sessions expire after 24 hours of inactivity
10
10
  * - Env isolation: Claude subprocess doesn't see credential env vars
11
11
  */
12
- import type { AgentProfile, OnTextCallback, OnToolActivityCallback, VerboseLevel } from '../types.js';
12
+ import type { AgentProfile } from '../types.js';
13
13
  import { AgentManager } from './agent-manager.js';
14
- import { type ToolsetName } from './toolsets.js';
15
14
  /**
16
15
  * Estimate token count for Claude.
17
16
  *
@@ -28,13 +27,10 @@ import { type ToolsetName } from './toolsets.js';
28
27
  * SDK result; this function is for pre-flight planning only.
29
28
  */
30
29
  export declare function estimateTokens(text: string): number;
31
- export declare function looksLikeContextThrashText(value: unknown): boolean;
32
30
  /** Format a millisecond duration as a human-friendly "X ago" string. */
33
31
  export declare function formatTimeAgo(ms: number): string;
34
- export declare function scrubInternalContextBlocks(text: string): string;
35
32
  export declare function looksLikeOneMillionContextError(value: unknown): boolean;
36
33
  export declare function oneMillionContextRecoveryMessage(): string;
37
- export declare function looksLikeProviderApiErrorResponse(value: unknown): boolean;
38
34
  export declare function looksLikeNoResponseRequested(value: unknown): boolean;
39
35
  /** Autonomous jobs use this sentinel to mean "completed, but do not notify the owner." */
40
36
  export declare function isAutonomousNothingOutput(response: string): boolean;
@@ -59,13 +55,6 @@ export interface ProactiveGoalInput {
59
55
  nextActions?: string[];
60
56
  };
61
57
  }
62
- /**
63
- * Build the compact "active goals" block that gets injected when no goal
64
- * keyword matches the user's prompt. Pure so it can be tested without the
65
- * full Assistant/vault setup.
66
- */
67
- export declare function buildActiveGoalsBlock(goals: ProactiveGoalInput[], agentSlug?: string | null, maxEntries?: number): string;
68
- export declare function chunkReferencedInResponse(chunkContent: string, responseLower: string): boolean;
69
58
  export declare class PersonalAssistant {
70
59
  static readonly MAX_SESSION_EXCHANGES = 40;
71
60
  private sessions;
@@ -80,9 +69,6 @@ export declare class PersonalAssistant {
80
69
  private _lastDailyNotePath;
81
70
  private memoryStore;
82
71
  private _lastUserMessage?;
83
- private onUnleashedComplete;
84
- private onPhaseComplete;
85
- private onPhaseProgress;
86
72
  onSkillProposed: ((skill: import('../types.js').SkillDocument) => void) | null;
87
73
  private _lastMcpStatus;
88
74
  private _lastMcpStatusTime;
@@ -91,8 +77,6 @@ export declare class PersonalAssistant {
91
77
  /** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
92
78
  private stallNudges;
93
79
  /** Last contradiction finding per session, consumed by the session transcript writer to splice a correction note. */
94
- private _lastContradictionFinding;
95
- private _compactedSessions;
96
80
  /** Last auto-matched project per session — exposed for CLI display. */
97
81
  private _lastMatchedProject;
98
82
  /**
@@ -100,7 +84,6 @@ export declare class PersonalAssistant {
100
84
  * post-response outcome scorer can check which actually got referenced.
101
85
  * Cleared after each scoring pass.
102
86
  */
103
- private _lastRetrievedChunks;
104
87
  /** Lazy-built SessionStore adapter that mirrors SDK transcripts to SQLite. */
105
88
  private _sessionStore;
106
89
  /** Hot correction buffer — explicit behavioral corrections applied before nightly SI. */
@@ -109,11 +92,6 @@ export declare class PersonalAssistant {
109
92
  private initPromptWatchers;
110
93
  /** Log SDK result metrics and store usage. Shared across all query methods. */
111
94
  private logQueryResult;
112
- /** Capture MCP server status from system init messages. */
113
- private captureMcpStatus;
114
- setUnleashedCompleteCallback(cb: (jobName: string, result: string) => void): void;
115
- setPhaseCompleteCallback(cb: (jobName: string, phase: number, totalPhases: number, output: string) => void): void;
116
- setPhaseProgressCallback(cb: (jobName: string, phase: number, summary: string) => void): void;
117
95
  setSkillProposedCallback(cb: (skill: import('../types.js').SkillDocument) => void): void;
118
96
  getMcpStatus(): {
119
97
  servers: Array<{
@@ -153,45 +131,6 @@ export declare class PersonalAssistant {
153
131
  getMemoryChunkCount(): number;
154
132
  private buildSystemPrompt;
155
133
  private buildOptions;
156
- private retrieveContext;
157
- /** Cached active goals — avoids N file reads per query. Refreshes every 30s. */
158
- private _goalCache;
159
- private static readonly GOAL_CACHE_TTL_MS;
160
- private loadGoalsFromCache;
161
- /**
162
- * Match a user message against active goals by keyword overlap.
163
- * Returns formatted goal status block for injection into system prompt,
164
- * or empty string if no goals match.
165
- */
166
- private matchGoals;
167
- /**
168
- * Compact always-on block of active goals. Used when no keyword match
169
- * fires so the agent still sees what it's supposed to be working on.
170
- * Scoped: for agent sessions, includes that agent's goals plus any
171
- * clementine-owned goals it might contribute to.
172
- */
173
- private formatActiveGoalsBlock;
174
- chat(text: string, sessionKey?: string | null, options?: {
175
- onText?: OnTextCallback;
176
- onToolActivity?: OnToolActivityCallback;
177
- model?: string;
178
- maxTurns?: number;
179
- profile?: AgentProfile;
180
- securityAnnotation?: string;
181
- projectOverride?: ProjectMeta;
182
- verboseLevel?: VerboseLevel;
183
- abortController?: AbortController;
184
- toolset?: ToolsetName;
185
- }): Promise<[string, string]>;
186
- /**
187
- * Compare retrieved chunks against the response text and record which
188
- * were referenced. Uses a distinctive-token overlap heuristic — cheap,
189
- * deterministic, no extra LLM calls. Called right after a turn completes.
190
- */
191
- private scoreRetrievalOutcomes;
192
- private static readonly RATE_LIMIT_MAX_RETRIES;
193
- private static readonly RATE_LIMIT_BACKOFF;
194
- private runQuery;
195
134
  /**
196
135
  * Compact a session's context when nearing the context window limit.
197
136
  *
@@ -208,11 +147,6 @@ export declare class PersonalAssistant {
208
147
  summary?: string;
209
148
  reason: string;
210
149
  };
211
- /**
212
- * Expire sessions inactive for more than 24 hours.
213
- * Called periodically from chat() to prevent unbounded map growth.
214
- */
215
- private expireOldSessions;
216
150
  /**
217
151
  * Build an instant local summary from in-memory exchange history.
218
152
  * No LLM call — returns immediately. Used during session rotation
@@ -227,36 +161,10 @@ export declare class PersonalAssistant {
227
161
  * (which represent in-flight messages with no reply yet).
228
162
  */
229
163
  private pairTranscriptTurns;
230
- /**
231
- * Build a short summary of older turns (older than what's already cached
232
- * in `lastExchanges`) for restart-restore prompt injection. Returns ''
233
- * if there's nothing older or no memory store. Capped at 600 chars.
234
- */
235
- private buildOlderTurnsContext;
236
- /**
237
- * Reconstruct context after the SDK reports the session is dead
238
- * ("no conversation found"). Pulls last N turns from the transcripts
239
- * table (hydrating `lastExchanges` if the cache is too thin) and
240
- * builds a recovery prefix that gets prepended to the retry prompt.
241
- * Mirrors the buildContextRecoveredPrompt pattern used by autocompact.
242
- */
243
- private buildSessionDeathRecoveryPrompt;
244
- /**
245
- * Auto-save a lightweight handoff file when a session rotates.
246
- * Uses in-memory exchange history — no LLM call.
247
- */
248
- private saveAutoHandoff;
249
- /**
250
- * Load a handoff file for a session if one exists.
251
- * Returns formatted context string or empty string.
252
- */
253
- private loadHandoff;
254
164
  /**
255
165
  * Run an LLM summary in the background and save to memoryStore.
256
166
  * Does not block the caller — for future retrieval context only.
257
167
  */
258
- private summarizeSessionAsync;
259
- private summarizeSession;
260
168
  /**
261
169
  * Parse a structured checkpoint from an unleashed phase's STATUS SUMMARY output.
262
170
  * Returns null if no recognizable structure is found.
@@ -270,10 +178,6 @@ export declare class PersonalAssistant {
270
178
  } | null;
271
179
  /** Fire-and-forget: extract a reusable skill from a successful execution. */
272
180
  private extractSkillFromExecution;
273
- private preRotationFlush;
274
- private lastExtractionTimes;
275
- private memoryExtractionKey;
276
- private assessMemoryExtraction;
277
181
  private logMemoryExtractionSkip;
278
182
  /**
279
183
  * Public entry point for triggering auto-memory extraction after an
@@ -298,7 +202,6 @@ export declare class PersonalAssistant {
298
202
  private spawnMemoryExtraction;
299
203
  private static readonly MEMORY_TOOL_NAMES;
300
204
  private extractMemory;
301
- heartbeat(standingInstructions: string, changesSummary?: string, timeContext?: string, dedupContext?: string, profile?: AgentProfile | null): Promise<string>;
302
205
  runPlanStep(stepId: string, prompt: string, opts?: {
303
206
  tier?: number;
304
207
  maxTurns?: number;
@@ -315,9 +218,6 @@ export declare class PersonalAssistant {
315
218
  usageLabel?: string;
316
219
  usageAgentSlug?: string;
317
220
  }): Promise<string>;
318
- runCronJob(jobName: string, jobPrompt: string, tier?: number, maxTurns?: number, model?: string, workDir?: string, timeoutMs?: number, successCriteria?: string[], agentSlug?: string, opts?: {
319
- disableAllTools?: boolean;
320
- }): Promise<string>;
321
221
  /**
322
222
  * Goal-backward verification pass using Haiku after cron job execution.
323
223
  * Instead of vague quality ratings, verifies actual outcomes:
@@ -326,15 +226,6 @@ export declare class PersonalAssistant {
326
226
  * 3. Does it connect to the goal / produce actionable results? (wired)
327
227
  */
328
228
  private runCronReflection;
329
- runUnleashedTask(jobName: string, jobPrompt: string, tier?: number, maxTurns?: number, model?: string, workDir?: string, maxHours?: number, agentSlug?: string): Promise<string>;
330
- /**
331
- * Run a team message as an unleashed-style autonomous task.
332
- * Gives team agents the same multi-phase execution as cron jobs,
333
- * instead of being killed by the 5-minute interactive chat timeout.
334
- *
335
- * @param onText Streaming callback for real-time progress updates
336
- */
337
- runTeamTask(fromName: string, fromSlug: string, content: string, profile: AgentProfile, onText?: (token: string) => void, externalAbortController?: AbortController): Promise<string>;
338
229
  /**
339
230
  * Inject a user/assistant exchange into a session's context without running
340
231
  * a query. Used to give the DM session visibility of cron/heartbeat outputs