clementine-agent 1.0.82 → 1.0.83

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.
@@ -1122,7 +1122,7 @@ export async function startDiscord(gateway, heartbeat, cronScheduler, dispatcher
1122
1122
  const streamer = new DiscordStreamingMessage(message.channel);
1123
1123
  await streamer.start();
1124
1124
  try {
1125
- const response = await gateway.handleMessage(sessionKey, effectiveText, (t) => streamer.update(t), oneOffModel, oneOffMaxTurns, (toolName, toolInput) => { streamer.setToolStatus(friendlyToolName(toolName, toolInput)); return Promise.resolve(); });
1125
+ const response = await gateway.handleMessage(sessionKey, effectiveText, (t) => streamer.update(t), oneOffModel, oneOffMaxTurns, (toolName, toolInput) => { streamer.setToolStatus(friendlyToolName(toolName, toolInput)); return Promise.resolve(); }, (status) => { streamer.setToolStatus(status); return Promise.resolve(); });
1126
1126
  await streamer.finalize(response);
1127
1127
  updatePresence(sessionKey);
1128
1128
  // Track bot message for feedback reactions
@@ -5,7 +5,7 @@
5
5
  * Manages per-user/channel sessions for conversation continuity.
6
6
  */
7
7
  import { PersonalAssistant, type ProjectMeta } from '../agent/assistant.js';
8
- import type { OnTextCallback, OnToolActivityCallback, PlanProgressUpdate, PlanStep, SelfImproveConfig, SelfImproveExperiment, SessionProvenance, TeamMessage, VerboseLevel, WorkflowDefinition } from '../types.js';
8
+ import type { OnProgressCallback, OnTextCallback, OnToolActivityCallback, PlanProgressUpdate, PlanStep, SelfImproveConfig, SelfImproveExperiment, SessionProvenance, TeamMessage, VerboseLevel, WorkflowDefinition } from '../types.js';
9
9
  import { AgentManager } from '../agent/agent-manager.js';
10
10
  import { TeamRouter } from '../agent/team-router.js';
11
11
  import { TeamBus } from '../agent/team-bus.js';
@@ -149,7 +149,7 @@ export declare class Gateway {
149
149
  * or correct the agent mid-response instead of queuing behind a long query.
150
150
  */
151
151
  private acquireSessionLock;
152
- handleMessage(sessionKey: string, text: string, onText?: OnTextCallback, model?: string, maxTurns?: number, onToolActivity?: OnToolActivityCallback): Promise<string>;
152
+ handleMessage(sessionKey: string, text: string, onText?: OnTextCallback, model?: string, maxTurns?: number, onToolActivity?: OnToolActivityCallback, onProgress?: OnProgressCallback): Promise<string>;
153
153
  private _handleMessageInner;
154
154
  handleHeartbeat(standingInstructions: string, changesSummary?: string, timeContext?: string, dedupContext?: string, profile?: import('../types.js').AgentProfile | null): Promise<string>;
155
155
  handleCronJob(jobName: string, jobPrompt: string, tier?: number, maxTurns?: number, model?: string, workDir?: string, mode?: 'standard' | 'unleashed', maxHours?: number, timeoutMs?: number, successCriteria?: string[], agentSlug?: string): Promise<string>;
@@ -691,7 +691,7 @@ export class Gateway {
691
691
  };
692
692
  }
693
693
  // ── Message handling ────────────────────────────────────────────────
694
- async handleMessage(sessionKey, text, onText, model, maxTurns, onToolActivity) {
694
+ async handleMessage(sessionKey, text, onText, model, maxTurns, onToolActivity, onProgress) {
695
695
  if (this.draining) {
696
696
  return "I'm restarting momentarily — your message will be processed after I'm back online.";
697
697
  }
@@ -713,7 +713,7 @@ export class Gateway {
713
713
  text_len: text.length,
714
714
  });
715
715
  try {
716
- const result = await this._handleMessageInner(sessionKey, text, onText, model, maxTurns, onToolActivity);
716
+ const result = await this._handleMessageInner(sessionKey, text, onText, model, maxTurns, onToolActivity, onProgress);
717
717
  logAuditJsonl({
718
718
  event_type: 'message_completed',
719
719
  duration_ms: Date.now() - traceStart,
@@ -731,7 +731,7 @@ export class Gateway {
731
731
  }
732
732
  });
733
733
  }
734
- async _handleMessageInner(sessionKey, text, onText, model, maxTurns, onToolActivity) {
734
+ async _handleMessageInner(sessionKey, text, onText, model, maxTurns, onToolActivity, onProgress) {
735
735
  // ── Auth circuit breaker — stop spamming error messages ────────
736
736
  if (this.authCircuitOpen) {
737
737
  if (!this.shouldProbeAuth()) {
@@ -743,10 +743,31 @@ export class Gateway {
743
743
  // Allow this one message through as a probe to see if auth recovered
744
744
  logger.info({ sessionKey }, 'Auth circuit open — allowing probe message');
745
745
  }
746
+ // Show "queued" status if either lane or session lock is contended,
747
+ // so the user doesn't stare at "thinking..." for up to 60s while a
748
+ // previous message is still processing.
749
+ const laneWaitStart = Date.now();
750
+ let queuedStatusShown = false;
751
+ const queuedTimer = onProgress
752
+ ? setTimeout(() => {
753
+ queuedStatusShown = true;
754
+ onProgress('waiting for previous message to finish...').catch(() => { });
755
+ }, 750)
756
+ : null;
746
757
  const releaseLane = await lanes.acquire('chat');
758
+ if (queuedTimer)
759
+ clearTimeout(queuedTimer);
747
760
  try {
748
761
  const release = await this.acquireSessionLock(sessionKey);
749
762
  try {
763
+ if (queuedStatusShown && onProgress) {
764
+ // Lane was busy — clear the wait notice now that we're moving
765
+ await onProgress('thinking...').catch(() => { });
766
+ }
767
+ const laneWaitMs = Date.now() - laneWaitStart;
768
+ if (laneWaitMs > 1000) {
769
+ logger.info({ sessionKey, laneWaitMs }, 'Chat lane wait was non-trivial');
770
+ }
750
771
  logger.info(`Message from ${sessionKey}: ${text.slice(0, 100)}...`);
751
772
  events.emit('message:received', { sessionKey, text, timestamp: Date.now() });
752
773
  // ── Register provenance on first interaction ────────────────
@@ -849,6 +870,9 @@ export class Gateway {
849
870
  || text.startsWith('[Approval:')
850
871
  || text.startsWith('[Reaction:')
851
872
  || text.startsWith('[System:');
873
+ if (!isInternalMsg && !sess?.profile && !text.startsWith('!') && !isStructuredWorkflowMsg && onProgress) {
874
+ await onProgress('checking if a teammate should handle this...').catch(() => { });
875
+ }
852
876
  const routingResult = !isInternalMsg && !sess?.profile && !text.startsWith('!') && !isStructuredWorkflowMsg
853
877
  ? await this._maybeRouteToSpecialist(sessionKey, text, onText)
854
878
  : null;
@@ -1058,6 +1082,9 @@ export class Gateway {
1058
1082
  // Primary guardrail is cost budget (maxBudgetUsd in buildOptions).
1059
1083
  // Wall clock (CHAT_MAX_WALL_MS) and StallGuard are safety nets.
1060
1084
  events.emit('query:start', { sessionKey, model: effectiveModel, maxTurns: maxTurns, timestamp: Date.now() });
1085
+ if (onProgress) {
1086
+ await onProgress('thinking...').catch(() => { });
1087
+ }
1061
1088
  const queryStartMs = Date.now();
1062
1089
  const [response] = await Promise.race([
1063
1090
  this.assistant.chat(chatPrompt, effectiveSessionKey, { onText: wrappedOnText, onToolActivity: wrappedOnToolActivity, model: effectiveModel, maxTurns: maxTurns, securityAnnotation, projectOverride, profile: resolvedProfile, verboseLevel, abortController: chatAc }),
package/dist/types.d.ts CHANGED
@@ -103,6 +103,13 @@ export interface ChannelCapabilities {
103
103
  export declare const DEFAULT_CHANNEL_CAPABILITIES: ChannelCapabilities;
104
104
  export type OnTextCallback = (text: string) => Promise<void>;
105
105
  export type OnToolActivityCallback = (toolName: string, toolInput: Record<string, unknown>) => Promise<void>;
106
+ /**
107
+ * Pre-query progress callback. Fired at stage transitions BEFORE the SDK
108
+ * query starts (routing, complexity classification, lock waits, etc.) so
109
+ * the user sees the indicator change instead of staring at "thinking..."
110
+ * for several seconds.
111
+ */
112
+ export type OnProgressCallback = (status: string) => Promise<void>;
106
113
  export interface NotificationContext {
107
114
  agentSlug?: string;
108
115
  /** When set, the dispatcher routes the message back to the channel that owns this session. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.0.82",
3
+ "version": "1.0.83",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",