clementine-agent 1.18.52 → 1.18.54
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.
- package/dist/agent/assistant.d.ts +14 -0
- package/dist/agent/assistant.js +22 -0
- package/dist/agent/run-agent-context.d.ts +10 -0
- package/dist/agent/run-agent-context.js +42 -12
- package/dist/agent/run-agent-cron.js +6 -0
- package/dist/agent/run-agent-team-task.js +6 -0
- package/dist/gateway/router.js +26 -1
- package/package.json +1 -1
|
@@ -179,6 +179,20 @@ export declare class PersonalAssistant {
|
|
|
179
179
|
/** Fire-and-forget: extract a reusable skill from a successful execution. */
|
|
180
180
|
private extractSkillFromExecution;
|
|
181
181
|
private logMemoryExtractionSkip;
|
|
182
|
+
/**
|
|
183
|
+
* Public accessor for the SDK session ID associated with a sessionKey.
|
|
184
|
+
* Used by the canonical chat path so consecutive turns resume the
|
|
185
|
+
* same SDK conversation (the SDK persists sessions to JSONL on disk;
|
|
186
|
+
* resuming gives the agent native conversation history). Returns ''
|
|
187
|
+
* when no session exists yet.
|
|
188
|
+
*/
|
|
189
|
+
getSdkSessionId(sessionKey: string): string;
|
|
190
|
+
/**
|
|
191
|
+
* Persist the SDK session ID for a sessionKey. Called after a runAgent
|
|
192
|
+
* call returns so the next call can resume the same conversation.
|
|
193
|
+
* Writes through to disk via the existing saveSessions plumbing.
|
|
194
|
+
*/
|
|
195
|
+
setSdkSessionId(sessionKey: string, sdkSessionId: string): void;
|
|
182
196
|
/**
|
|
183
197
|
* Public entry point for triggering auto-memory extraction after an
|
|
184
198
|
* exchange. Used by the new runAgent chat path (Phase 2 migration)
|
package/dist/agent/assistant.js
CHANGED
|
@@ -2681,6 +2681,28 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2681
2681
|
}
|
|
2682
2682
|
catch { /* telemetry only */ }
|
|
2683
2683
|
}
|
|
2684
|
+
/**
|
|
2685
|
+
* Public accessor for the SDK session ID associated with a sessionKey.
|
|
2686
|
+
* Used by the canonical chat path so consecutive turns resume the
|
|
2687
|
+
* same SDK conversation (the SDK persists sessions to JSONL on disk;
|
|
2688
|
+
* resuming gives the agent native conversation history). Returns ''
|
|
2689
|
+
* when no session exists yet.
|
|
2690
|
+
*/
|
|
2691
|
+
getSdkSessionId(sessionKey) {
|
|
2692
|
+
return this.sessions.get(sessionKey) ?? '';
|
|
2693
|
+
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Persist the SDK session ID for a sessionKey. Called after a runAgent
|
|
2696
|
+
* call returns so the next call can resume the same conversation.
|
|
2697
|
+
* Writes through to disk via the existing saveSessions plumbing.
|
|
2698
|
+
*/
|
|
2699
|
+
setSdkSessionId(sessionKey, sdkSessionId) {
|
|
2700
|
+
if (!sdkSessionId)
|
|
2701
|
+
return;
|
|
2702
|
+
this.sessions.set(sessionKey, sdkSessionId);
|
|
2703
|
+
this.sessionTimestamps.set(sessionKey, new Date());
|
|
2704
|
+
this.saveSessions();
|
|
2705
|
+
}
|
|
2684
2706
|
/**
|
|
2685
2707
|
* Public entry point for triggering auto-memory extraction after an
|
|
2686
2708
|
* exchange. Used by the new runAgent chat path (Phase 2 migration)
|
|
@@ -14,4 +14,14 @@ export interface BuildChatContextOptions {
|
|
|
14
14
|
* SDK then runs with just the bare `claude_code` preset.
|
|
15
15
|
*/
|
|
16
16
|
export declare function buildChatSystemAppend(opts?: BuildChatContextOptions): string;
|
|
17
|
+
/**
|
|
18
|
+
* Read the long-term memory block for an autonomous run (cron, team-task).
|
|
19
|
+
* Returns the agent-specific MEMORY.md when a hired agent is active, the
|
|
20
|
+
* global MEMORY.md when running as Clementine, or empty when neither
|
|
21
|
+
* exists. Returns a heading-prefixed block ready to drop into a prompt.
|
|
22
|
+
*
|
|
23
|
+
* Heartbeats deliberately skip this — they're tool-free Haiku decisions
|
|
24
|
+
* and a 6KB memory block defeats the cost economy.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildAutonomousMemoryContext(profile?: AgentProfile | null): string;
|
|
17
27
|
//# sourceMappingURL=run-agent-context.d.ts.map
|
|
@@ -41,40 +41,70 @@ function trimTo(text, max) {
|
|
|
41
41
|
*/
|
|
42
42
|
export function buildChatSystemAppend(opts = {}) {
|
|
43
43
|
const blocks = [];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
const isHiredAgent = !!opts.profile && opts.profile.slug !== 'clementine';
|
|
45
|
+
// 1. Identity & Voice.
|
|
46
|
+
// - Clementine main agent: SOUL.md (the global personality file).
|
|
47
|
+
// - Hired agent main: their own profile.systemPromptBody IS their
|
|
48
|
+
// identity — don't load Clementine's SOUL on top of it.
|
|
49
|
+
if (!isHiredAgent) {
|
|
50
|
+
const soul = readFileSafe(SOUL_FILE);
|
|
51
|
+
if (soul.trim()) {
|
|
52
|
+
blocks.push(`## Identity & Voice\n${trimTo(soul, SOUL_MAX_CHARS)}`);
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
|
-
// 2. Long-term memory — agent-specific file
|
|
50
|
-
|
|
55
|
+
// 2. Long-term memory — agent-specific file when a hired agent is
|
|
56
|
+
// active, otherwise the global one.
|
|
57
|
+
const profileMemoryPath = isHiredAgent
|
|
51
58
|
? path.join(AGENTS_DIR, opts.profile.slug, 'MEMORY.md')
|
|
52
59
|
: null;
|
|
53
|
-
let memory = '';
|
|
54
60
|
if (profileMemoryPath && fs.existsSync(profileMemoryPath)) {
|
|
55
|
-
memory = readFileSafe(profileMemoryPath);
|
|
61
|
+
const memory = readFileSafe(profileMemoryPath);
|
|
56
62
|
if (memory.trim()) {
|
|
57
|
-
blocks.push(`## Long-Term Memory (${opts.profile
|
|
63
|
+
blocks.push(`## Long-Term Memory (${opts.profile.name ?? opts.profile.slug})\n${trimTo(memory, PROFILE_MEMORY_MAX_CHARS)}`);
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
else {
|
|
61
|
-
memory = readFileSafe(MEMORY_FILE);
|
|
67
|
+
const memory = readFileSafe(MEMORY_FILE);
|
|
62
68
|
if (memory.trim()) {
|
|
63
69
|
blocks.push(`## Long-Term Memory\n${trimTo(memory, MEMORY_MAX_CHARS)}`);
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
// 3. Team roster (only when not running AS a hired agent — Sasha
|
|
67
73
|
// doesn't need to be told who Sasha is).
|
|
68
|
-
if (!
|
|
74
|
+
if (!isHiredAgent) {
|
|
69
75
|
const agentsRoster = readFileSafe(AGENTS_FILE);
|
|
70
76
|
if (agentsRoster.trim()) {
|
|
71
77
|
blocks.push(`## Team Roster\n${trimTo(agentsRoster, AGENTS_MAX_CHARS)}`);
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
|
-
// 4. Profile system prompt body (
|
|
80
|
+
// 4. Profile system prompt body (the hired agent's identity + role).
|
|
75
81
|
if (opts.profileAppend?.trim()) {
|
|
76
82
|
blocks.push(opts.profileAppend);
|
|
77
83
|
}
|
|
78
84
|
return blocks.join('\n\n');
|
|
79
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Read the long-term memory block for an autonomous run (cron, team-task).
|
|
88
|
+
* Returns the agent-specific MEMORY.md when a hired agent is active, the
|
|
89
|
+
* global MEMORY.md when running as Clementine, or empty when neither
|
|
90
|
+
* exists. Returns a heading-prefixed block ready to drop into a prompt.
|
|
91
|
+
*
|
|
92
|
+
* Heartbeats deliberately skip this — they're tool-free Haiku decisions
|
|
93
|
+
* and a 6KB memory block defeats the cost economy.
|
|
94
|
+
*/
|
|
95
|
+
export function buildAutonomousMemoryContext(profile) {
|
|
96
|
+
const isHiredAgent = !!profile && profile.slug !== 'clementine';
|
|
97
|
+
const memoryPath = isHiredAgent
|
|
98
|
+
? path.join(AGENTS_DIR, profile.slug, 'MEMORY.md')
|
|
99
|
+
: MEMORY_FILE;
|
|
100
|
+
if (!fs.existsSync(memoryPath))
|
|
101
|
+
return '';
|
|
102
|
+
const memory = readFileSafe(memoryPath);
|
|
103
|
+
if (!memory.trim())
|
|
104
|
+
return '';
|
|
105
|
+
const label = isHiredAgent
|
|
106
|
+
? `Long-Term Memory (${profile.name ?? profile.slug})`
|
|
107
|
+
: 'Long-Term Memory';
|
|
108
|
+
return `## ${label}\n${trimTo(memory, isHiredAgent ? PROFILE_MEMORY_MAX_CHARS : MEMORY_MAX_CHARS)}\n\n`;
|
|
109
|
+
}
|
|
80
110
|
//# sourceMappingURL=run-agent-context.js.map
|
|
@@ -20,6 +20,7 @@ import pino from 'pino';
|
|
|
20
20
|
import { BASE_DIR, VAULT_DIR, CRON_PROGRESS_DIR, } from '../config.js';
|
|
21
21
|
import { runAgent } from './run-agent.js';
|
|
22
22
|
import { buildExtraMcpForRunAgent } from './run-agent-mcp.js';
|
|
23
|
+
import { buildAutonomousMemoryContext } from './run-agent-context.js';
|
|
23
24
|
import { listAllGoals } from '../tools/shared.js';
|
|
24
25
|
const CRON_PROGRESS_PENDING_MAX_ITEMS = 20;
|
|
25
26
|
const CRON_PROGRESS_NOTES_MAX_CHARS = 2000;
|
|
@@ -234,6 +235,10 @@ export async function runAgentCron(opts) {
|
|
|
234
235
|
const agentSlug = opts.profile?.slug;
|
|
235
236
|
const ownerName = process.env.OWNER_NAME ?? 'the user';
|
|
236
237
|
// ── Compose context blocks (mirrors legacy runCronJob) ─────────────
|
|
238
|
+
// Memory block goes first so the agent reads its long-term context
|
|
239
|
+
// before the run-specific progress/goals/etc. For a hired agent
|
|
240
|
+
// (Ross/Sasha) this is their own MEMORY.md, not Clementine's global.
|
|
241
|
+
const memoryContext = buildAutonomousMemoryContext(opts.profile);
|
|
237
242
|
const progressContext = buildProgressContext(opts.jobName);
|
|
238
243
|
const goalContext = buildGoalContext(opts.jobName);
|
|
239
244
|
const delegationContext = buildDelegationContext(agentSlug);
|
|
@@ -246,6 +251,7 @@ export async function runAgentCron(opts) {
|
|
|
246
251
|
// spawns isolated sub-agents without a hand-rolled prompt directive.
|
|
247
252
|
// Final prompt
|
|
248
253
|
const builtPrompt = `[Scheduled task: ${opts.jobName}]\n\n` +
|
|
254
|
+
memoryContext +
|
|
249
255
|
progressContext +
|
|
250
256
|
goalContext +
|
|
251
257
|
skillContext +
|
|
@@ -17,15 +17,21 @@
|
|
|
17
17
|
import pino from 'pino';
|
|
18
18
|
import { runAgent } from './run-agent.js';
|
|
19
19
|
import { buildExtraMcpForRunAgent } from './run-agent-mcp.js';
|
|
20
|
+
import { buildAutonomousMemoryContext } from './run-agent-context.js';
|
|
20
21
|
const logger = pino({ name: 'clementine.run-agent-team-task' });
|
|
21
22
|
export async function runAgentTeamTask(opts) {
|
|
22
23
|
const taskName = `team-msg:${opts.fromSlug}-to-${opts.profile.slug}`;
|
|
23
24
|
const now = new Date();
|
|
24
25
|
const timestamp = now.toISOString().slice(0, 16).replace('T', ' ');
|
|
26
|
+
// Inject the recipient's own long-term memory so they have context
|
|
27
|
+
// about prior work, preferences, and team relationships before
|
|
28
|
+
// processing the message.
|
|
29
|
+
const memoryContext = buildAutonomousMemoryContext(opts.profile);
|
|
25
30
|
// Match the legacy phase-1 prompt shape so existing agent training
|
|
26
31
|
// (Sasha/Ross/Nora) keeps responding the same way. Phases 2+ are no
|
|
27
32
|
// longer needed — the SDK keeps the conversation in one session.
|
|
28
33
|
const builtPrompt = `[TEAM MESSAGE from ${opts.fromName} (${opts.fromSlug}) — ${timestamp}]\n\n` +
|
|
34
|
+
memoryContext +
|
|
29
35
|
`You received a direct message from a teammate. Process it fully and autonomously.\n\n` +
|
|
30
36
|
`MESSAGE:\n${opts.content}\n\n` +
|
|
31
37
|
`IMPORTANT:\n` +
|
package/dist/gateway/router.js
CHANGED
|
@@ -1801,6 +1801,23 @@ export class Gateway {
|
|
|
1801
1801
|
profile: resolvedProfile,
|
|
1802
1802
|
profileAppend: resolvedProfile?.systemPromptBody,
|
|
1803
1803
|
});
|
|
1804
|
+
// Per-turn context — recall of recent transcripts, persistent
|
|
1805
|
+
// learnings, silent context blocks, security advisories, and
|
|
1806
|
+
// toolset directives accumulated above. This is what gives
|
|
1807
|
+
// continuity across daemon restarts (SDK in-memory session is
|
|
1808
|
+
// gone, but transcripts in SQLite persist; recall surfaces
|
|
1809
|
+
// them). Prefixed to the user message in a clearly-delimited
|
|
1810
|
+
// [Context] block so the model knows it's framing, not user
|
|
1811
|
+
// input.
|
|
1812
|
+
const turnContextPrefix = securityAnnotation.trim()
|
|
1813
|
+
? `[Context — read this for continuity, then respond to the user message below]\n${securityAnnotation}\n[/Context]\n\n`
|
|
1814
|
+
: '';
|
|
1815
|
+
const finalPrompt = turnContextPrefix + chatPrompt;
|
|
1816
|
+
// Resume the prior SDK session when one exists for this
|
|
1817
|
+
// sessionKey. The SDK persists session JSONLs to disk, so
|
|
1818
|
+
// resume works across daemon restarts. Without this, every
|
|
1819
|
+
// turn is a fresh SDK session with zero conversation history.
|
|
1820
|
+
const priorSdkSessionId = this.assistant.getSdkSessionId(effectiveSessionKey);
|
|
1804
1821
|
logger.info({
|
|
1805
1822
|
sessionKey: effectiveSessionKey,
|
|
1806
1823
|
profile: resolvedProfile?.slug,
|
|
@@ -1808,8 +1825,10 @@ export class Gateway {
|
|
|
1808
1825
|
composioConnected: chatMcp.composioConnected.length,
|
|
1809
1826
|
externalConnected: chatMcp.externalConnected.length,
|
|
1810
1827
|
systemAppendChars: chatSystemAppend.length,
|
|
1828
|
+
turnContextChars: turnContextPrefix.length,
|
|
1829
|
+
resumingSdkSessionId: priorSdkSessionId || null,
|
|
1811
1830
|
}, 'Routing chat through runAgent');
|
|
1812
|
-
const runAgentResult = await runAgent(
|
|
1831
|
+
const runAgentResult = await runAgent(finalPrompt, {
|
|
1813
1832
|
sessionKey: effectiveSessionKey,
|
|
1814
1833
|
source: 'chat',
|
|
1815
1834
|
profile: resolvedProfile,
|
|
@@ -1818,6 +1837,7 @@ export class Gateway {
|
|
|
1818
1837
|
...(effectiveModel ? { model: effectiveModel } : {}),
|
|
1819
1838
|
...(maxTurns ? { maxTurns } : {}),
|
|
1820
1839
|
...(chatSystemAppend ? { systemPromptAppend: chatSystemAppend } : {}),
|
|
1840
|
+
...(priorSdkSessionId ? { resumeSessionId: priorSdkSessionId } : {}),
|
|
1821
1841
|
extraMcpServers: chatMcp.servers,
|
|
1822
1842
|
onText: wrappedOnText,
|
|
1823
1843
|
onToolActivity: ({ tool, input }) => {
|
|
@@ -1829,6 +1849,11 @@ export class Gateway {
|
|
|
1829
1849
|
},
|
|
1830
1850
|
abortSignal: chatAc.signal,
|
|
1831
1851
|
});
|
|
1852
|
+
// Persist the SDK session ID so the next turn resumes the
|
|
1853
|
+
// same conversation. Survives daemon restarts via SESSIONS_FILE.
|
|
1854
|
+
if (runAgentResult.sessionId) {
|
|
1855
|
+
this.assistant.setSdkSessionId(effectiveSessionKey, runAgentResult.sessionId);
|
|
1856
|
+
}
|
|
1832
1857
|
clearTimeout(chatTimer);
|
|
1833
1858
|
clearTimeout(hardWallTimer);
|
|
1834
1859
|
// Mirror transcript so memory + recall continue working.
|