clementine-agent 1.18.209 → 1.18.211

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 (55) hide show
  1. package/dist/agent/agent-definitions.js +10 -10
  2. package/dist/agent/approval-signals.d.ts +2 -2
  3. package/dist/agent/approval-signals.js +1 -1
  4. package/dist/agent/assistant.js +34 -3
  5. package/dist/agent/bg-planner.d.ts +3 -4
  6. package/dist/agent/bg-planner.js +4 -5
  7. package/dist/agent/claim-verification.d.ts +1 -1
  8. package/dist/agent/claim-verification.js +1 -1
  9. package/dist/agent/clarification-gate.d.ts +116 -0
  10. package/dist/agent/clarification-gate.js +402 -0
  11. package/dist/agent/clementine-turn-context.d.ts +1 -1
  12. package/dist/agent/clementine-turn-context.js +8 -3
  13. package/dist/agent/complex-task-detector.d.ts +7 -12
  14. package/dist/agent/complex-task-detector.js +70 -26
  15. package/dist/agent/execution-policy.d.ts +23 -0
  16. package/dist/agent/execution-policy.js +36 -0
  17. package/dist/agent/intent-classifier.d.ts +1 -1
  18. package/dist/agent/precondition-guard.d.ts +61 -0
  19. package/dist/agent/precondition-guard.js +88 -0
  20. package/dist/agent/project-resolver.d.ts +3 -3
  21. package/dist/agent/project-resolver.js +7 -7
  22. package/dist/agent/role-scaffolds.d.ts +1 -1
  23. package/dist/agent/role-scaffolds.js +1 -1
  24. package/dist/agent/run-agent-context.js +4 -5
  25. package/dist/agent/run-agent-cron.d.ts +2 -3
  26. package/dist/agent/run-agent-cron.js +7 -3
  27. package/dist/agent/run-agent-heartbeat.js +6 -2
  28. package/dist/agent/run-agent-mcp.d.ts +1 -2
  29. package/dist/agent/run-agent-mcp.js +29 -5
  30. package/dist/agent/run-agent-team-task.js +6 -3
  31. package/dist/agent/run-agent.d.ts +8 -0
  32. package/dist/agent/run-agent.js +30 -2
  33. package/dist/agent/schedule-registry.d.ts +2 -2
  34. package/dist/agent/skill-suppressions.d.ts +1 -1
  35. package/dist/agent/skill-suppressions.js +1 -1
  36. package/dist/agent/turn-policy.js +3 -3
  37. package/dist/channels/discord-agent-bot.js +2 -12
  38. package/dist/channels/discord-utils.d.ts +2 -0
  39. package/dist/channels/discord-utils.js +32 -0
  40. package/dist/channels/discord.js +2 -12
  41. package/dist/cli/dashboard.js +7 -6
  42. package/dist/config.js +1 -1
  43. package/dist/gateway/failure-monitor.js +9 -2
  44. package/dist/gateway/heartbeat-scheduler.js +1 -1
  45. package/dist/gateway/router.d.ts +1 -0
  46. package/dist/gateway/router.js +130 -30
  47. package/dist/integrations/composio/client.d.ts +6 -0
  48. package/dist/integrations/composio/client.js +117 -10
  49. package/dist/memory/store.js +2 -2
  50. package/dist/tools/admin-tools.js +2 -2
  51. package/dist/tools/project-tools.d.ts +1 -1
  52. package/dist/tools/project-tools.js +68 -17
  53. package/dist/tools/schedule-tools.js +1 -1
  54. package/dist/vault-migrations/0002-add-agentic-communication.js +1 -1
  55. package/package.json +1 -1
@@ -141,15 +141,15 @@ function buildHiredAgentDescription(p) {
141
141
  'Spawn this subagent when the user names them, asks a question in their domain, or asks Clementine to "have <name> do X".',
142
142
  ].filter(Boolean).join(' ');
143
143
  }
