clementine-agent 1.18.19 → 1.18.21

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 (37) hide show
  1. package/README.md +17 -0
  2. package/dist/agent/action-enforcer.d.ts +29 -0
  3. package/dist/agent/action-enforcer.js +120 -0
  4. package/dist/agent/assistant.d.ts +14 -0
  5. package/dist/agent/assistant.js +190 -35
  6. package/dist/agent/auto-update.js +46 -2
  7. package/dist/agent/local-turn.d.ts +16 -0
  8. package/dist/agent/local-turn.js +54 -1
  9. package/dist/agent/route-classifier.d.ts +1 -0
  10. package/dist/agent/route-classifier.js +30 -3
  11. package/dist/agent/toolsets.d.ts +14 -0
  12. package/dist/agent/toolsets.js +68 -0
  13. package/dist/brain/ingestion-pipeline.d.ts +7 -0
  14. package/dist/brain/ingestion-pipeline.js +107 -21
  15. package/dist/channels/discord.js +38 -7
  16. package/dist/channels/telegram.js +5 -6
  17. package/dist/cli/dashboard.js +112 -6
  18. package/dist/cli/index.js +174 -0
  19. package/dist/cli/ingest.js +8 -2
  20. package/dist/gateway/context-hygiene.d.ts +17 -0
  21. package/dist/gateway/context-hygiene.js +31 -0
  22. package/dist/gateway/heartbeat-scheduler.d.ts +20 -0
  23. package/dist/gateway/heartbeat-scheduler.js +27 -10
  24. package/dist/gateway/router.d.ts +8 -1
  25. package/dist/gateway/router.js +326 -12
  26. package/dist/gateway/turn-ledger.d.ts +32 -0
  27. package/dist/gateway/turn-ledger.js +55 -0
  28. package/dist/memory/embeddings.d.ts +2 -0
  29. package/dist/memory/embeddings.js +8 -1
  30. package/dist/memory/store.d.ts +88 -1
  31. package/dist/memory/store.js +349 -18
  32. package/dist/memory/write-queue.d.ts +16 -0
  33. package/dist/memory/write-queue.js +5 -0
  34. package/dist/tools/shared.d.ts +89 -0
  35. package/dist/types.d.ts +11 -0
  36. package/package.json +1 -1
  37. package/scripts/postinstall.js +56 -6
package/README.md CHANGED
@@ -216,10 +216,27 @@ clementine login | auth Authenticate Claude Code / OAuth providers
216
216
  clementine chat Interactive REPL
217
217
  clementine memory status Index size, recent activity
218
218
  clementine memory search <q> FTS5 search
219
+ clementine memory model status Local dense embedding model cache
220
+ clementine memory model install Pre-cache the local embedding model
219
221
  clementine memory dedup | reembed Maintenance
220
222
  clementine brain digest Run the brain digest pipeline
221
223
  ```
222
224
 
225
+ Dense neural recall uses a local Transformers.js embedding model. Model
226
+ weights are not bundled into the npm tarball; the first install caches them
227
+ under `~/.clementine/models/`. To make repo or npm updates prefetch the model
228
+ automatically, set this once in `~/.clementine/.env`:
229
+
230
+ ```
231
+ CLEMENTINE_PREFETCH_EMBEDDINGS=1
232
+ ```
233
+
234
+ You can also opt in for a single install/update command:
235
+
236
+ ```
237
+ CLEMENTINE_INSTALL_EMBEDDINGS=1 npm install -g clementine-agent
238
+ ```
239
+
223
240
  **Projects & agents**
224
241
  ```
225
242
  clementine projects list | add <p> | remove <p>
