Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/skills/coc-knowledge/references/dashboard-spa.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Each tool's internal sub-tab/hash scheme (e.g. `#skills/installed`,

## Memory Route

The top-level `#memory` route is embedded in the Admin shell's Knowledge group and renders `MemoryV2Panel` in the right pane. The panel root owns the stable `#view-memory` id. `MemorySubTab` values are `facts`, `review`, `episodes`, and `settings`; hash links such as `#memory/review` and `#memory/settings` select the matching V2 tab. Legacy bounded-memory/config/explore-cache panels are not rendered on the Memory route. Repo settings still use `RepoMemorySection` for repo-scoped bounded memory and raw memory inspection.
The top-level `#memory` route is embedded in the Admin shell's Knowledge group and renders `MemoryV2Panel` in the right pane. The panel root owns the stable `#view-memory` id. `MemorySubTab` values are `facts`, `review`, `episodes`, and `settings`; hash links such as `#memory/review` and `#memory/settings` select the matching V2 tab. The legacy memory-config panel is not rendered on the Memory route (the tool-call/explore cache has been removed). Repo settings still use `RepoMemorySection` for repo-scoped bounded memory and raw memory inspection.

`MemoryV2Panel` lists the global scope plus registered workspace scopes, lets users enable/disable the active scope from the Settings tab, exports JSON, and wipes the active scope after confirmation. The tab content is split into `MemoryV2FactsTab`, `MemoryV2ReviewTab`, `MemoryV2EpisodesTab`, and `MemoryV2SettingsTab`.

Expand Down
79 changes: 0 additions & 79 deletions packages/coc-client/src/contracts/memory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export type MemoryLevel = 'system' | 'repo' | 'git-remote';