144
- /** Map a hired-agent profile to an AgentDefinition.
145
- * Used when Clementine wants to delegate to Ross/Sasha/Nora etc. */
144
+ /** Map a hired-agent profile to an AgentDefinition. */
146
145
  function profileToAgentDefinition(p) {
147
- // Always include `Agent` so the subagent can further fan out, plus
148
- // core read tools as a baseline. profile.team.allowedTools narrows
149
- // beyond this when set.
150
- const baseline = ['Agent', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch', 'TodoWrite'];
146
+ // Hired-agent definitions are leaf subagents by default. The Claude
147
+ // Agent SDK does not support recursive subagent spawning via `Agent`
148
+ // inside subagent tool lists; if Clementine grows nested orchestration,
149
+ // it should be explicit and depth-limited rather than accidental.
150
+ const baseline = ['Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch', 'TodoWrite'];
151
151
  const tools = p.team?.allowedTools?.length
152
- ? Array.from(new Set(['Agent', ...p.team.allowedTools]))
152
+ ? Array.from(new Set(p.team.allowedTools.filter(tool => tool !== 'Agent')))
153
153
  : baseline;
154
154
  return {
155
155
  description: buildHiredAgentDescription(p),
@@ -198,10 +198,10 @@ export function buildAgentMap(opts = {}) {
198
198
  // 1.18.198 — NO `tools` allowlist. Researcher inherits every tool the
199
199
  // parent has access to (Bash, Read, MCP wildcards, etc.). The earlier
200
200
  // hardcoded ['Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'] blocked
201
- // researcher from using the parent's MCP servers when Ross dispatched
202
- // "Parallel SEO enrichment for 13 domains" the subagent couldn't call
201
+ // researcher from using the parent's MCP servers. A delegated
202
+ // "Parallel SEO enrichment for 13 domains" subagent couldn't call
203
203
  // `mcp__dataforseo__*` because it wasn't in the allowlist. Result: the
204
- // subagent said "I can't do that" and Ross fell back to running 25
204
+ // subagent said "I can't do that" and the parent fell back to running 25
205
205
  // sequential MCP calls in his own turn, defeating the fan-out.
206
206
  //
207
207
  // Read-only behavior is enforced in RESEARCHER_PROMPT (behavior class:
@@ -13,7 +13,7 @@
13
13
  * ## Owner approval signals (recent)
14
14
  * APPROVED (do more like this):
15
15
  * - cron/insight-check: "Apply lean mode to reduce prompt size"
16
- * - agent/sasha-the-cmo: "Add explicit citation requirement to system prompt"
16
+ * - agent/marketing-agent: "Add explicit citation requirement to system prompt"
17
17
  *
18
18
  * DENIED (avoid these patterns):
19
19
  * - workflow/email-gen: "Replace template with LLM generation" ← user note: "too generic; loses voice"
@@ -31,7 +31,7 @@ export interface ApprovalSignal {
31
31
  experimentId: string;
32
32
  /** The area the proposal targeted (cron, agent, skill, soul, etc.). */
33
33
  area: string;
34
- /** The specific target (e.g., "insight-check", "sasha-the-cmo"). */
34
+ /** The specific target (e.g., "insight-check", "marketing-agent"). */
35
35
  target: string;
36
36
  /** The proposal's one-sentence hypothesis (truncated to 200 chars). */
37
37
  hypothesis: string;
@@ -13,7 +13,7 @@
13
13
  * ## Owner approval signals (recent)
14
14
  * APPROVED (do more like this):
15
15
  * - cron/insight-check: "Apply lean mode to reduce prompt size"
16
- * - agent/sasha-the-cmo: "Add explicit citation requirement to system prompt"
16
+ * - agent/marketing-agent: "Add explicit citation requirement to system prompt"
17
17
  *
18
18
  * DENIED (avoid these patterns):
19
19
  * - workflow/email-gen: "Replace template with LLM generation" ← user note: "too generic; loses voice"
@@ -262,6 +262,9 @@ const TOOLS_SERVER = `${ASSISTANT_NAME.toLowerCase()}-tools`;
262
262
  function mcpTool(name) {
263
263
  return `mcp__${TOOLS_SERVER}__${name}`;
264
264
  }
265
+ function mcpServerWildcard(serverName) {
266
+ return `mcp__${serverName}__*`;
267
+ }
265
268
  const CLEMENTINE_CORE_TOOL_NAMES = [
266
269
  'working_memory',
267
270
  'user_model',
@@ -1748,6 +1751,31 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1748
1751
  fullSurface: false,
1749
1752
  };
1750
1753
  }
1754
+ const profileComposioAllowList = profile?.allowedComposioToolkits;
1755
+ if (!toolsDisabledForCall
1756
+ && !isPlanStep
1757
+ && !toolRoute.fullSurface
1758
+ && Array.isArray(toolRoute.composioToolkits)
1759
+ && !Array.isArray(profileComposioAllowList)) {
1760
+ try {
1761
+ const { listConnectedToolkits, matchConnectedToolkitsInText } = await import('../integrations/composio/client.js');
1762
+ const composioRoutingText = [
1763
+ directScopeText,
1764
+ allowContextToolRoute ? contextRoutingText : '',
1765
+ ].filter(Boolean).join('\n');
1766
+ const mentioned = matchConnectedToolkitsInText(composioRoutingText, await listConnectedToolkits());
1767
+ if (mentioned.length > 0) {
1768
+ toolRoute = {
1769
+ ...toolRoute,
1770
+ composioToolkits: [...new Set([...toolRoute.composioToolkits, ...mentioned])],
1771
+ reason: 'matched',
1772
+ };
1773
+ }
1774
+ }
1775
+ catch (err) {
1776
+ logger.debug({ err }, 'Connected Composio toolkit text match failed (non-fatal)');
1777
+ }
1778
+ }
1751
1779
  let allowedTools = [];
1752
1780
  const addAllowed = (...tools) => {
1753
1781
  for (const tool of tools) {
@@ -1944,9 +1972,8 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1944
1972
  if (!toolsDisabledForCall && !isPlanStep) {
1945
1973
  try {
1946
1974
  const { buildComposioMcpServers } = await import('../integrations/composio/mcp-bridge.js');
1947
- const profileAllowList = profile?.allowedComposioToolkits;
1948
- const allowList = Array.isArray(profileAllowList)
1949
- ? profileAllowList
1975
+ const allowList = Array.isArray(profileComposioAllowList)
1976
+ ? profileComposioAllowList
1950
1977
  : toolRoute.composioToolkits;
1951
1978
  composioMcpServers = await buildComposioMcpServers(allowList);
1952
1979
  }
@@ -1956,6 +1983,10 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1956
1983
  }
1957
1984
  }
1958
1985
  const composioConnectedSlugs = Object.keys(composioMcpServers);
1986
+ if (!toolsDisabledForCall) {
1987
+ for (const slug of composioConnectedSlugs)
1988
+ addAllowed(mcpServerWildcard(slug));
1989
+ }
1959
1990
  const { stable, volatile: volatilePromptPart } = this.buildSystemPrompt({
1960
1991
  isHeartbeat, cronTier: isPlanStep ? null : cronTier, retrievalContext, profile, sessionKey, model: resolvedModel, verboseLevel, intentClassification,
1961
1992
  contextTier: turnPolicy?.retrievalTier ?? (retrievalContext ? 'full' : 'core'),
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Why this exists (1.18.190)
6
6
  * ──────────────────────────
7
- * Before this, a complex multi-step user ask ("find the coaches project,
7
+ * Before this, a complex multi-step user ask ("find the catalog project,
8
8
  * build me an HTML report, deploy it to Netlify, verify the URL") got
9
9
  * handed to a single monolithic bg-task worker. The worker had its own
10
10
  * 200K context but still autocompact-thrashed because:
@@ -36,15 +36,14 @@
36
36
  * a multi-domain ask into proper steps is not mechanical.
37
37
  *
38
38
  * If you're tempted to "save tokens" by flipping this to Haiku, read
39
- * the 2026-05-12 root-cause plan first
40
- * (~/.claude/plans/look-at-the-last-vivid-rossum.md). The whole point
39
+ * a recent root-cause plan first. The whole point
41
40
  * of this ship is to NOT cut corners on the decomposition layer.
42
41
  */
43
42
  import type { ProjectMeta } from './assistant.js';
44
43
  export interface PlanStep {
45
44
  /** 0-indexed position. */
46
45
  index: number;
47
- /** Short imperative title (e.g., "Find the coaches project"). */
46
+ /** Short imperative title (e.g., "Find the catalog project"). */
48
47
  title: string;
49
48
  /** What this step does, in 1-2 sentences. The chained worker sees this. */
50
49
  scope: string;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Why this exists (1.18.190)
6
6
  * ──────────────────────────
7
- * Before this, a complex multi-step user ask ("find the coaches project,
7
+ * Before this, a complex multi-step user ask ("find the catalog project,
8
8
  * build me an HTML report, deploy it to Netlify, verify the URL") got
9
9
  * handed to a single monolithic bg-task worker. The worker had its own
10
10
  * 200K context but still autocompact-thrashed because:
@@ -36,8 +36,7 @@
36
36
  * a multi-domain ask into proper steps is not mechanical.
37
37
  *
38
38
  * If you're tempted to "save tokens" by flipping this to Haiku, read
39
- * the 2026-05-12 root-cause plan first
40
- * (~/.claude/plans/look-at-the-last-vivid-rossum.md). The whole point
39
+ * a recent root-cause plan first. The whole point
41
40
  * of this ship is to NOT cut corners on the decomposition layer.
42
41
  */
43
42
  import fs from 'node:fs';
@@ -202,7 +201,7 @@ function buildPlannerSystemPrompt() {
202
201
  '{',
203
202
  ' "steps": [',
204
203
  ' {',
205
- ' "title": "<short imperative title, e.g. \'Find the coaches project\'>",',
204
+ ' "title": "<short imperative title, e.g. \'Find the catalog project\'>",',
206
205
  ' "scope": "<1-2 sentences describing exactly what this step does>",',
207
206
  ' "expectedTools": ["tool_name_1", "tool_name_2"],',
208
207
  ' "deliverable": "<file path | URL | description of the artifact>"',
@@ -273,7 +272,7 @@ async function runPlannerLlm(userPrompt, systemPrompt, model) {
273
272
  // Raw `systemPrompt: string` tells the SDK to use API-key auth, which
274
273
  // 99% of installs don't have configured — they're logged into Claude
275
274
  // Code, not the Anthropic API. This was the "Not logged in · Please
276
- // run /login" failure Ross's owner hit on 2026-05-12.
275
+ // run /login" provider-auth failure.
277
276
  //
278
277
  // The preset injects Claude Code's default system prompt; our planning
279
278
  // instructions go in `append` and dominate behavior for the single
@@ -5,7 +5,7 @@
5
5
  * Why this exists (1.18.187)
6
6
  * ──────────────────────────
7
7
  * On 2026-05-11 a bg task was diagnosed where Clementine said "The
8
- * site is live again at https://X.netlify.app — all 100 coaches with
8
+ * site is live again at https://X.netlify.app — all 100 product records with
9
9
  * search/filter/sort intact" — but the live URL returned HTTP 404,
10
10
  * and the run had zero tool calls matching a deploy. She had
11
11
  * confabulated success from a recall summary of a PRIOR task.
@@ -5,7 +5,7 @@
5
5
  * Why this exists (1.18.187)
6
6
  * ──────────────────────────
7
7
  * On 2026-05-11 a bg task was diagnosed where Clementine said "The
8
- * site is live again at https://X.netlify.app — all 100 coaches with
8
+ * site is live again at https://X.netlify.app — all 100 product records with
9
9
  * search/filter/sort intact" — but the live URL returned HTTP 404,
10
10
  * and the run had zero tool calls matching a deploy. She had
11
11
  * confabulated success from a recall summary of a PRIOR task.
@@ -0,0 +1,116 @@
1
+ import type { RunSummary, SideEffectCall } from './run-summary.js';
2
+ export type PendingAgentDecisionKind = 'blocked_external_action';
3
+ export type BlockedActionCategory = 'deployment_target_missing';
4
+ export interface PendingAgentDecision {
5
+ id: string;
6
+ kind: PendingAgentDecisionKind;
7
+ createdAt: number;
8
+ expiresAt: number;
9
+ runIds: string[];
10
+ originalRequest: string;
11
+ question: string;
12
+ context: {
13
+ category: BlockedActionCategory;
14
+ classifierId: string;
15
+ provider: string;
16
+ providerLabel: string;
17
+ blockerSummary: string;
18
+ failedCommand: string;
19
+ error: string;
20
+ targetNoun: string;
21
+ targetPlaceholder: string;
22
+ createInstructions: string[];
23
+ existingInstructions: string[];
24
+ projectPath?: string;
25
+ agentId?: string;
26
+ completedSideEffects?: string[];
27
+ };
28
+ }
29
+ export type AgentDecisionReply = {
30
+ kind: 'answer';
31
+ action: 'create_new_target';
32
+ } | {
33
+ kind: 'answer';
34
+ action: 'use_existing_target';
35
+ target: string;
36
+ } | {
37
+ kind: 'cancel';
38
+ } | {
39
+ kind: 'unclear';
40
+ message: string;
41
+ };
42
+ export interface BlockedActionClassifier {
43
+ id: string;
44
+ category: BlockedActionCategory;
45
+ provider: string;
46
+ providerLabel: string;
47
+ targetNoun: string;
48
+ targetPlaceholder: string;
49
+ defaultCommand: string;
50
+ defaultError: string;
51
+ blockerSummary: string;
52
+ matches(call: SideEffectCall): boolean;
53
+ createInstructions: string[];
54
+ existingInstructions: string[];
55
+ }
56
+ /**
57
+ * Extension point for install-specific tool/provider blockers. Core owns the
58
+ * state machine; providers own only small classifiers and resume instructions.
59
+ */
60
+ export declare function registerBlockedActionClassifier(classifier: BlockedActionClassifier): () => void;
61
+ export interface PreconditionEnv {
62
+ /** Working directory the agent will use for this tool call. */
63
+ cwd: string;
64
+ /** Active project path if the router resolved one. */
65
+ activeProjectPath?: string;
66
+ /** filesystem.existsSync injection point for testability. */
67
+ existsSync?: (path: string) => boolean;
68
+ }
69
+ export interface PreconditionMatch {
70
+ /** The command or tool action that would have run (for owner display). */
71
+ attemptedCommand: string;
72
+ /** The reason the precondition failed (for owner display). */
73
+ reason: string;
74
+ /** Optional project path inferred from the input (e.g. `cd /path && netlify deploy`). */
75
+ projectPath?: string;
76
+ }
77
+ export interface PreconditionClassifier {
78
+ id: string;
79
+ category: BlockedActionCategory;
80
+ provider: string;
81
+ providerLabel: string;
82
+ targetNoun: string;
83
+ targetPlaceholder: string;
84
+ blockerSummary: string;
85
+ /** Tool names this classifier inspects. e.g. ['Bash'] or
86
+ * ['mcp__clementine-tools__project_deploy']. Wildcard `'*'` not supported —
87
+ * classifiers should be narrow on purpose. */
88
+ toolNames: readonly string[];
89
+ /** Returns a PreconditionMatch when the call should be blocked, or null
90
+ * to let it proceed. Errors thrown here are caught and treated as a pass
91
+ * (fail-open) — the post-hoc classifier still catches the failure. */
92
+ matchesPreconditions(input: Record<string, unknown>, env: PreconditionEnv): PreconditionMatch | null;
93
+ createInstructions: string[];
94
+ existingInstructions: string[];
95
+ }
96
+ /** Extension point for pre-call classifiers. Mirrors registerBlockedActionClassifier. */
97
+ export declare function registerPreconditionClassifier(classifier: PreconditionClassifier): () => void;
98
+ export declare function buildBlockedActionDecisionFromRunSummary(summary: RunSummary, originalRequest: string, nowMs?: number): PendingAgentDecision | null;
99
+ export declare function parseAgentDecisionReply(decision: PendingAgentDecision, message: string): AgentDecisionReply;
100
+ export declare function buildAgentDecisionContinuationPrompt(decision: PendingAgentDecision, reply: Extract<AgentDecisionReply, {
101
+ kind: 'answer';
102
+ }>): string;
103
+ export declare const buildRepairDecisionFromRunSummary: typeof buildBlockedActionDecisionFromRunSummary;
104
+ /**
105
+ * Run all registered precondition classifiers against an attempted tool
106
+ * call. Returns the first PendingAgentDecision that matches, or null if
107
+ * the call should proceed. Errors inside individual classifiers are
108
+ * swallowed (fail-open) — the post-hoc classifier remains as the safety
109
+ * net for any case the pre-call rule mishandles.
110
+ */
111
+ export declare function evaluatePreconditionsForToolCall(toolName: string, input: Record<string, unknown>, env: PreconditionEnv, opts?: {
112
+ originalRequest?: string;
113
+ runId?: string;
114
+ nowMs?: number;
115
+ }): PendingAgentDecision | null;
116
+ //# sourceMappingURL=clarification-gate.d.ts.map