@@ -0,0 +1,29 @@
1
+ export type ActionExpectationSource = 'approval_followup' | 'user_request' | 'diagnostic_request' | 'none';
2
+ export interface ActionExpectation {
3
+ expected: boolean;
4
+ source: ActionExpectationSource;
5
+ reason: string;
6
+ }
7
+ export interface ActionResponseAssessment {
8
+ violation: boolean;
9
+ reason: string;
10
+ }
11
+ export declare function detectActionExpectation(userText: string, opts?: {
12
+ approvalFollowup?: boolean;
13
+ }): ActionExpectation;
14
+ export declare function buildApprovalFollowupPrompt(reply: string): string;
15
+ export declare function assessActionResponse(input: {
16
+ actionExpectation: ActionExpectation;
17
+ userText: string;
18
+ response: string;
19
+ toolActivityCount: number;
20
+ backgroundStarted?: boolean;
21
+ delegated?: boolean;
22
+ }): ActionResponseAssessment;
23
+ export declare function buildActionEnforcementPrompt(input: {
24
+ userText: string;
25
+ previousResponse: string;
26
+ reason: string;
27
+ }): string;
28
+ export declare function fallbackUnverifiedActionResponse(reason: string): string;
29
+ //# sourceMappingURL=action-enforcer.d.ts.map
@@ -0,0 +1,120 @@
1
+ import { looksLikeApprovalPrompt } from './local-turn.js';
2
+ function normalize(text) {
3
+ return text.trim().toLowerCase().replace(/\s+/g, ' ');
4
+ }
5
+ const ACTION_REQUEST_RE = /\b(can you|could you|would you|please|pls|i need you to|i want you to|let'?s|go ahead and|do it|handle this|take care of)\b[\s\S]{0,160}\b(send|email|message|post|publish|delete|change|update|run|execute|check|look(?:\s+into)?|diagnose|investigate|figure(?:\s+it)?\s*out|find|search|read|write|create|fix|schedule|reschedule|pull|fetch|review|tag|save|upload|download)\b/i;
6
+ const DIRECT_ACTION_RE = /^(send|email|message|post|publish|delete|change|update|run|execute|check|look(?:\s+into)?|diagnose|investigate|find|search|read|write|create|fix|schedule|reschedule|pull|fetch|review|tag|save|upload|download)\b/i;
7
+ const DIAGNOSTIC_RE = /\b(log|logs|crash|crashing|error|failing|failure|broken|diagnose|debug|investigate|look into|figure it out|what'?s causing|why is|why did)\b/i;
8
+ const DONE_CLAIM_RE = /\b(done|sent|emailed|queued|accepted|completed|finished|fixed|updated|changed|deleted|posted|published|scheduled|rescheduled|created|saved|uploaded|downloaded|tagged|checked|reviewed|found|read|ran|executed)\b/i;
9
+ const PROMISE_RE = /\b(i'?ll|i will|i am going to|i'?m going to|let me|i'?m checking|i'?m sending|i'?m running|i'?m looking|working on it|on it)\b[\s\S]{0,120}\b(send|email|message|post|publish|delete|change|update|run|execute|check|look|diagnose|investigate|find|search|read|write|create|fix|schedule|reschedule|pull|fetch|review|tag|save|upload|download|now)\b/i;
10
+ const VACUOUS_ACK_RE = /^(got it|okay|ok|sure|perfect|sounds good|on it|will do|yep|yeah)[.! ]*$/i;
11
+ const BLOCKED_OR_ASKING_RE = /\b(i can'?t|i cannot|unable to|blocked|need you to|need a|need the|please provide|please send|can you send|can you share|which|what should|who should|before i|confirm|approve|good to go|okay to)\b/i;
12
+ const DIAGNOSTIC_DEFLECTION_RE = /\b(what are you seeing|what do you see|send (me )?the logs|share (the )?logs|provide (the )?logs|can you paste|can you send me)\b/i;
13
+ export function detectActionExpectation(userText, opts = {}) {
14
+ if (opts.approvalFollowup) {
15
+ return {
16
+ expected: true,
17
+ source: 'approval_followup',
18
+ reason: 'user approved the previous action prompt',
19
+ };
20
+ }
21
+ const text = userText.trim();
22
+ if (!text)
23
+ return { expected: false, source: 'none', reason: 'empty message' };
24
+ if (ACTION_REQUEST_RE.test(text) || DIRECT_ACTION_RE.test(text)) {
25
+ const diagnostic = DIAGNOSTIC_RE.test(text);
26
+ return {
27
+ expected: true,
28
+ source: diagnostic ? 'diagnostic_request' : 'user_request',
29
+ reason: diagnostic ? 'user asked for local/tool-backed diagnosis' : 'user asked Clementine to take an action',
30
+ };
31
+ }
32
+ if (DIAGNOSTIC_RE.test(text) && /\b(can you|could you|please|figure|diagnose|debug|look)\b/i.test(text)) {
33
+ return {
34
+ expected: true,
35
+ source: 'diagnostic_request',
36
+ reason: 'user asked for local/tool-backed diagnosis',
37
+ };
38
+ }
39
+ return { expected: false, source: 'none', reason: 'no concrete action requested' };
40
+ }
41
+ export function buildApprovalFollowupPrompt(reply) {
42
+ return [
43
+ `[Approval reply: "${reply.trim().slice(0, 120)}"]`,
44
+ 'The user approved the action you proposed in your previous message.',
45
+ 'Continue from that previous approval prompt and perform the approved action now using the appropriate tool.',
46
+ 'Do not treat this as casual feedback. Do not say it is done unless a tool call verifies it. If you are blocked, say exactly what is blocking you.',
47
+ ].join('\n');
48
+ }
49
+ export function assessActionResponse(input) {
50
+ const { actionExpectation, userText, response, toolActivityCount } = input;
51
+ if (!actionExpectation.expected)
52
+ return { violation: false, reason: 'no action expected' };
53
+ if (input.backgroundStarted)
54
+ return { violation: false, reason: 'action was queued in background' };
55
+ if (input.delegated)
56
+ return { violation: false, reason: 'action was delegated' };
57
+ if (toolActivityCount > 0)
58
+ return { violation: false, reason: 'tool activity observed' };
59
+ const trimmed = response.trim();
60
+ if (!trimmed)
61
+ return { violation: true, reason: 'empty response to action request' };
62
+ if (looksLikeApprovalPrompt(trimmed)) {
63
+ if (actionExpectation.source === 'approval_followup') {
64
+ return { violation: true, reason: 'asked for approval again after the user already approved' };
65
+ }
66
+ return { violation: false, reason: 'assistant requested approval before acting' };
67
+ }
68
+ const lower = normalize(trimmed);
69
+ if (actionExpectation.source === 'diagnostic_request' && DIAGNOSTIC_DEFLECTION_RE.test(lower)) {
70
+ return { violation: true, reason: 'asked user for logs instead of using available local tools' };
71
+ }
72
+ if (VACUOUS_ACK_RE.test(trimmed)) {
73
+ return { violation: true, reason: 'acknowledged action request without acting' };
74
+ }
75
+ if (DONE_CLAIM_RE.test(trimmed)) {
76
+ return { violation: true, reason: 'claimed completion without tool activity' };
77
+ }
78
+ if (PROMISE_RE.test(trimmed)) {
79
+ return { violation: true, reason: 'promised future action without same-turn tool activity' };
80
+ }
81
+ if (BLOCKED_OR_ASKING_RE.test(lower) || trimmed.endsWith('?')) {
82
+ return { violation: false, reason: 'assistant asked for needed input or reported a block' };
83
+ }
84
+ // Diagnostic requests should usually use local tools. A generic answer with
85
+ // no tool activity is allowed only if it clearly does not claim inspection.
86
+ if (actionExpectation.source === 'diagnostic_request' && /\b(i think|likely|probably|sounds like)\b/i.test(trimmed)) {
87
+ return { violation: false, reason: 'assistant gave hypothesis without claiming inspection' };
88
+ }
89
+ // For action-shaped requests, a generic short answer is usually a stall.
90
+ if (trimmed.length < 80 && /\b(send|email|message|run|fix|check|diagnose|figure|look)\b/i.test(userText)) {
91
+ return { violation: true, reason: 'short action response without tool activity' };
92
+ }
93
+ return { violation: false, reason: 'no unsupported action claim detected' };
94
+ }
95
+ export function buildActionEnforcementPrompt(input) {
96
+ return [
97
+ '[SYSTEM ACTION ENFORCEMENT]',
98
+ 'Your previous response was not allowed because it implied action without verified tool activity.',
99
+ `Reason: ${input.reason}`,
100
+ '',
101
+ 'Original user request:',
102
+ input.userText.slice(0, 1200),
103
+ '',
104
+ 'Previous response:',
105
+ input.previousResponse.slice(0, 1200),
106
+ '',
107
+ 'Now correct this in the same turn:',
108
+ '- If the action is possible, use the appropriate tool now.',
109
+ '- If the action is blocked, say exactly what is blocking it.',
110
+ '- Do not say "done", "sent", "queued", "checked", or similar unless a tool call actually verifies it.',
111
+ ].join('\n');
112
+ }
113
+ export function fallbackUnverifiedActionResponse(reason) {
114
+ return [
115
+ "I didn't complete that yet.",
116
+ `I caught an action-verification issue: ${reason}.`,
117
+ "I won't call it done without a tool confirmation. Please resend the request and I'll retry from a clean turn.",
118
+ ].join(' ');
119
+ }
120
+ //# sourceMappingURL=action-enforcer.js.map
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import type { AgentProfile, OnTextCallback, OnToolActivityCallback, VerboseLevel } from '../types.js';
13
13
  import { AgentManager } from './agent-manager.js';
14
+ import { type ToolsetName } from './toolsets.js';
14
15
  /**
15
16
  * Estimate token count for Claude.
16
17
  *
@@ -30,7 +31,11 @@ export declare function estimateTokens(text: string): number;
30
31
  export declare function looksLikeContextThrashText(value: unknown): boolean;
31
32
  export declare function contextThrashRecoveryNotice(): string;
32
33
  export declare function buildContextThrashRecoveryPrompt(userRequest: string, priorFailureText?: string): string;
34
+ /** Format a millisecond duration as a human-friendly "X ago" string. */
35
+ export declare function formatTimeAgo(ms: number): string;
33
36
  export declare function looksLikeOneMillionContextError(value: unknown): boolean;
37
+ export declare function oneMillionContextRecoveryMessage(): string;
38
+ export declare function looksLikeProviderApiErrorResponse(value: unknown): boolean;
34
39
  export declare function looksLikeNoResponseRequested(value: unknown): boolean;
35
40
  /** Autonomous jobs use this sentinel to mean "completed, but do not notify the owner." */
36
41
  export declare function isAutonomousNothingOutput(response: string): boolean;
@@ -145,6 +150,7 @@ export declare class PersonalAssistant {
145
150
  flushSessions(): void;
146
151
  private saveSessionsNow;
147
152
  getExchangeCount(sessionKey: string): number;
153
+ hasRecentApprovalPrompt(sessionKey: string): boolean;
148
154
  getMemoryChunkCount(): number;
149
155
  private buildSystemPrompt;
150
156
  private buildOptions;
@@ -176,6 +182,7 @@ export declare class PersonalAssistant {
176
182
  projectOverride?: ProjectMeta;
177
183
  verboseLevel?: VerboseLevel;
178
184
  abortController?: AbortController;
185
+ toolset?: ToolsetName;
179
186
  }): Promise<[string, string]>;
180
187
  /**
181
188
  * Compare retrieved chunks against the response text and record which
@@ -196,6 +203,12 @@ export declare class PersonalAssistant {
196
203
  * No LLM call — uses buildLocalSummary for instant summarization.
197
204
  */
198
205
  private compactContext;
206
+ compactSessionForGateway(sessionKey: string, reason?: string): {
207
+ compacted: boolean;
208
+ exchangeCount: number;
209
+ summary?: string;
210
+ reason: string;
211
+ };
199
212
  /**
200
213
  * Expire sessions inactive for more than 24 hours.
201
214
  * Called periodically from chat() to prevent unbounded map growth.
@@ -207,6 +220,7 @@ export declare class PersonalAssistant {
207
220
  * to avoid blocking the user's query.
208
221
  */
209
222
  private buildLocalSummary;
223
+ private buildStructuredCompactionSummary;
210
224
  private buildLocalSummaryFromTurns;
211
225
  /**
212
226
  * Walk a chronological list of transcript turns and pair adjacent