mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
## CHANGES
- Persist Claude session context-usage percentage, surviving resumes and restarts 🧠 - New IPC/API: `claude:updateSessionContextUsage` to store live context stats 🔌 - Fix resumed sessions falsely showing 100% context from lifetime tokens 🧯 - Usage stats now preserve cost only; tokens intentionally zeroed 📉 - Resume now always fetches session origins to restore context usage 🧭 - Reconstruct context percent on resume by synthesizing equivalent input tokens 🧮 - Add `{{AGENT_HISTORY_PATH}}` template variable for prompts and commands 🧾 - System prompt gains Task Recall guidance using the history JSON file 🗂️ - Input processing now resolves history file path during prompt substitution 🧬 - Add Electron DevTools trace-export workarounds in performance docs 🛠️
This commit is contained in:
@@ -230,3 +230,34 @@ useEffect(() => {
|
||||
return () => document.removeEventListener('click', handler);
|
||||
}, []);
|
||||
```
|
||||
|
||||
## Performance Profiling
|
||||
|
||||
**Exporting DevTools Performance traces:**
|
||||
|
||||
The Chrome DevTools Performance panel's "Save profile" button fails in Electron with:
|
||||
```
|
||||
NotAllowedError: The request is not allowed by the user agent or the platform in the current context.
|
||||
```
|
||||
|
||||
This occurs because Electron 28 doesn't fully support the File System Access API (`showSaveFilePicker`). Full support was added in Electron 30+ ([electron/electron#41419](https://github.com/electron/electron/pull/41419)).
|
||||
|
||||
**Workarounds:**
|
||||
|
||||
1. **Launch with experimental flag** (enables FSAA):
|
||||
```bash
|
||||
# macOS
|
||||
/Applications/Maestro.app/Contents/MacOS/Maestro --enable-experimental-web-platform-features
|
||||
|
||||
# Development
|
||||
npm run dev -- --enable-experimental-web-platform-features
|
||||
```
|
||||
|
||||
2. **Use Maestro's native save dialog** (copy trace JSON from DevTools, then in renderer console):
|
||||
```javascript
|
||||
navigator.clipboard.readText().then(data =>
|
||||
window.maestro.dialog.saveFile({ defaultPath: 'trace.json', content: data })
|
||||
);
|
||||
```
|
||||
|
||||
3. **Right-click context menu** - Right-click on the flame graph and select "Save profile..." which may use a different code path.
|
||||
|
||||
@@ -153,8 +153,9 @@ describe('Claude IPC handlers', () => {
|
||||
// Line 1422: ipcMain.handle('claude:registerSessionOrigin', ...) - Register session origin (user/auto)
|
||||
// Line 1438: ipcMain.handle('claude:updateSessionName', ...) - Update session name
|
||||
// Line 1459: ipcMain.handle('claude:updateSessionStarred', ...) - Update session starred status
|
||||
// Line 1480: ipcMain.handle('claude:getSessionOrigins', ...) - Get session origins for a project
|
||||
// Line 1488: ipcMain.handle('claude:getAllNamedSessions', ...) - Get all sessions with names
|
||||
// Line 1461: ipcMain.handle('claude:updateSessionContextUsage', ...) - Update context usage percentage
|
||||
// Line 1482: ipcMain.handle('claude:getSessionOrigins', ...) - Get session origins for a project
|
||||
// Line 1490: ipcMain.handle('claude:getAllNamedSessions', ...) - Get all sessions with names
|
||||
const expectedChannels = [
|
||||
'claude:listSessions',
|
||||
'claude:listSessionsPaginated',
|
||||
@@ -168,6 +169,7 @@ describe('Claude IPC handlers', () => {
|
||||
'claude:registerSessionOrigin',
|
||||
'claude:updateSessionName',
|
||||
'claude:updateSessionStarred',
|
||||
'claude:updateSessionContextUsage',
|
||||
'claude:getSessionOrigins',
|
||||
'claude:getAllNamedSessions',
|
||||
];
|
||||
|
||||
@@ -2180,6 +2180,8 @@ describe('AgentSessionsBrowser', () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
// buildUsageStats now only preserves cost (tokens are zeroed to avoid stale context display)
|
||||
// The actual context usage will be looked up from session origins by handleResumeSession
|
||||
expect(onResumeSession).toHaveBeenCalledWith(
|
||||
'session-1',
|
||||
expect.arrayContaining([
|
||||
@@ -2189,8 +2191,8 @@ describe('AgentSessionsBrowser', () => {
|
||||
'My Session',
|
||||
false, // not starred
|
||||
expect.objectContaining({
|
||||
inputTokens: 5000,
|
||||
outputTokens: 2000,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalCostUsd: 0.15,
|
||||
})
|
||||
);
|
||||
@@ -2229,14 +2231,15 @@ describe('AgentSessionsBrowser', () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
// buildUsageStats now only preserves cost (tokens are zeroed to avoid stale context display)
|
||||
expect(onResumeSession).toHaveBeenCalledWith(
|
||||
'session-1',
|
||||
expect.any(Array),
|
||||
undefined,
|
||||
true, // starred
|
||||
expect.objectContaining({
|
||||
inputTokens: 5000,
|
||||
outputTokens: 2000,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalCostUsd: 0.15,
|
||||
})
|
||||
);
|
||||
@@ -2307,14 +2310,15 @@ describe('AgentSessionsBrowser', () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
// buildUsageStats now only preserves cost (tokens are zeroed to avoid stale context display)
|
||||
expect(onResumeSession).toHaveBeenCalledWith(
|
||||
'session-1',
|
||||
[], // Empty messages for quick resume
|
||||
'Quick Session',
|
||||
false,
|
||||
expect.objectContaining({
|
||||
inputTokens: 5000,
|
||||
outputTokens: 2000,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalCostUsd: 0.15,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -297,7 +297,7 @@ describe('useAgentSessionManagement', () => {
|
||||
expect(updatedSession.inputMode).toBe('ai');
|
||||
});
|
||||
|
||||
it('skips origin lookup when metadata is already provided', async () => {
|
||||
it('skips message fetch when messages are already provided', async () => {
|
||||
const activeSession = createMockSession({ projectRoot: '/test/project' });
|
||||
const setSessions = vi.fn();
|
||||
|
||||
@@ -325,7 +325,9 @@ describe('useAgentSessionManagement', () => {
|
||||
await result.current.handleResumeSession('agent-789', providedMessages, 'Named Session', false);
|
||||
});
|
||||
|
||||
expect(window.maestro.claude.getSessionOrigins).not.toHaveBeenCalled();
|
||||
// Origin lookup is still called to get contextUsage for context window persistence
|
||||
expect(window.maestro.claude.getSessionOrigins).toHaveBeenCalled();
|
||||
// But message fetch should be skipped since messages were provided
|
||||
expect(window.maestro.agentSessions.read).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,6 +64,7 @@ describe('TEMPLATE_VARIABLES constant', () => {
|
||||
expect(variables).toContain('{{AGENT_PATH}}');
|
||||
expect(variables).toContain('{{AGENT_GROUP}}');
|
||||
expect(variables).toContain('{{AGENT_SESSION_ID}}');
|
||||
expect(variables).toContain('{{AGENT_HISTORY_PATH}}');
|
||||
expect(variables).toContain('{{TAB_NAME}}');
|
||||
expect(variables).toContain('{{TOOL_TYPE}}');
|
||||
});
|
||||
@@ -205,6 +206,22 @@ describe('substituteTemplateVariables', () => {
|
||||
expect(result1).toBe(result2);
|
||||
expect(result1).toBe('Aliased Name');
|
||||
});
|
||||
|
||||
it('should replace {{AGENT_HISTORY_PATH}} with historyFilePath', () => {
|
||||
const context = createTestContext({
|
||||
historyFilePath: '/Users/test/.config/Maestro/history/session-123.json',
|
||||
});
|
||||
const result = substituteTemplateVariables('History: {{AGENT_HISTORY_PATH}}', context);
|
||||
expect(result).toBe('History: /Users/test/.config/Maestro/history/session-123.json');
|
||||
});
|
||||
|
||||
it('should replace {{AGENT_HISTORY_PATH}} with empty string when historyFilePath is undefined', () => {
|
||||
const context = createTestContext({
|
||||
historyFilePath: undefined,
|
||||
});
|
||||
const result = substituteTemplateVariables('History: {{AGENT_HISTORY_PATH}}', context);
|
||||
expect(result).toBe('History: ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Legacy Session Variables (backwards compatibility)', () => {
|
||||
|
||||
@@ -123,6 +123,8 @@ export interface SessionOriginInfo {
|
||||
origin: AgentSessionOrigin;
|
||||
sessionName?: string;
|
||||
starred?: boolean;
|
||||
/** Last known context window usage percentage (0-100) for session resume */
|
||||
contextUsage?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -301,6 +301,7 @@ interface ClaudeSessionOriginInfo {
|
||||
origin: ClaudeSessionOrigin;
|
||||
sessionName?: string; // User-defined session name from Maestro
|
||||
starred?: boolean; // Whether the session is starred
|
||||
contextUsage?: number; // Last known context window usage percentage (0-100)
|
||||
}
|
||||
interface ClaudeSessionOriginsData {
|
||||
// Map of projectPath -> { agentSessionId -> origin info }
|
||||
|
||||
@@ -111,6 +111,7 @@ interface ClaudeSessionOriginInfo {
|
||||
origin: ClaudeSessionOrigin;
|
||||
sessionName?: string;
|
||||
starred?: boolean;
|
||||
contextUsage?: number;
|
||||
}
|
||||
|
||||
interface ClaudeSessionOriginsData {
|
||||
@@ -1457,6 +1458,27 @@ export function registerClaudeHandlers(deps: ClaudeHandlerDependencies): void {
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('claude:updateSessionContextUsage', withIpcErrorLogging(
|
||||
handlerOpts('updateSessionContextUsage', ORIGINS_LOG_CONTEXT),
|
||||
async (projectPath: string, agentSessionId: string, contextUsage: number) => {
|
||||
const origins = claudeSessionOriginsStore.get('origins', {});
|
||||
if (!origins[projectPath]) {
|
||||
origins[projectPath] = {};
|
||||
}
|
||||
const existing = origins[projectPath][agentSessionId];
|
||||
if (typeof existing === 'string') {
|
||||
origins[projectPath][agentSessionId] = { origin: existing, contextUsage };
|
||||
} else if (existing) {
|
||||
origins[projectPath][agentSessionId] = { ...existing, contextUsage };
|
||||
} else {
|
||||
origins[projectPath][agentSessionId] = { origin: 'user', contextUsage };
|
||||
}
|
||||
claudeSessionOriginsStore.set('origins', origins);
|
||||
// Don't log - this updates frequently and would spam logs
|
||||
return true;
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('claude:getSessionOrigins', withIpcErrorLogging(
|
||||
handlerOpts('getSessionOrigins', ORIGINS_LOG_CONTEXT),
|
||||
async (projectPath: string) => {
|
||||
|
||||
@@ -87,6 +87,7 @@ interface ClaudeSessionOriginInfo {
|
||||
origin: ClaudeSessionOrigin;
|
||||
sessionName?: string;
|
||||
starred?: boolean;
|
||||
contextUsage?: number;
|
||||
}
|
||||
interface ClaudeSessionOriginsData {
|
||||
origins: Record<string, Record<string, ClaudeSessionOrigin | ClaudeSessionOriginInfo>>;
|
||||
|
||||
@@ -863,6 +863,9 @@ contextBridge.exposeInMainWorld('maestro', {
|
||||
logDeprecationWarning('updateSessionStarred');
|
||||
return ipcRenderer.invoke('claude:updateSessionStarred', projectPath, agentSessionId, starred);
|
||||
},
|
||||
updateSessionContextUsage: (projectPath: string, agentSessionId: string, contextUsage: number) => {
|
||||
return ipcRenderer.invoke('claude:updateSessionContextUsage', projectPath, agentSessionId, contextUsage);
|
||||
},
|
||||
getSessionOrigins: (projectPath: string) => {
|
||||
logDeprecationWarning('getSessionOrigins');
|
||||
return ipcRenderer.invoke('claude:getSessionOrigins', projectPath);
|
||||
@@ -2348,7 +2351,8 @@ export interface MaestroAPI {
|
||||
registerSessionOrigin: (projectPath: string, agentSessionId: string, origin: 'user' | 'auto', sessionName?: string) => Promise<boolean>;
|
||||
updateSessionName: (projectPath: string, agentSessionId: string, sessionName: string) => Promise<boolean>;
|
||||
updateSessionStarred: (projectPath: string, agentSessionId: string, starred: boolean) => Promise<boolean>;
|
||||
getSessionOrigins: (projectPath: string) => Promise<Record<string, 'user' | 'auto' | { origin: 'user' | 'auto'; sessionName?: string; starred?: boolean }>>;
|
||||
updateSessionContextUsage: (projectPath: string, agentSessionId: string, contextUsage: number) => Promise<boolean>;
|
||||
getSessionOrigins: (projectPath: string) => Promise<Record<string, 'user' | 'auto' | { origin: 'user' | 'auto'; sessionName?: string; starred?: boolean; contextUsage?: number }>>;
|
||||
deleteMessagePair: (projectPath: string, sessionId: string, userMessageUuid: string, fallbackContent?: string) => Promise<{ success: boolean; linesRemoved?: number; error?: string }>;
|
||||
};
|
||||
agentSessions: {
|
||||
|
||||
@@ -46,6 +46,7 @@ type StoredOriginData =
|
||||
origin: AgentSessionOrigin;
|
||||
sessionName?: string;
|
||||
starred?: boolean;
|
||||
contextUsage?: number;
|
||||
};
|
||||
|
||||
export interface ClaudeSessionOriginsData {
|
||||
@@ -1143,6 +1144,27 @@ export class ClaudeSessionStorage implements AgentSessionStorage {
|
||||
logger.debug(`Updated Claude session starred: ${agentSessionId} = ${starred}`, LOG_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the context usage percentage of a session
|
||||
* This persists the last known context window usage so it can be restored on resume
|
||||
*/
|
||||
updateSessionContextUsage(projectPath: string, agentSessionId: string, contextUsage: number): void {
|
||||
const origins = this.originsStore.get('origins', {});
|
||||
if (!origins[projectPath]) {
|
||||
origins[projectPath] = {};
|
||||
}
|
||||
const existing = origins[projectPath][agentSessionId];
|
||||
if (typeof existing === 'string') {
|
||||
origins[projectPath][agentSessionId] = { origin: existing, contextUsage };
|
||||
} else if (existing) {
|
||||
origins[projectPath][agentSessionId] = { ...existing, contextUsage };
|
||||
} else {
|
||||
origins[projectPath][agentSessionId] = { origin: 'user', contextUsage };
|
||||
}
|
||||
this.originsStore.set('origins', origins);
|
||||
// Don't log this - it updates frequently and would spam logs
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all origin info for a project
|
||||
*/
|
||||
@@ -1160,6 +1182,7 @@ export class ClaudeSessionStorage implements AgentSessionStorage {
|
||||
origin: data.origin,
|
||||
sessionName: data.sessionName,
|
||||
starred: data.starred,
|
||||
contextUsage: data.contextUsage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,17 @@ Maestro is an Electron desktop application for managing multiple AI coding assis
|
||||
- **Current Directory:** {{CWD}}
|
||||
- **Git Branch:** {{GIT_BRANCH}}
|
||||
- **Session ID:** {{AGENT_SESSION_ID}}
|
||||
- **History File:** {{AGENT_HISTORY_PATH}}
|
||||
|
||||
## Task Recall
|
||||
|
||||
Your session history is stored at `{{AGENT_HISTORY_PATH}}`. When you need context about previously completed tasks, read this JSON file and parse the `entries` array. Each entry contains:
|
||||
- `summary`: Brief description of the task
|
||||
- `timestamp`: When the task was completed (Unix ms)
|
||||
- `type`: `AUTO` (automated) or `USER` (interactive)
|
||||
- `success`: Whether the task succeeded
|
||||
|
||||
To recall recent work, read the file and scan the most recent entries by timestamp.
|
||||
|
||||
## Auto-run Documents
|
||||
|
||||
|
||||
@@ -2247,6 +2247,22 @@ function MaestroConsoleInner() {
|
||||
batchedUpdater.updateContextUsage(actualSessionId, contextPercentage);
|
||||
batchedUpdater.updateCycleTokens(actualSessionId, usageStats.outputTokens);
|
||||
|
||||
// Persist context usage for Claude sessions so it survives app restart
|
||||
// Only persist if we have a valid tab with an agent session ID
|
||||
if (isClaudeUsage && tabId && sessionForUsage?.projectRoot) {
|
||||
const tab = sessionForUsage.aiTabs?.find(t => t.id === tabId);
|
||||
if (tab?.agentSessionId) {
|
||||
// Fire and forget - don't await to avoid blocking the UI
|
||||
window.maestro.claude.updateSessionContextUsage(
|
||||
sessionForUsage.projectRoot,
|
||||
tab.agentSessionId,
|
||||
contextPercentage
|
||||
).catch(() => {
|
||||
// Silently ignore errors - this is a best-effort persistence
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update persistent global stats (not batched - this is a separate concern)
|
||||
updateGlobalStatsRef.current({
|
||||
totalInputTokens: usageStats.inputTokens,
|
||||
|
||||
@@ -502,14 +502,18 @@ export function AgentSessionsBrowser({
|
||||
};
|
||||
|
||||
// Helper to build UsageStats from session data
|
||||
// NOTE: Token counts from stored sessions are LIFETIME TOTALS, not current context.
|
||||
// We only preserve the cost for display. Token fields are set to 0 so context window
|
||||
// starts at 0% and gets updated when Claude Code sends fresh usage data.
|
||||
// This fixes the bug where resumed sessions showed 100% context due to stale cumulative tokens.
|
||||
const buildUsageStats = useCallback((session: ClaudeSession): UsageStats | undefined => {
|
||||
// Only build if we have token data
|
||||
if (!session.inputTokens && !session.outputTokens) return undefined;
|
||||
// Only build if we have cost data (tokens are intentionally zeroed)
|
||||
if (!session.costUsd) return undefined;
|
||||
return {
|
||||
inputTokens: session.inputTokens || 0,
|
||||
outputTokens: session.outputTokens || 0,
|
||||
cacheReadInputTokens: session.cacheReadTokens || 0,
|
||||
cacheCreationInputTokens: session.cacheCreationTokens || 0,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
totalCostUsd: session.costUsd || 0,
|
||||
contextWindow: 200000, // Default Claude context window
|
||||
};
|
||||
|
||||
3
src/renderer/global.d.ts
vendored
3
src/renderer/global.d.ts
vendored
@@ -810,7 +810,8 @@ interface MaestroAPI {
|
||||
registerSessionOrigin: (projectPath: string, agentSessionId: string, origin: 'user' | 'auto', sessionName?: string) => Promise<boolean>;
|
||||
updateSessionName: (projectPath: string, agentSessionId: string, sessionName: string) => Promise<boolean>;
|
||||
updateSessionStarred: (projectPath: string, agentSessionId: string, starred: boolean) => Promise<boolean>;
|
||||
getSessionOrigins: (projectPath: string) => Promise<Record<string, 'user' | 'auto' | { origin: 'user' | 'auto'; sessionName?: string; starred?: boolean }>>;
|
||||
updateSessionContextUsage: (projectPath: string, agentSessionId: string, contextUsage: number) => Promise<boolean>;
|
||||
getSessionOrigins: (projectPath: string) => Promise<Record<string, 'user' | 'auto' | { origin: 'user' | 'auto'; sessionName?: string; starred?: boolean; contextUsage?: number }>>;
|
||||
getAllNamedSessions: () => Promise<Array<{
|
||||
agentId: string;
|
||||
agentSessionId: string;
|
||||
|
||||
@@ -196,16 +196,16 @@ export function useAgentSessionManagement(
|
||||
}));
|
||||
}
|
||||
|
||||
// Look up starred status and session name from stores if not provided
|
||||
// Look up starred status, session name, and context usage from stores if not provided
|
||||
let isStarred = starred ?? false;
|
||||
let name = sessionName ?? null;
|
||||
let storedContextUsage: number | undefined;
|
||||
let finalUsageStats = usageStats;
|
||||
|
||||
const shouldLookupOrigins = activeSession.toolType === 'claude-code'
|
||||
&& (starred === undefined || sessionName === undefined);
|
||||
|
||||
if (shouldLookupOrigins) {
|
||||
// Always look up origins for Claude sessions to get contextUsage (and name/starred if not provided)
|
||||
if (activeSession.toolType === 'claude-code') {
|
||||
try {
|
||||
// Look up session metadata from session origins (name and starred)
|
||||
// Look up session metadata from session origins (name, starred, contextUsage)
|
||||
// Note: getSessionOrigins is still Claude-specific until we add generic origin tracking
|
||||
// Use projectRoot (not cwd) for consistent session storage access
|
||||
const origins = await window.maestro.claude.getSessionOrigins(activeSession.projectRoot);
|
||||
@@ -217,12 +217,31 @@ export function useAgentSessionManagement(
|
||||
if (starred === undefined && originData.starred !== undefined) {
|
||||
isStarred = originData.starred;
|
||||
}
|
||||
if (originData.contextUsage !== undefined) {
|
||||
storedContextUsage = originData.contextUsage;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[handleResumeSession] Failed to lookup starred/named status:', error);
|
||||
console.warn('[handleResumeSession] Failed to lookup session metadata:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have stored contextUsage, set token values to reproduce that percentage
|
||||
// The context calculation is: (inputTokens + cacheRead + cacheCreation) / contextWindow * 100
|
||||
// So we set inputTokens = contextUsage * contextWindow / 100 to get the correct percentage
|
||||
if (storedContextUsage !== undefined && storedContextUsage > 0) {
|
||||
const contextWindow = finalUsageStats?.contextWindow || 200000;
|
||||
finalUsageStats = {
|
||||
inputTokens: Math.round(storedContextUsage * contextWindow / 100),
|
||||
outputTokens: finalUsageStats?.outputTokens || 0,
|
||||
cacheReadInputTokens: 0,
|
||||
cacheCreationInputTokens: 0,
|
||||
totalCostUsd: finalUsageStats?.totalCostUsd || 0,
|
||||
contextWindow,
|
||||
reasoningTokens: finalUsageStats?.reasoningTokens,
|
||||
};
|
||||
}
|
||||
|
||||
// Update the session and switch to AI mode
|
||||
// IMPORTANT: Use functional update to get fresh session state and avoid race conditions
|
||||
setSessions(prev => prev.map(s => {
|
||||
@@ -234,7 +253,7 @@ export function useAgentSessionManagement(
|
||||
logs: messages,
|
||||
name,
|
||||
starred: isStarred,
|
||||
usageStats,
|
||||
usageStats: finalUsageStats,
|
||||
saveToHistory: defaultSaveToHistory,
|
||||
showThinking: defaultShowThinking
|
||||
});
|
||||
|
||||
@@ -729,6 +729,14 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
}
|
||||
}
|
||||
|
||||
// Get history file path for task recall
|
||||
let historyFilePath: string | undefined;
|
||||
try {
|
||||
historyFilePath = await window.maestro.history.getFilePath(freshSession.id) || undefined;
|
||||
} catch {
|
||||
// Ignore history errors
|
||||
}
|
||||
|
||||
// Substitute template variables in the system prompt
|
||||
console.log('[useInputProcessing] Template substitution context:', {
|
||||
sessionId: freshSession.id,
|
||||
@@ -737,10 +745,12 @@ export function useInputProcessing(deps: UseInputProcessingDeps): UseInputProces
|
||||
fullPath: freshSession.fullPath,
|
||||
cwd: freshSession.cwd,
|
||||
parentSessionId: freshSession.parentSessionId,
|
||||
historyFilePath,
|
||||
});
|
||||
const substitutedSystemPrompt = substituteTemplateVariables(maestroSystemPrompt, {
|
||||
session: freshSession,
|
||||
gitBranch,
|
||||
historyFilePath,
|
||||
});
|
||||
|
||||
// Prepend system prompt to user's message
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* {{AGENT_PATH}} - Agent home directory path (full path to project)
|
||||
* {{AGENT_GROUP}} - Agent's group name (if grouped)
|
||||
* {{AGENT_SESSION_ID}} - Agent session ID (for conversation continuity)
|
||||
* {{AGENT_HISTORY_PATH}} - Path to agent's history JSON file (for task recall)
|
||||
* {{TAB_NAME}} - Custom tab name (alias: SESSION_NAME)
|
||||
* {{TOOL_TYPE}} - Agent type (claude-code, aider, etc.)
|
||||
*
|
||||
@@ -65,12 +66,15 @@ export interface TemplateContext {
|
||||
// Auto Run document context
|
||||
documentName?: string;
|
||||
documentPath?: string;
|
||||
// History file path for task recall
|
||||
historyFilePath?: string;
|
||||
}
|
||||
|
||||
// List of all available template variables for documentation (alphabetically sorted)
|
||||
// Variables marked as autoRunOnly are only shown in Auto Run contexts, not in AI Commands settings
|
||||
export const TEMPLATE_VARIABLES = [
|
||||
{ variable: '{{AGENT_GROUP}}', description: 'Agent group name' },
|
||||
{ variable: '{{AGENT_HISTORY_PATH}}', description: 'History file path (task recall)' },
|
||||
{ variable: '{{AGENT_NAME}}', description: 'Agent name' },
|
||||
{ variable: '{{AGENT_PATH}}', description: 'Agent home directory path' },
|
||||
{ variable: '{{AGENT_SESSION_ID}}', description: 'Agent session ID' },
|
||||
@@ -106,7 +110,7 @@ export function substituteTemplateVariables(
|
||||
template: string,
|
||||
context: TemplateContext
|
||||
): string {
|
||||
const { session, gitBranch, groupName, autoRunFolder, loopNumber, documentName, documentPath } = context;
|
||||
const { session, gitBranch, groupName, autoRunFolder, loopNumber, documentName, documentPath, historyFilePath } = context;
|
||||
const now = new Date();
|
||||
|
||||
// Build replacements map
|
||||
@@ -116,6 +120,7 @@ export function substituteTemplateVariables(
|
||||
'AGENT_PATH': session.fullPath || session.projectRoot || session.cwd,
|
||||
'AGENT_GROUP': groupName || '',
|
||||
'AGENT_SESSION_ID': session.agentSessionId || '',
|
||||
'AGENT_HISTORY_PATH': historyFilePath || '',
|
||||
'TAB_NAME': session.name,
|
||||
'TOOL_TYPE': session.toolType,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user