export interface MemoryConfig {
storageDir: string;
backend: string;
Expand All @@ -9,83 +7,6 @@ export interface MemoryConfig {
recording?: { enabled: boolean };
}

// ── Explore-cache types ───────────────────────────────────────────────────────

export interface ExploreCacheStats {
rawCount: number;
consolidatedExists: boolean;
consolidatedCount: number;
lastAggregation: string | null;
}

export interface ExploreCacheRepoEntry extends ExploreCacheStats {
hash: string;
path?: string;
name?: string;
remoteUrl?: string;
}

export interface ExploreCacheGitRemoteEntry extends ExploreCacheStats {
hash: string;
remoteUrl?: string;
name?: string;
}

export interface ExploreCacheLevelsOverview {
system: ExploreCacheStats;
repos: ExploreCacheRepoEntry[];
gitRemotes: ExploreCacheGitRemoteEntry[];
}

export interface ExploreCacheRawListResponse {
level: MemoryLevel;
hash?: string;
files: string[];
}

export interface ToolCallQAEntry {
id: string;
toolName: string;
question: string;
answer: string;
args: Record<string, unknown>;
gitHash?: string;
timestamp: string;
}

export interface ConsolidatedIndexEntry {
id: string;
question: string;
topics: string[];
toolSources: string[];
createdAt: string;
hitCount: number;
gitHash?: string;
}

export interface ExploreCacheConsolidatedListResponse {
level: MemoryLevel;
hash?: string;
entries: ConsolidatedIndexEntry[];
}

export interface ConsolidatedEntryWithAnswer extends ConsolidatedIndexEntry {
answer: string;
}

export interface ToolCallCacheStats {
rawCount: number;
consolidatedCount: number;
consolidatedExists: boolean;
lastAggregation: string | null;
}

export interface AggregateToolCallsResponse {
aggregated?: boolean;
rawCount: number;
consolidatedCount: number;
}

export type MemoryScope = 'global' | 'workspace';

export type MemoryFactStatus = 'active' | 'review' | 'rejected' | 'archived';
Expand Down
49 changes: 0 additions & 49 deletions packages/coc-client/src/domains/memory.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import { CocApiError } from '../errors';
import type {
AggregateToolCallsResponse,
ConsolidatedEntryWithAnswer,
ExploreCacheConsolidatedListResponse,
ExploreCacheLevelsOverview,
ExploreCacheRawListResponse,
ListFactsOptions,
MemoryConfig,
MemoryEpisode,
MemoryFact,
MemoryLevel,
MemoryScopeInfo,
MemoryV2ExportData,
ToolCallCacheStats,
ToolCallQAEntry,
} from '../contracts';
import type { RequestAdapter } from '../types';
import { encodePathSegment } from '../url';

export interface MemoryHashOptions {
hash?: string;
}

export class MemoryClient {
constructor(private readonly transport: RequestAdapter) {}

Expand All @@ -32,42 +19,6 @@ export class MemoryClient {
replaceConfig(config: MemoryConfig): Promise<MemoryConfig> {
return this.transport.request<MemoryConfig>('/memory/config', { method: 'PUT', body: { ...config } });
}

getExploreCacheLevels(): Promise<ExploreCacheLevelsOverview> {
return this.transport.request<ExploreCacheLevelsOverview>('/memory/explore-cache/levels');
}

listExploreCacheRaw(level: MemoryLevel = 'system', options?: MemoryHashOptions): Promise<ExploreCacheRawListResponse> {
return this.transport.request<ExploreCacheRawListResponse>('/memory/explore-cache/raw', {
query: { level, hash: options?.hash },
});
}

getExploreCacheRaw(filename: string, level: MemoryLevel = 'system', options?: MemoryHashOptions): Promise<ToolCallQAEntry> {
return this.transport.request<ToolCallQAEntry>(`/memory/explore-cache/raw/${encodePathSegment(filename)}`, {
query: { level, hash: options?.hash },
});
}

listExploreCacheConsolidated(level: MemoryLevel = 'system', options?: MemoryHashOptions): Promise<ExploreCacheConsolidatedListResponse> {
return this.transport.request<ExploreCacheConsolidatedListResponse>('/memory/explore-cache/consolidated', {
query: { level, hash: options?.hash },
});
}

getExploreCacheConsolidated(id: string, level: MemoryLevel = 'system', options?: MemoryHashOptions): Promise<ConsolidatedEntryWithAnswer> {
return this.transport.request<ConsolidatedEntryWithAnswer>(`/memory/explore-cache/consolidated/${encodePathSegment(id)}`, {
query: { level, hash: options?.hash },
});
}

getToolCallCacheStats(): Promise<ToolCallCacheStats> {
return this.transport.request<ToolCallCacheStats>('/memory/aggregate-tool-calls/stats');
}

aggregateToolCalls(): Promise<AggregateToolCallsResponse> {
return this.transport.request<AggregateToolCallsResponse>('/memory/aggregate-tool-calls', { method: 'POST' });
}
}

function memoryV2Path(workspaceId: string, suffix = ''): string {
Expand Down
5 changes: 2 additions & 3 deletions packages/coc/docs/queue-executor-bridge-symbol-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,13 @@ Exported. Implements `TaskExecutor` (forge) and `QueueExecutorBridge`.
| `followUpSuggestions: { enabled, count }` | private | `base-executor.ts` | Config forwarded to follow-up/chat executors |
| `getWsServer?: () => ProcessWebSocketServer` | private | `base-executor.ts` | Used in `executeResolveComments` |
| `queueManager?: TaskQueueManager` | private | `base-executor.ts` | Injected by factory; used in title generation and follow-up requeue |
| `toolCallCacheStore: FileToolCallCacheStore` | private | `base-executor.ts` | Tool-call Q&A capture |
| `registry: TaskStrategyRegistry` | private | `base-executor.ts` | Dispatch for `run-script` / `replicate-template` |

### 5b. Public Methods

| Method | Target module | Dependencies on other in-file symbols |
|---|---|---|
| `constructor` | `base-executor.ts` | All instance fields; `RunScriptStrategy`, `ReplicateTemplateStrategy`, `resolveToolCallCacheOptions` |
| `constructor` | `base-executor.ts` | All instance fields; `RunScriptStrategy`, `ReplicateTemplateStrategy` |
| `setQueueManager(qm)` | `base-executor.ts` | `queueManager` field |
| `execute(task)` ← `TaskExecutor` contract | `base-executor.ts` | `extractPrompt`, `applySkillContent`, `getWorkingDirectory`, `executeByType`, `generateTitleIfNeeded`, `cleanupSession`, `persistOutput`, `TERMINAL_STATUSES`, `sessions`, `ImageBlobStore` |
| `cancel(taskId)` ← `TaskExecutor` contract | `base-executor.ts` | `cancelledTasks` |
Expand All @@ -118,7 +117,7 @@ Exported. Implements `TaskExecutor` (forge) and `QueueExecutorBridge`.
| `applySkillContent(prompt, task)` | `executors/prompt-builder.ts` | — (pure transformation) |
| `buildExecutionContext(task)` | `base-executor.ts` | `getWorkingDirectory`, `store`, `approvePermissions` |
| `executeByType(task, prompt)` | `base-executor.ts` (dispatcher) | `executeRunPipeline`, `executeTaskGeneration`, `executeResolveComments`, `executeWithAI`, `buildFollowUpSuggestionsAddon`, `buildModeSystemMessage`, `withRepoInstructions`, `getWorkingDirectory`, `resolveSkillConfig`, `registry` |
| `executeWithAI(task, prompt, options)` | `base-executor.ts` | `getOrCreateSession`, `buildToolEventHandler`, `checkThrottleAndFlush`, `appendTimelineItem`, `toAgentMode`, `resolveSkillConfig`, `getWorkingDirectory`, `ImageBlobStore`, `saveImagesToTempFiles`, `cleanupTempDir`, `ToolCallCapture` |
| `executeWithAI(task, prompt, options)` | `base-executor.ts` | `getOrCreateSession`, `buildToolEventHandler`, `checkThrottleAndFlush`, `appendTimelineItem`, `toAgentMode`, `resolveSkillConfig`, `getWorkingDirectory`, `ImageBlobStore`, `saveImagesToTempFiles`, `cleanupTempDir` |
| `executeTaskGeneration(task)` | `executors/plan-executor.ts` | `executeWithAI`, `resolveTaskRoot`, `resolveWorkspaceIdForPath`, `gatherFeatureContext` (forge), prompt-builder helpers (forge) |
| `executeRunPipeline(task)` | `executors/workflow-executor.ts` | `createCLIAIInvoker`, `compileToWorkflow`/`executeWorkflow`/`flattenWorkflowResult` (forge), `store` |
| `executeResolveComments(task)` | `executors/chat-executor.ts` | `executeWithAI`, `store`, `getWsServer`, `dataDir`, `TaskCommentsManager` |
Expand Down
26 changes: 1 addition & 25 deletions packages/coc/src/ai-invoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import {
approveAllPermissions,
denyAllPermissions,
DEFAULT_AI_TIMEOUT_MS,
ToolCallCapture,
FileToolCallCacheStore,
resolveToolCallCacheOptions,
TASK_FILTER,
} from '@plusplusoneplusplus/forge';
import type {
AIInvoker,
Expand Down Expand Up @@ -51,10 +47,6 @@ export interface CLIAIInvokerOptions {
onChunk?: (chunk: string) => void;
/** Custom tools to expose to the AI session */
tools?: Tool<any>[];
/** Override for the cache store data dir; defaults to ~/.coc/memory */
cacheDataDir?: string;
/** Current git HEAD hash for staleness tracking; optional — cache still works without it */
gitHash?: string;
/** Optional AI service override; if omitted, resolves via sdkServiceRegistry */
aiService?: import('@plusplusoneplusplus/forge').ISDKService;
}
Expand Down Expand Up @@ -143,23 +135,7 @@ export function createCLIAIInvoker(options: CLIAIInvokerOptions = {}): AIInvoker
}
};

const store = new FileToolCallCacheStore(
resolveToolCallCacheOptions(options.workingDirectory, options.cacheDataDir),
);
const capture = new ToolCallCapture(store, TASK_FILTER, {
gitHash: options.gitHash,
});
const captureHandler = capture.createToolEventHandler();

return (prompt: string, invokerOptions?: AIInvokerOptions): Promise<AIInvokerResult> => {
const mergedOptions: AIInvokerOptions = {
...invokerOptions,
onToolEvent: invokerOptions?.onToolEvent
? (event) => { invokerOptions.onToolEvent!(event); captureHandler(event); }
: captureHandler,
};
return invoker(prompt, mergedOptions);
};
return invoker;
}

/**
Expand Down
39 changes: 0 additions & 39 deletions packages/coc/src/server/admin/admin-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1014,45 +1014,6 @@ The plan file should include:
description: 'Injection/exfiltration patterns blocked before memory writes are accepted',
text: SECURITY_PATTERNS_DESCRIPTION,
},
'tool-call-cache': {
id: 'tool-call-cache',
title: 'Tool-call Cache',
group: 'Memory',
source: 'forge/memory/tool-call-cache-aggregator.ts',
description: '7-step clustering/dedup instructions, output JSON schema',
text: `You are a tool-call cache consolidator. Your job is to merge raw Q&A pairs into a deduplicated, clustered, normalized index.

## Existing Consolidated Entries
\${existingSection}

## New Raw Entries (\${count} entries)
\${rawSection}

## Instructions
1. **Deduplicate**: Merge entries with near-identical questions (e.g. "list files in src" vs "list files in the src directory"). Keep the best answer.
2. **Cluster by topic**: Assign 1-3 topic tags per entry (e.g. ["file-structure", "git"], ["testing", "vitest"]).
3. **Normalize questions**: Rewrite questions to be generic and reusable. Remove repo-specific paths where possible, but preserve the semantic intent.
4. **Preserve tool sources**: Union all toolSources from merged entries.
5. **Set confidence**: 1.0 for entries with consistent answers, lower for entries with conflicting answers.
6. **Merge with existing**: If an existing consolidated entry covers the same question, update its answer and increment hitCount.
7. **Prune**: Drop entries that appear trivial or overly specific to a single context.

## Output Format
Respond with ONLY a JSON array of consolidated entries. No markdown fences, no explanation.
Each entry must have this exact shape:
\`\`\`
{
"id": "<unique-kebab-case-id>",
"question": "<normalized question>",
"answer": "<best answer>",
"topics": ["<topic1>", "<topic2>"],
"gitHash": "<most-recent-git-hash-or-null>",
"toolSources": ["<tool1>", "<tool2>"],
"createdAt": "<ISO-8601>",
"hitCount": <number>
}
\`\`\``,
},
'follow-up-suggestions': {
id: 'follow-up-suggestions',
title: 'Follow-up Suggestions',
Expand Down
23 changes: 1 addition & 22 deletions packages/coc/src/server/executors/chat-base-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,15 @@ import type {
SDKInvocationResult,
SystemMessageConfig,
TimelineItem,
ToolEvent,
} from '@plusplusoneplusplus/forge';
import type { Tool } from '@plusplusoneplusplus/coc-agent-sdk';
import {
approveAllPermissions,
FileToolCallCacheStore,
getLogger,
LogCategory,
mergeConsecutiveContentItems,
modelMetadataStore,
resolveReasoningSelection,
TASK_FILTER,
ToolCallCapture,
rewriteLargePrompt,
toQueueProcessId,
} from '@plusplusoneplusplus/forge';
Expand Down Expand Up @@ -131,8 +127,6 @@ export interface ChatModeExecutorOptions {
followUpSuggestions: { enabled: boolean; count: number };
/** Ask-user interactive tool configuration */
askUser?: { enabled: boolean };
/** Shared store for tool-call Q&A capture (explore cache) */
toolCallCacheStore: FileToolCallCacheStore;
/** Resolve skill configuration for a workspace */
resolveSkillConfig: (wsId: string | undefined, workDir?: string) => Promise<{ skillDirectories?: string[]; disabledSkills?: string[] }>;
/** Resolve workspace ID for a root path */
Expand Down Expand Up @@ -186,7 +180,6 @@ export abstract class ChatBaseExecutor extends BaseExecutor {
protected readonly defaultTimeoutMs: number;
protected readonly followUpSuggestions: { enabled: boolean; count: number };
protected readonly askUser: { enabled: boolean };
protected readonly toolCallCacheStore: FileToolCallCacheStore;
protected readonly resolveSkillConfigFn: (wsId: string | undefined, workDir?: string) => Promise<{ skillDirectories?: string[]; disabledSkills?: string[] }>;
protected readonly resolveWorkspaceIdForPathFn: (rootPath: string) => Promise<string>;
protected readonly getLoopInfra?: () => LoopInfraDeps | undefined;
Expand All @@ -204,7 +197,6 @@ export abstract class ChatBaseExecutor extends BaseExecutor {
this.defaultTimeoutMs = options.defaultTimeoutMs;
this.followUpSuggestions = options.followUpSuggestions;
this.askUser = options.askUser ?? { enabled: false };
this.toolCallCacheStore = options.toolCallCacheStore;
this.resolveSkillConfigFn = options.resolveSkillConfig;
this.resolveWorkspaceIdForPathFn = options.resolveWorkspaceIdForPath;
this.getLoopInfra = options.getLoopInfra;
Expand Down Expand Up @@ -506,14 +498,6 @@ export abstract class ChatBaseExecutor extends BaseExecutor {
resolveSelectedSkillReferences(selectedSkillNames, skillDirectories, disabledSkills),
);

let captureHandler: ((event: ToolEvent) => void) | undefined;
try {
const capture = new ToolCallCapture(this.toolCallCacheStore, TASK_FILTER);
captureHandler = capture.createToolEventHandler();
} catch (err) {
getLogger().warn(LogCategory.AI, `[ChatModeExecutor] ToolCallCapture setup failed: ${err}`);
}

const toolEventHandler = this.buildToolEventHandler(
processId,
() => 1,
Expand Down Expand Up @@ -577,12 +561,7 @@ export abstract class ChatBaseExecutor extends BaseExecutor {
}
this.checkThrottleAndFlush(processId);
},
onToolEvent: captureHandler
? (event: ToolEvent) => {
try { toolEventHandler(event); } catch { /* non-fatal */ }
try { captureHandler!(event); } catch { /* non-fatal */ }
}
: toolEventHandler,
onToolEvent: toolEventHandler,
onBackgroundTasksChanged: this.buildBackgroundTaskHandler(processId),
onMcpOAuthRequired: (() => {
const manager = this.getMcpOauthManager?.();
Expand Down
4 changes: 1 addition & 3 deletions packages/coc/src/server/executors/executor-registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ConversationTurn, ISDKService, FileToolCallCacheStore, ProcessStore, QueuedTask } from '@plusplusoneplusplus/forge';
import type { ConversationTurn, ISDKService, ProcessStore, QueuedTask } from '@plusplusoneplusplus/forge';
import { approveAllPermissions, toQueueProcessId } from '@plusplusoneplusplus/forge';
import type { ChatPayload } from '../tasks/task-types';
import { isChatPayload, isChatFollowUp, isRunWorkflowPayload, isRunScriptPayload, hasTaskGenerationContext, hasResolveCommentsContext, hasResolveDiffCommentsMultiContext, hasReplicationContext, hasCommitChatContext, hasNoteChatContext, hasNoteCreateContext, hasClassifyDiffContext, isPrClassificationPayload } from '../tasks/task-types';
Expand Down Expand Up @@ -40,7 +40,6 @@ export interface ExecutorRegistryOptions {
* receiving the RuntimeConfigService directly.
*/
resolveAiServiceForProvider?: (provider: import('../tasks/task-types').ChatProvider) => ISDKService;
toolCallCacheStore: FileToolCallCacheStore;
resolveSkillConfig: (wsId: string | undefined, workDir?: string) => Promise<{ skillDirectories?: string[]; disabledSkills?: string[] }>;
resolveWorkspaceIdForPath: (rootPath: string) => Promise<string>;
onTitleNeeded: (processId: string, turns: ConversationTurn[]) => void;
Expand Down Expand Up @@ -92,7 +91,6 @@ export class ExecutorRegistry {
defaultTimeoutMs: options.defaultTimeoutMs,
followUpSuggestions: options.followUpSuggestions,
askUser: options.askUser,
toolCallCacheStore: options.toolCallCacheStore,
resolveSkillConfig: options.resolveSkillConfig,
resolveWorkspaceIdForPath: options.resolveWorkspaceIdForPath,
getLoopInfra: options.getLoopInfra,
Expand Down
Loading
Loading