clementine-agent 1.18.3 → 1.18.5

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.
@@ -25,6 +25,8 @@ const SPECS = [
25
25
  { key: 'HAIKU_MODEL', group: 'models', jsonPath: 'models.haiku', default: 'claude-haiku-4-5-20251001' },
26
26
  { key: 'SONNET_MODEL', group: 'models', jsonPath: 'models.sonnet', default: 'claude-sonnet-4-6' },
27
27
  { key: 'OPUS_MODEL', group: 'models', jsonPath: 'models.opus', default: 'claude-opus-4-7' },
28
+ // Team routing
29
+ { key: 'AUTO_DELEGATE_ENABLED', group: 'team', default: false },
28
30
  // Budgets
29
31
  { key: 'BUDGET_HEARTBEAT_USD', group: 'budgets', jsonPath: 'budgets.heartbeat', default: 0.50 },
30
32
  { key: 'BUDGET_CRON_T1_USD', group: 'budgets', jsonPath: 'budgets.cronT1', default: 2.00 },
package/dist/config.d.ts CHANGED
@@ -84,6 +84,7 @@ export declare const TASK_BUDGET_TOKENS: {
84
84
  };
85
85
  export declare const DEFAULT_MODEL_TIER: keyof Models;
86
86
  export declare const MODEL: string;
87
+ export declare const AUTO_DELEGATE_ENABLED: boolean;
87
88
  export declare const DISCORD_TOKEN: string;
88
89
  export declare const DISCORD_OWNER_ID: string;
89
90
  export declare const DISCORD_WATCHED_CHANNELS: string[];
package/dist/config.js CHANGED
@@ -27,6 +27,32 @@ function readEnvFile() {
27
27
  return parseEnvText(readFileSync(envPath, 'utf-8'));
28
28
  }
29
29
  const env = readEnvFile();
30
+ // ── Claude Code CLI runtime env propagation ─────────────────────────
31
+ //
32
+ // The Claude Code CLI binary bundled inside @anthropic-ai/claude-agent-sdk
33
+ // auto-attaches the `context-1m-2025-08-07` beta header for any model that
34
+ // supports a 1M context (Sonnet 4.6, Opus 4.6/4.7, etc.). On accounts
35
+ // without the "extra usage" entitlement, every API call then fails with
36
+ // "Extra usage is required for 1M context."
37
+ //
38
+ // We don't ask for 1M anywhere in our code, but the CLI does on its own.
39
+ // The CLI honors CLAUDE_CODE_DISABLE_1M_CONTEXT — when truthy, the auto-
40
+ // enable path is skipped and the standard 200K context is used.
41
+ //
42
+ // Default to disabled here so users without extra usage stop hitting the
43
+ // gate. Anyone who has paid for / been entitled to 1M can opt back in by
44
+ // setting CLAUDE_CODE_DISABLE_1M_CONTEXT=0 in their .env.
45
+ {
46
+ const userPref = env['CLAUDE_CODE_DISABLE_1M_CONTEXT'] ?? process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT;
47
+ if (userPref === undefined || userPref === '') {
48
+ process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
49
+ }
50
+ else {
51
+ // Propagate the user's explicit choice from .env into process.env so the
52
+ // spawned CLI subprocess inherits it.
53
+ process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT = userPref;
54
+ }
55
+ }
30
56
  // ── Keychain-ref resolution (lazy, cached) ──────────────────────────
31
57
  //
32
58
  // `.env` may store keychain stubs ("keychain:clementine-agent-FOO") in place
@@ -236,6 +262,13 @@ export const TASK_BUDGET_TOKENS = {
236
262
  };
237
263
  export const DEFAULT_MODEL_TIER = (getEnvOrJson('DEFAULT_MODEL_TIER', json.models?.default, 'sonnet'));
238
264
  export const MODEL = MODELS[DEFAULT_MODEL_TIER] ?? MODELS.sonnet;
265
+ // ── Team routing ─────────────────────────────────────────────────────
266
+ // When false (default), the gateway never auto-delegates a Clementine-
267
+ // owned message to a sub-agent without explicit user opt-in. High-
268
+ // confidence routing is demoted to a soft-suggest so the user can
269
+ // confirm ("send to Ross") or override before any handoff happens.
270
+ // Flip via dashboard or `clementine config set AUTO_DELEGATE_ENABLED true`.
271
+ export const AUTO_DELEGATE_ENABLED = getEnv('AUTO_DELEGATE_ENABLED', 'false').toLowerCase() === 'true';
239
272
  // ── Discord ──────────────────────────────────────────────────────────
240
273
  export const DISCORD_TOKEN = getSecret('DISCORD_TOKEN');
241
274
  export const DISCORD_OWNER_ID = getEnv('DISCORD_OWNER_ID', '0');
@@ -10,7 +10,7 @@ import pino from 'pino';
10
10
  import { PersonalAssistant } from '../agent/assistant.js';
11
11
  import { runWithTrace, logAuditJsonl } from '../agent/hooks.js';
12
12
  import { SelfImproveLoop } from '../agent/self-improve.js';
13
- import { MODELS, AGENTS_DIR, TEAM_COMMS_LOG, BASE_DIR, SEEN_CHANNELS_FILE } from '../config.js';
13
+ import { MODELS, AGENTS_DIR, TEAM_COMMS_LOG, BASE_DIR, SEEN_CHANNELS_FILE, AUTO_DELEGATE_ENABLED } from '../config.js';
14
14
  import { scanner } from '../security/scanner.js';
15
15
  import { lanes } from './lanes.js';
16
16
  import { AgentManager } from '../agent/agent-manager.js';
@@ -255,8 +255,10 @@ export class Gateway {
255
255
  const targetProfile = agents.find(a => a.slug === decision.targetAgent);
256
256
  if (!targetProfile)
257
257
  return null;
258
- // Auto-delegate at high confidence
259
- if (decision.confidence >= 0.8) {
258
+ // Auto-delegate at high confidence — only when the user has explicitly
259
+ // opted in via AUTO_DELEGATE_ENABLED. Default behavior is to demote
260
+ // every routing to a soft-suggest so the user controls every handoff.
261
+ if (decision.confidence >= 0.8 && AUTO_DELEGATE_ENABLED) {
260
262
  // Fire the team task in the background; ack immediately.
261
263
  const ackMessage = `Routing this to **${targetProfile.name}** (${decision.reasoning.toLowerCase()}). I'll post their response back here when done.`;
262
264
  onText?.(ackMessage).catch(() => { });
@@ -266,7 +268,32 @@ export class Gateway {
266
268
  if (!sess.teamTaskControllers)
267
269
  sess.teamTaskControllers = new Set();
268
270
  sess.teamTaskControllers.add(teamAbortController);
269
- this.handleTeamTask('Clementine', 'clementine', text, targetProfile, undefined, teamAbortController)
271
+ // Progress visibility — without this the channel goes dark from
272
+ // ack to final result, which can be many minutes on research-style
273
+ // tasks. We batch the delegated agent's tool announcements and
274
+ // flush every PROGRESS_INTERVAL_MS so the user sees what's
275
+ // happening without a token firehose.
276
+ const recentTools = [];
277
+ let progressTimer;
278
+ const PROGRESS_INTERVAL_MS = 30_000;
279
+ const dispatcher = this._dispatcher;
280
+ const flushProgress = () => {
281
+ progressTimer = undefined;
282
+ if (recentTools.length === 0)
283
+ return;
284
+ const tools = recentTools.slice(-5).join(', ');
285
+ recentTools.length = 0;
286
+ void dispatcher?.send(`_${targetProfile.name} is working — recent actions: ${tools}_`, { sessionKey, agentSlug: targetProfile.slug });
287
+ };
288
+ const onTeamProgress = (chunk) => {
289
+ const m = chunk.match(/\[using ([^\]]+?)\.\.\.\]/);
290
+ if (m && m[1])
291
+ recentTools.push(m[1]);
292
+ if (!progressTimer && recentTools.length > 0) {
293
+ progressTimer = setTimeout(flushProgress, PROGRESS_INTERVAL_MS);
294
+ }
295
+ };
296
+ this.handleTeamTask('Clementine', 'clementine', text, targetProfile, onTeamProgress, teamAbortController)
270
297
  .then(response => {
271
298
  if (!response)
272
299
  return;
@@ -282,6 +309,10 @@ export class Gateway {
282
309
  void this._dispatcher?.send(`**${targetProfile.name}** hit an error handling that: ${String(err).slice(0, 200)}`, { sessionKey, agentSlug: targetProfile.slug });
283
310
  })
284
311
  .finally(() => {
312
+ if (progressTimer) {
313
+ clearTimeout(progressTimer);
314
+ progressTimer = undefined;
315
+ }
285
316
  const s = this.sessions.get(sessionKey);
286
317
  s?.teamTaskControllers?.delete(teamAbortController);
287
318
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.3",
3
+ "version": "1.18.5",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",