wave-agent-sdk 1.2.0 → 1.3.0
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.d.ts +58 -4
- package/dist/agent.js +91 -19
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/skills/settings.js +1 -12
- package/dist/builtin/skills/wave-daemon.d.ts +1 -0
- package/dist/builtin/skills/wave-daemon.js +194 -0
- package/dist/constants/images.d.ts +26 -0
- package/dist/constants/images.js +26 -0
- package/dist/constants/index.d.ts +16 -0
- package/dist/constants/index.js +16 -0
- package/dist/constants/memory.d.ts +26 -0
- package/dist/constants/memory.js +34 -0
- package/dist/constants/messages.d.ts +11 -0
- package/dist/constants/messages.js +11 -0
- package/dist/constants/plugins.d.ts +8 -0
- package/dist/constants/plugins.js +8 -0
- package/dist/constants/tools.d.ts +1 -0
- package/dist/constants/tools.js +1 -0
- package/dist/core/plugin.d.ts +53 -13
- package/dist/core/plugin.js +134 -26
- package/dist/core/session.d.ts +1 -1
- package/dist/core/session.js +1 -1
- package/dist/exec/catalog.d.ts +140 -0
- package/dist/exec/catalog.js +470 -0
- package/dist/exec/catalogAnnouncement.d.ts +89 -0
- package/dist/exec/catalogAnnouncement.js +293 -0
- package/dist/exec/constants.d.ts +51 -0
- package/dist/exec/constants.js +51 -0
- package/dist/exec/execRuntime.d.ts +55 -0
- package/dist/exec/execRuntime.js +217 -0
- package/dist/exec/workerSource.d.ts +28 -0
- package/dist/exec/workerSource.js +299 -0
- package/dist/host/index.d.ts +23 -0
- package/dist/host/index.js +23 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -1
- package/dist/managers/aiManager.js +190 -21
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/hookManager.d.ts +13 -0
- package/dist/managers/hookManager.js +31 -4
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +103 -8
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +45 -10
- package/dist/managers/mcpManager.js +103 -1
- package/dist/managers/messageManager.d.ts +48 -5
- package/dist/managers/messageManager.js +107 -21
- package/dist/managers/permissionManager.d.ts +40 -0
- package/dist/managers/permissionManager.js +63 -8
- package/dist/managers/pluginManager.d.ts +46 -2
- package/dist/managers/pluginManager.js +117 -11
- package/dist/managers/pluginScopeManager.d.ts +15 -2
- package/dist/managers/pluginScopeManager.js +20 -1
- package/dist/managers/skillManager.d.ts +19 -0
- package/dist/managers/skillManager.js +44 -0
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +35 -3
- package/dist/managers/subagentManager.d.ts +8 -0
- package/dist/managers/subagentManager.js +20 -0
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/prompts/autoMemory.d.ts +9 -0
- package/dist/prompts/autoMemory.js +30 -31
- package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
- package/dist/prompts/autoMemoryExtraction.js +8 -111
- package/dist/prompts/memoryTypes.d.ts +63 -0
- package/dist/prompts/memoryTypes.js +191 -0
- package/dist/services/GitService.d.ts +7 -0
- package/dist/services/GitService.js +23 -0
- package/dist/services/MarketplaceService.d.ts +101 -17
- package/dist/services/MarketplaceService.js +318 -119
- package/dist/services/artifactContent.d.ts +84 -0
- package/dist/services/artifactContent.js +204 -0
- package/dist/services/artifactSession.d.ts +6 -0
- package/dist/services/artifactSession.js +17 -0
- package/dist/services/autoMemoryService.js +5 -13
- package/dist/services/configurationService.d.ts +60 -9
- package/dist/services/configurationService.js +129 -54
- package/dist/services/contentSummarizer.d.ts +15 -0
- package/dist/services/contentSummarizer.js +45 -0
- package/dist/services/execAvailability.d.ts +9 -0
- package/dist/services/execAvailability.js +32 -0
- package/dist/services/fileWatcher.js +61 -6
- package/dist/services/initializationService.js +19 -15
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +84 -0
- package/dist/services/jsonlHandler.js +209 -14
- package/dist/services/memory.d.ts +3 -1
- package/dist/services/memory.js +13 -9
- package/dist/services/officialMarketplaceMirror.js +3 -2
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +16 -2
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +144 -3
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/stdio/index.d.ts +3 -1
- package/dist/stdio/index.js +3 -1
- package/dist/stdio/notificationRouter.js +1 -0
- package/dist/stdio/stdioAgent.d.ts +14 -7
- package/dist/stdio/stdioAgent.js +19 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +8 -6
- package/dist/tools/editTool.js +6 -3
- package/dist/tools/execTool.d.ts +2 -0
- package/dist/tools/execTool.js +165 -0
- package/dist/tools/grepTool.js +7 -1
- package/dist/tools/readTool.js +30 -2
- package/dist/tools/types.d.ts +34 -8
- package/dist/tools/webFetchTool.js +15 -166
- package/dist/tools/workflowTool.js +40 -8
- package/dist/tools/writeTool.js +6 -3
- package/dist/types/agent.d.ts +20 -5
- package/dist/types/configuration.d.ts +39 -1
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +39 -0
- package/dist/types/permissions.d.ts +22 -0
- package/dist/types/permissions.js +17 -0
- package/dist/types/plugins.d.ts +26 -2
- package/dist/types/skills.d.ts +15 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +43 -0
- package/dist/utils/convertMessagesForAPI.d.ts +7 -1
- package/dist/utils/convertMessagesForAPI.js +64 -14
- package/dist/utils/fileChangeReminder.d.ts +20 -0
- package/dist/utils/fileChangeReminder.js +153 -0
- package/dist/utils/fileSearch.js +4 -3
- package/dist/utils/fileUtils.d.ts +33 -0
- package/dist/utils/fileUtils.js +81 -0
- package/dist/utils/frontmatterYaml.d.ts +33 -0
- package/dist/utils/frontmatterYaml.js +192 -0
- package/dist/utils/imageBudget.d.ts +85 -0
- package/dist/utils/imageBudget.js +109 -0
- package/dist/utils/imageDimensions.d.ts +83 -0
- package/dist/utils/imageDimensions.js +232 -0
- package/dist/utils/imageProcessor.d.ts +66 -0
- package/dist/utils/imageProcessor.js +84 -0
- package/dist/utils/imageRewrite.d.ts +29 -0
- package/dist/utils/imageRewrite.js +251 -0
- package/dist/utils/markdownParser.d.ts +5 -1
- package/dist/utils/markdownParser.js +9 -51
- package/dist/utils/mcpInstructions.d.ts +61 -0
- package/dist/utils/mcpInstructions.js +126 -0
- package/dist/utils/mcpUtils.d.ts +7 -0
- package/dist/utils/mcpUtils.js +11 -2
- package/dist/utils/memoryAge.d.ts +32 -0
- package/dist/utils/memoryAge.js +47 -0
- package/dist/utils/memoryEntrypoint.d.ts +20 -0
- package/dist/utils/memoryEntrypoint.js +49 -0
- package/dist/utils/memoryIndex.d.ts +30 -0
- package/dist/utils/memoryIndex.js +76 -0
- package/dist/utils/messageOperations.d.ts +6 -2
- package/dist/utils/messageOperations.js +40 -29
- package/dist/utils/nestedMemory.d.ts +22 -0
- package/dist/utils/nestedMemory.js +61 -0
- package/dist/utils/npmTarball.d.ts +19 -0
- package/dist/utils/npmTarball.js +92 -0
- package/dist/utils/pluginSource.d.ts +37 -0
- package/dist/utils/pluginSource.js +73 -0
- package/dist/utils/ripgrep.d.ts +18 -4
- package/dist/utils/ripgrep.js +56 -4
- package/dist/utils/runtimeDeps.d.ts +35 -0
- package/dist/utils/runtimeDeps.js +426 -0
- package/dist/utils/skillParser.js +22 -52
- package/dist/utils/subagentParser.js +39 -43
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/package.json +10 -7
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { UserMessageParams, type AgentToolBlockUpdateParams, type ToolBlockUpdateCallbackParams, type AddNotificationMessageParams } from "../utils/messageOperations.js";
|
|
2
|
-
import type { Message, Usage } from "../types/index.js";
|
|
2
|
+
import type { Message, Usage, InvokedSkillRecord } from "../types/index.js";
|
|
3
3
|
import { SessionData } from "../services/session.js";
|
|
4
4
|
import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
|
|
5
5
|
import type { MemoryRule } from "../types/memoryRule.js";
|
|
6
|
+
import { type NestedMemoryFile } from "../utils/nestedMemory.js";
|
|
6
7
|
import { Container } from "../utils/container.js";
|
|
7
8
|
export interface MessageManagerCallbacks {
|
|
8
9
|
onSessionIdChange?: (sessionId: string) => void;
|
|
@@ -59,11 +60,21 @@ export declare class MessageManager {
|
|
|
59
60
|
private savedMessageCount;
|
|
60
61
|
private pendingFileReadTriggers;
|
|
61
62
|
private loadedRuleIds;
|
|
63
|
+
private pendingNestedMemoryTriggers;
|
|
64
|
+
private loadedNestedMemoryPaths;
|
|
62
65
|
private recentFileReads;
|
|
63
66
|
private invokedSkills;
|
|
64
67
|
private sessionType;
|
|
65
68
|
private subagentType?;
|
|
66
69
|
private _usages;
|
|
70
|
+
/**
|
|
71
|
+
* This session's user-set title, as last known to this process (set on
|
|
72
|
+
* rename, or recovered from the transcript on resume). It is the fallback
|
|
73
|
+
* `reAppendSessionMetadata` writes back at EOF once the `custom-title` entry
|
|
74
|
+
* has been pushed out of the listing's tail window — at which point the file
|
|
75
|
+
* itself can no longer answer "what was this session called".
|
|
76
|
+
*/
|
|
77
|
+
private customTitle?;
|
|
67
78
|
constructor(container: Container, options: MessageManagerOptions);
|
|
68
79
|
private get memoryRuleManager();
|
|
69
80
|
private get memoryService();
|
|
@@ -74,6 +85,13 @@ export declare class MessageManager {
|
|
|
74
85
|
getDisplayMessages(): Message[];
|
|
75
86
|
getUsages(): Usage[];
|
|
76
87
|
getLatestTotalTokens(): number;
|
|
88
|
+
/**
|
|
89
|
+
* Re-point this manager at another working directory (in-session session
|
|
90
|
+
* switch). The encoded workdir and the transcript path are both derived from
|
|
91
|
+
* it, so they must move together with it: otherwise the next append would
|
|
92
|
+
* create a second copy of the same session file under the old project dir.
|
|
93
|
+
*/
|
|
94
|
+
setWorkdir(workdir: string): void;
|
|
77
95
|
getWorkdir(): string;
|
|
78
96
|
/**
|
|
79
97
|
* Process pending file read triggers: match against conditional rules,
|
|
@@ -111,6 +129,19 @@ export declare class MessageManager {
|
|
|
111
129
|
* conditional memory rules before the next AI call.
|
|
112
130
|
*/
|
|
113
131
|
triggerFileRead(filePath: string): void;
|
|
132
|
+
/**
|
|
133
|
+
* Record an absolute path whose read may pull in a nested memory file
|
|
134
|
+
* (`AGENTS.md` in one of its ancestor directories). Separate from
|
|
135
|
+
* `triggerFileRead`: rule matching needs workdir-relative paths, the nested
|
|
136
|
+
* walk needs absolute ones.
|
|
137
|
+
*/
|
|
138
|
+
triggerNestedMemory(filePath: string): void;
|
|
139
|
+
/**
|
|
140
|
+
* Resolve nested memory files for the paths read since the last call,
|
|
141
|
+
* outermost first. Clears the pending set; dedup is session-scoped via
|
|
142
|
+
* `loadedNestedMemoryPaths`.
|
|
143
|
+
*/
|
|
144
|
+
collectNestedMemoryFiles(): Promise<NestedMemoryFile[]>;
|
|
114
145
|
/**
|
|
115
146
|
* Check if a file has been read (for read-before-edit enforcement).
|
|
116
147
|
* Uses recentFileReads Map populated from Read tool execution.
|
|
@@ -124,6 +155,16 @@ export declare class MessageManager {
|
|
|
124
155
|
*/
|
|
125
156
|
saveSession(): Promise<void>;
|
|
126
157
|
setlatestTotalTokens(latestTotalTokens: number): void;
|
|
158
|
+
getCustomTitle(): string | undefined;
|
|
159
|
+
setCustomTitle(customTitle: string | undefined): void;
|
|
160
|
+
/**
|
|
161
|
+
* Re-append the session's metadata entries at EOF, so they stay inside the
|
|
162
|
+
* tail window the session list reads. Called at the sparse points where the
|
|
163
|
+
* transcript is about to be read back by another process (exit, compaction,
|
|
164
|
+
* resume, rewind); doing it per save would cost a tail read plus a line on
|
|
165
|
+
* every message.
|
|
166
|
+
*/
|
|
167
|
+
reAppendSessionMetadata(): Promise<void>;
|
|
127
168
|
/**
|
|
128
169
|
* Clear messages
|
|
129
170
|
*/
|
|
@@ -214,7 +255,9 @@ export declare class MessageManager {
|
|
|
214
255
|
*/
|
|
215
256
|
truncateHistory(index: number, reversionManager?: import("./reversionManager.js").ReversionManager): Promise<void>;
|
|
216
257
|
/**
|
|
217
|
-
*
|
|
258
|
+
* Drop everything past the first `keepMessageCount` messages from the
|
|
259
|
+
* session file. The rewrite is a filter over the file's own lines, so the
|
|
260
|
+
* metadata header, the user-set title and any other reserved entry survive.
|
|
218
261
|
*/
|
|
219
262
|
private rewriteSessionFile;
|
|
220
263
|
/**
|
|
@@ -242,11 +285,11 @@ export declare class MessageManager {
|
|
|
242
285
|
*/
|
|
243
286
|
private extractSkillInvocationsFromMessage;
|
|
244
287
|
/**
|
|
245
|
-
* Get recently invoked
|
|
288
|
+
* Get recently invoked skills, sorted by timestamp (newest first).
|
|
246
289
|
* @param maxSkills - Maximum number of skills to return
|
|
247
|
-
* @returns
|
|
290
|
+
* @returns Invoked skill records sorted by recency
|
|
248
291
|
*/
|
|
249
|
-
|
|
292
|
+
getInvokedSkills(maxSkills?: number): InvokedSkillRecord[];
|
|
250
293
|
/**
|
|
251
294
|
* Clear all invoked skills (e.g., after compaction).
|
|
252
295
|
*/
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addUserMessageToMessages, updateUserMessageInMessages, removeLastUserMessage, addToolBlockToMessageInMessages, addNotificationMessageToMessages, generateMessageId, sliceFromLastCompact, } from "../utils/messageOperations.js";
|
|
2
2
|
import { getLastApiRounds } from "../utils/groupMessagesByApiRound.js";
|
|
3
|
-
import { join, isAbsolute, relative } from "path";
|
|
4
|
-
import { appendMessages, createSession, generateSessionId,
|
|
3
|
+
import { join, isAbsolute, relative, resolve } from "path";
|
|
4
|
+
import { appendMessages, createSession, generateSessionId,
|
|
5
|
+
// Aliased: this class exposes a `reAppendSessionMetadata` method of its own.
|
|
6
|
+
reAppendSessionMetadata as reAppendSessionMetadataToFile, truncateSession, SESSION_DIR, } from "../services/session.js";
|
|
5
7
|
import { pathEncoder } from "../utils/pathEncoder.js";
|
|
6
8
|
import { estimateTokens } from "../utils/tokenEstimate.js";
|
|
9
|
+
import { findNestedMemoryFiles, } from "../utils/nestedMemory.js";
|
|
7
10
|
import { READ_TOOL_NAME } from "../constants/tools.js";
|
|
8
11
|
// Cap for recentFileReads: content is only consumed by getRecentFileReads
|
|
9
12
|
// (top 5 for post-compact restoration), so bound both the entry count and the
|
|
@@ -25,8 +28,10 @@ export class MessageManager {
|
|
|
25
28
|
this.displayMessages = [];
|
|
26
29
|
this.pendingFileReadTriggers = new Set(); // File paths read via Read tool, awaiting rule matching
|
|
27
30
|
this.loadedRuleIds = new Set(); // IDs of conditional rules already injected as meta messages
|
|
31
|
+
this.pendingNestedMemoryTriggers = new Set(); // Absolute paths read since the last nested-memory scan
|
|
32
|
+
this.loadedNestedMemoryPaths = new Set(); // Nested memory files already injected (session-scoped, never evicted)
|
|
28
33
|
this.recentFileReads = new Map(); // Track file read contents
|
|
29
|
-
this.invokedSkills = new Map(); // Track invoked
|
|
34
|
+
this.invokedSkills = new Map(); // Track invoked skills
|
|
30
35
|
this._usages = [];
|
|
31
36
|
this.sessionId = generateSessionId();
|
|
32
37
|
this.messages = [];
|
|
@@ -72,6 +77,20 @@ export class MessageManager {
|
|
|
72
77
|
getLatestTotalTokens() {
|
|
73
78
|
return this.latestTotalTokens;
|
|
74
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Re-point this manager at another working directory (in-session session
|
|
82
|
+
* switch). The encoded workdir and the transcript path are both derived from
|
|
83
|
+
* it, so they must move together with it: otherwise the next append would
|
|
84
|
+
* create a second copy of the same session file under the old project dir.
|
|
85
|
+
*/
|
|
86
|
+
setWorkdir(workdir) {
|
|
87
|
+
if (this.workdir === workdir) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.workdir = workdir;
|
|
91
|
+
this.encodedWorkdir = pathEncoder.encodeSync(workdir);
|
|
92
|
+
this.transcriptPath = this.computeTranscriptPath();
|
|
93
|
+
}
|
|
75
94
|
getWorkdir() {
|
|
76
95
|
return this.workdir;
|
|
77
96
|
}
|
|
@@ -174,6 +193,31 @@ export class MessageManager {
|
|
|
174
193
|
: filePath;
|
|
175
194
|
this.pendingFileReadTriggers.add(normalizedPath);
|
|
176
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Record an absolute path whose read may pull in a nested memory file
|
|
198
|
+
* (`AGENTS.md` in one of its ancestor directories). Separate from
|
|
199
|
+
* `triggerFileRead`: rule matching needs workdir-relative paths, the nested
|
|
200
|
+
* walk needs absolute ones.
|
|
201
|
+
*/
|
|
202
|
+
triggerNestedMemory(filePath) {
|
|
203
|
+
this.pendingNestedMemoryTriggers.add(isAbsolute(filePath) ? filePath : resolve(this.workdir, filePath));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Resolve nested memory files for the paths read since the last call,
|
|
207
|
+
* outermost first. Clears the pending set; dedup is session-scoped via
|
|
208
|
+
* `loadedNestedMemoryPaths`.
|
|
209
|
+
*/
|
|
210
|
+
async collectNestedMemoryFiles() {
|
|
211
|
+
if (this.pendingNestedMemoryTriggers.size === 0)
|
|
212
|
+
return [];
|
|
213
|
+
const filePaths = Array.from(this.pendingNestedMemoryTriggers);
|
|
214
|
+
this.pendingNestedMemoryTriggers.clear();
|
|
215
|
+
const memories = [];
|
|
216
|
+
for (const filePath of filePaths) {
|
|
217
|
+
memories.push(...(await findNestedMemoryFiles(filePath, this.workdir, this.loadedNestedMemoryPaths)));
|
|
218
|
+
}
|
|
219
|
+
return memories;
|
|
220
|
+
}
|
|
177
221
|
/**
|
|
178
222
|
* Check if a file has been read (for read-before-edit enforcement).
|
|
179
223
|
* Uses recentFileReads Map populated from Read tool execution.
|
|
@@ -268,6 +312,27 @@ export class MessageManager {
|
|
|
268
312
|
this.callbacks.onLatestTotalTokensChange?.(latestTotalTokens);
|
|
269
313
|
}
|
|
270
314
|
}
|
|
315
|
+
getCustomTitle() {
|
|
316
|
+
return this.customTitle;
|
|
317
|
+
}
|
|
318
|
+
setCustomTitle(customTitle) {
|
|
319
|
+
this.customTitle = customTitle;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Re-append the session's metadata entries at EOF, so they stay inside the
|
|
323
|
+
* tail window the session list reads. Called at the sparse points where the
|
|
324
|
+
* transcript is about to be read back by another process (exit, compaction,
|
|
325
|
+
* resume, rewind); doing it per save would cost a tail read plus a line on
|
|
326
|
+
* every message.
|
|
327
|
+
*/
|
|
328
|
+
async reAppendSessionMetadata() {
|
|
329
|
+
try {
|
|
330
|
+
await reAppendSessionMetadataToFile(this.sessionId, this.workdir, this.customTitle, this.sessionType);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
logger?.error("Failed to re-append session metadata:", error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
271
336
|
/**
|
|
272
337
|
* Clear messages
|
|
273
338
|
*/
|
|
@@ -277,6 +342,9 @@ export class MessageManager {
|
|
|
277
342
|
this.setSessionId(newSessionId);
|
|
278
343
|
this.setlatestTotalTokens(0);
|
|
279
344
|
this.savedMessageCount = 0; // Reset saved message count
|
|
345
|
+
// The title belongs to the session that was just abandoned: the new
|
|
346
|
+
// session id starts untitled until the user renames it.
|
|
347
|
+
this.customTitle = undefined;
|
|
280
348
|
}
|
|
281
349
|
/**
|
|
282
350
|
* Trigger the rewind UI callback
|
|
@@ -295,6 +363,10 @@ export class MessageManager {
|
|
|
295
363
|
});
|
|
296
364
|
this.displayMessages = [...sessionData.messages];
|
|
297
365
|
this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
|
|
366
|
+
// Recovered by the whole-file scan on load: from here on it is the
|
|
367
|
+
// in-memory fallback that survives the title entry sliding out of the
|
|
368
|
+
// tail window.
|
|
369
|
+
this.customTitle = sessionData.metadata.customTitle;
|
|
298
370
|
// Rebuild loadedRuleIds from persisted meta messages (session restore)
|
|
299
371
|
this.rebuildLoadedRuleIds();
|
|
300
372
|
// Set saved message count to the number of loaded messages since they're already saved
|
|
@@ -448,6 +520,9 @@ export class MessageManager {
|
|
|
448
520
|
// message id appearing exactly once (dedup-skipped, aligned with CC).
|
|
449
521
|
const { appendMessages } = await import("../services/session.js");
|
|
450
522
|
await appendMessages(this.sessionId, [compactMessage], this.workdir, this.sessionType);
|
|
523
|
+
// Compaction leaves the transcript readable by the next process that
|
|
524
|
+
// lists sessions, so this is a flush point for the metadata entries.
|
|
525
|
+
await this.reAppendSessionMetadata();
|
|
451
526
|
// Update in-memory state: the API context folds to [compact, ...last
|
|
452
527
|
// rounds], while the UI display stream keeps the full history and appends
|
|
453
528
|
// the compact block at the end.
|
|
@@ -459,6 +534,11 @@ export class MessageManager {
|
|
|
459
534
|
// Clear and rebuild loaded rule IDs from remaining meta messages
|
|
460
535
|
this.clearLoadedRuleIds();
|
|
461
536
|
this.rebuildLoadedRuleIds();
|
|
537
|
+
// Nested memory has no marker to rebuild from, so the dedup set is dropped
|
|
538
|
+
// outright: compaction may have summarized the injected text away, and
|
|
539
|
+
// re-injecting it on the next read of that subtree beats never showing it
|
|
540
|
+
// again in this session.
|
|
541
|
+
this.loadedNestedMemoryPaths.clear();
|
|
462
542
|
// Trigger compaction callback
|
|
463
543
|
this.callbacks.onCompactBlockAdded?.(compactedContent);
|
|
464
544
|
}
|
|
@@ -737,8 +817,16 @@ export class MessageManager {
|
|
|
737
817
|
if (reversionManager && messageIdsToRemove.length > 0) {
|
|
738
818
|
await reversionManager.revertTo(messageIdsToRemove, messages);
|
|
739
819
|
}
|
|
740
|
-
//
|
|
741
|
-
|
|
820
|
+
// Keep the first `newMessages.length` messages on disk. Note the count is
|
|
821
|
+
// the message index, not a file operation: the rewrite walks the file's own
|
|
822
|
+
// lines and carries reserved entries (metadata header, custom-title) over.
|
|
823
|
+
await this.rewriteSessionFile(newMessages.length);
|
|
824
|
+
// The rewrite drops messages but keeps the reserved entries where they sit,
|
|
825
|
+
// which is not enough for the title: the session list reads only the file's
|
|
826
|
+
// tail window, and a title written early can end up outside it (the
|
|
827
|
+
// rewrite keeps the *first* messages, so the survivors after it are exactly
|
|
828
|
+
// what pushes it away). Re-append it at EOF like the other flush points do.
|
|
829
|
+
await this.reAppendSessionMetadata();
|
|
742
830
|
// The UI keeps the full truncated thread; the agent context folds at the
|
|
743
831
|
// last compact boundary so the agent only sees messages from the latest
|
|
744
832
|
// compact summary forward — matching the compact and resume behaviors.
|
|
@@ -751,18 +839,13 @@ export class MessageManager {
|
|
|
751
839
|
this.savedMessageCount = this.messages.length;
|
|
752
840
|
}
|
|
753
841
|
/**
|
|
754
|
-
*
|
|
842
|
+
* Drop everything past the first `keepMessageCount` messages from the
|
|
843
|
+
* session file. The rewrite is a filter over the file's own lines, so the
|
|
844
|
+
* metadata header, the user-set title and any other reserved entry survive.
|
|
755
845
|
*/
|
|
756
|
-
async rewriteSessionFile(
|
|
846
|
+
async rewriteSessionFile(keepMessageCount) {
|
|
757
847
|
try {
|
|
758
|
-
|
|
759
|
-
const content = messages
|
|
760
|
-
.map((m) => {
|
|
761
|
-
const { timestamp, ...rest } = m;
|
|
762
|
-
return JSON.stringify({ timestamp, ...rest });
|
|
763
|
-
})
|
|
764
|
-
.join("\n") + (messages.length > 0 ? "\n" : "");
|
|
765
|
-
await writeFile(this.transcriptPath, content, "utf8");
|
|
848
|
+
await truncateSession(this.sessionId, keepMessageCount, this.workdir, this.sessionType);
|
|
766
849
|
}
|
|
767
850
|
catch (error) {
|
|
768
851
|
logger?.error("Failed to rewrite session file:", error);
|
|
@@ -859,6 +942,10 @@ export class MessageManager {
|
|
|
859
942
|
this.invokedSkills.set(skillName, {
|
|
860
943
|
skillName,
|
|
861
944
|
timestamp: Date.now(),
|
|
945
|
+
// 模型当时实际看到的那份正文(已替换 ${WAVE_SKILL_DIR} 等占位符、
|
|
946
|
+
// 首行带 `Base directory for this skill:`)——压缩后回灌直接复用它,
|
|
947
|
+
// 不能重新读文件原文(spec core/message-compact 场景 4)。
|
|
948
|
+
content: block.result,
|
|
862
949
|
});
|
|
863
950
|
}
|
|
864
951
|
}
|
|
@@ -869,15 +956,14 @@ export class MessageManager {
|
|
|
869
956
|
}
|
|
870
957
|
}
|
|
871
958
|
/**
|
|
872
|
-
* Get recently invoked
|
|
959
|
+
* Get recently invoked skills, sorted by timestamp (newest first).
|
|
873
960
|
* @param maxSkills - Maximum number of skills to return
|
|
874
|
-
* @returns
|
|
961
|
+
* @returns Invoked skill records sorted by recency
|
|
875
962
|
*/
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
.sort((
|
|
963
|
+
getInvokedSkills(maxSkills = 10) {
|
|
964
|
+
return Array.from(this.invokedSkills.values())
|
|
965
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
879
966
|
.slice(0, maxSkills);
|
|
880
|
-
return sorted.map(([, { skillName }]) => skillName);
|
|
881
967
|
}
|
|
882
968
|
/**
|
|
883
969
|
* Clear all invoked skills (e.g., after compaction).
|
|
@@ -18,6 +18,12 @@ export declare function resolveAdditionalDirectory(dir: string, workdir: string
|
|
|
18
18
|
export interface PermissionManagerOptions {
|
|
19
19
|
/** Configured permission mode from settings */
|
|
20
20
|
configuredPermissionMode?: PermissionMode;
|
|
21
|
+
/**
|
|
22
|
+
* Session-level bypass authorization (see `captureBypassAuthorization`).
|
|
23
|
+
* Passed explicitly by subagents so they inherit the parent session's value
|
|
24
|
+
* instead of re-deriving it from their own (mid-session) mode.
|
|
25
|
+
*/
|
|
26
|
+
bypassAuthorization?: boolean;
|
|
21
27
|
/** Allowed rules from settings */
|
|
22
28
|
allowedRules?: string[];
|
|
23
29
|
/** Denied rules from settings */
|
|
@@ -40,6 +46,15 @@ export interface PermissionManagerOptions {
|
|
|
40
46
|
export declare class PermissionManager {
|
|
41
47
|
private container;
|
|
42
48
|
private configuredPermissionMode?;
|
|
49
|
+
/**
|
|
50
|
+
* Session-level bypass authorization: the permission mode in effect when the
|
|
51
|
+
* session was created was `bypassPermissions`. Frozen at session start, so a
|
|
52
|
+
* later mode switch (or config reload) can never grant it. Its only effect is
|
|
53
|
+
* that plan mode skips permission checks instead of re-asking — mirroring
|
|
54
|
+
* Claude Code's `isBypassPermissionsModeAvailable`. `undefined` = not
|
|
55
|
+
* captured yet (subagents get an explicit value instead).
|
|
56
|
+
*/
|
|
57
|
+
private bypassAuthorization?;
|
|
43
58
|
private allowedRules;
|
|
44
59
|
private deniedRules;
|
|
45
60
|
private instanceAllowedRules;
|
|
@@ -73,6 +88,17 @@ export declare class PermissionManager {
|
|
|
73
88
|
* Get the configured default mode
|
|
74
89
|
*/
|
|
75
90
|
getConfiguredPermissionMode(): PermissionMode | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Freeze the session-level bypass authorization from the permission mode in
|
|
93
|
+
* effect when the session was created (CLI mode > configured default mode >
|
|
94
|
+
* `default`). No-op once captured, so switching to `bypassPermissions`
|
|
95
|
+
* mid-session does not grant it.
|
|
96
|
+
*/
|
|
97
|
+
captureBypassAuthorization(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Whether this session keeps bypass semantics while in plan mode
|
|
100
|
+
*/
|
|
101
|
+
getBypassAuthorization(): boolean;
|
|
76
102
|
/**
|
|
77
103
|
* Get all currently allowed rules (user-defined)
|
|
78
104
|
*/
|
|
@@ -94,6 +120,13 @@ export declare class PermissionManager {
|
|
|
94
120
|
* Unlike addPermissionRule, this does NOT persist to settings.local.json.
|
|
95
121
|
*/
|
|
96
122
|
addInstanceAllowedRule(rule: string): void;
|
|
123
|
+
/**
|
|
124
|
+
* Remove an instance-level allowed rule added via addInstanceAllowedRule.
|
|
125
|
+
* Used when a builtin plugin that contributed a helper-script grant is
|
|
126
|
+
* unloaded, so the grant does not outlive the plugin.
|
|
127
|
+
* @returns true if the rule was present
|
|
128
|
+
*/
|
|
129
|
+
removeInstanceAllowedRule(rule: string): boolean;
|
|
97
130
|
/**
|
|
98
131
|
* Get all additional directories
|
|
99
132
|
*/
|
|
@@ -143,6 +176,13 @@ export declare class PermissionManager {
|
|
|
143
176
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
144
177
|
*/
|
|
145
178
|
addSystemAdditionalDirectory(directory: string): void;
|
|
179
|
+
/**
|
|
180
|
+
* Remove a system-level additional directory (idempotent). Used when a
|
|
181
|
+
* settings.json change revokes a privilege that was granted at construction
|
|
182
|
+
* time — e.g. turning auto-memory off removes its directory from the safe
|
|
183
|
+
* zone (core/agent-config.md scenario 6).
|
|
184
|
+
*/
|
|
185
|
+
removeSystemAdditionalDirectory(directory: string): void;
|
|
146
186
|
/**
|
|
147
187
|
* Set the current plan file path
|
|
148
188
|
*/
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import { minimatch } from "minimatch";
|
|
11
|
-
import { RESTRICTED_TOOLS } from "../types/permissions.js";
|
|
11
|
+
import { RESTRICTED_TOOLS, USER_INTERACTION_REQUIRED_TOOLS, } from "../types/permissions.js";
|
|
12
12
|
import { splitBashCommand, stripEnvVars, stripRedirections, hasWriteRedirections, getSmartPrefix, isDangerousFind, hasCommandSubstitution, hasProcessSubstitution, hasSedInPlace, stripGitScopePrefix, DANGEROUS_COMMANDS, READ_ONLY_COMMANDS, PATH_COMMANDS, extractPathArgs, } from "../utils/bashParser.js";
|
|
13
13
|
import { parseBashStructure, } from "../utils/bashStructure/index.js";
|
|
14
14
|
import { isPathInside } from "../utils/pathSafety.js";
|
|
15
15
|
import { toWindowsPath } from "../utils/path.js";
|
|
16
|
-
import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, READ_TOOL_NAME,
|
|
16
|
+
import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, READ_TOOL_NAME, } from "../constants/tools.js";
|
|
17
17
|
const DEFAULT_ALLOWED_RULES = [
|
|
18
18
|
"Bash(git status*)",
|
|
19
19
|
"Bash(git diff*)",
|
|
@@ -87,6 +87,7 @@ export class PermissionManager {
|
|
|
87
87
|
this.hasExitedPlanMode = false;
|
|
88
88
|
this.needsPlanModeExitAttachment = false;
|
|
89
89
|
this.configuredPermissionMode = options.configuredPermissionMode;
|
|
90
|
+
this.bypassAuthorization = options.bypassAuthorization;
|
|
90
91
|
this.allowedRules = options.allowedRules || [];
|
|
91
92
|
this.deniedRules = options.deniedRules || [];
|
|
92
93
|
this.instanceAllowedRules = options.instanceAllowedRules || [];
|
|
@@ -135,6 +136,28 @@ export class PermissionManager {
|
|
|
135
136
|
getConfiguredPermissionMode() {
|
|
136
137
|
return this.configuredPermissionMode;
|
|
137
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Freeze the session-level bypass authorization from the permission mode in
|
|
141
|
+
* effect when the session was created (CLI mode > configured default mode >
|
|
142
|
+
* `default`). No-op once captured, so switching to `bypassPermissions`
|
|
143
|
+
* mid-session does not grant it.
|
|
144
|
+
*/
|
|
145
|
+
captureBypassAuthorization() {
|
|
146
|
+
if (this.bypassAuthorization !== undefined) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const sessionMode = this.container.has("PermissionMode")
|
|
150
|
+
? this.container.get("PermissionMode")
|
|
151
|
+
: undefined;
|
|
152
|
+
this.bypassAuthorization =
|
|
153
|
+
this.getCurrentEffectiveMode(sessionMode) === "bypassPermissions";
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Whether this session keeps bypass semantics while in plan mode
|
|
157
|
+
*/
|
|
158
|
+
getBypassAuthorization() {
|
|
159
|
+
return this.bypassAuthorization === true;
|
|
160
|
+
}
|
|
138
161
|
/**
|
|
139
162
|
* Get all currently allowed rules (user-defined)
|
|
140
163
|
*/
|
|
@@ -168,6 +191,20 @@ export class PermissionManager {
|
|
|
168
191
|
this.instanceAllowedRules.push(rule);
|
|
169
192
|
}
|
|
170
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Remove an instance-level allowed rule added via addInstanceAllowedRule.
|
|
196
|
+
* Used when a builtin plugin that contributed a helper-script grant is
|
|
197
|
+
* unloaded, so the grant does not outlive the plugin.
|
|
198
|
+
* @returns true if the rule was present
|
|
199
|
+
*/
|
|
200
|
+
removeInstanceAllowedRule(rule) {
|
|
201
|
+
const index = this.instanceAllowedRules.indexOf(rule);
|
|
202
|
+
if (index === -1) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
this.instanceAllowedRules.splice(index, 1);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
171
208
|
/**
|
|
172
209
|
* Get all additional directories
|
|
173
210
|
*/
|
|
@@ -258,6 +295,16 @@ export class PermissionManager {
|
|
|
258
295
|
this.systemAdditionalDirectories.push(resolvedPath);
|
|
259
296
|
}
|
|
260
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Remove a system-level additional directory (idempotent). Used when a
|
|
300
|
+
* settings.json change revokes a privilege that was granted at construction
|
|
301
|
+
* time — e.g. turning auto-memory off removes its directory from the safe
|
|
302
|
+
* zone (core/agent-config.md scenario 6).
|
|
303
|
+
*/
|
|
304
|
+
removeSystemAdditionalDirectory(directory) {
|
|
305
|
+
const resolvedPath = resolveAdditionalDirectory(directory, this.workdir);
|
|
306
|
+
this.systemAdditionalDirectories = this.systemAdditionalDirectories.filter((dir) => dir !== resolvedPath);
|
|
307
|
+
}
|
|
261
308
|
/**
|
|
262
309
|
* Set the current plan file path
|
|
263
310
|
*/
|
|
@@ -434,12 +481,17 @@ export class PermissionManager {
|
|
|
434
481
|
}
|
|
435
482
|
}
|
|
436
483
|
}
|
|
437
|
-
// If bypassPermissions mode
|
|
438
|
-
//
|
|
439
|
-
//
|
|
484
|
+
// If bypassPermissions mode — or plan mode in a session that was authorized
|
|
485
|
+
// to bypass at creation time — always allow.
|
|
486
|
+
// Exception: tools that require user interaction (AskUserQuestion and
|
|
487
|
+
// ExitPlanMode) must still prompt, matching Claude Code's
|
|
488
|
+
// requiresUserInteraction behavior (its step 1e returns the tool's ask
|
|
489
|
+
// result before the bypass step).
|
|
440
490
|
// Worktree safety check above runs unconditionally, so bypass never skips it.
|
|
441
|
-
|
|
442
|
-
|
|
491
|
+
const bypassesPermissionChecks = context.permissionMode === "bypassPermissions" ||
|
|
492
|
+
(context.permissionMode === "plan" && this.getBypassAuthorization());
|
|
493
|
+
if (bypassesPermissionChecks) {
|
|
494
|
+
const requiresUserInteraction = USER_INTERACTION_REQUIRED_TOOLS.includes(context.toolName);
|
|
443
495
|
if (!requiresUserInteraction) {
|
|
444
496
|
return { behavior: "allow" };
|
|
445
497
|
}
|
|
@@ -615,9 +667,12 @@ export class PermissionManager {
|
|
|
615
667
|
const targetPath = toolInput?.file_path;
|
|
616
668
|
const workdir = toolInput?.workdir;
|
|
617
669
|
if (targetPath) {
|
|
618
|
-
const { isInside } = this.isInsideSafeZone(targetPath, workdir);
|
|
670
|
+
const { isInside, resolvedPath } = this.isInsideSafeZone(targetPath, workdir);
|
|
619
671
|
if (!isInside) {
|
|
620
672
|
context.hidePersistentOption = true;
|
|
673
|
+
// Surface the target's directory so the confirmation UI can offer
|
|
674
|
+
// adding it to the session Safe Zone.
|
|
675
|
+
context.outsideSafeZoneDirectory = path.dirname(resolvedPath);
|
|
621
676
|
}
|
|
622
677
|
}
|
|
623
678
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plugin, PluginConfig } from "../types/index.js";
|
|
1
|
+
import { Plugin, PluginConfig, PluginLoadFailure, PluginReloadResult } from "../types/index.js";
|
|
2
2
|
import { Container } from "../utils/container.js";
|
|
3
3
|
export interface PluginManagerOptions {
|
|
4
4
|
workdir: string;
|
|
@@ -17,6 +17,17 @@ export declare class PluginManager {
|
|
|
17
17
|
private plugins;
|
|
18
18
|
private workdir;
|
|
19
19
|
private enabledPlugins;
|
|
20
|
+
/** Explicit configs from the last load, replayed by reloadAllPlugins(). */
|
|
21
|
+
private lastLoadConfigs;
|
|
22
|
+
/** Failures collected by the current loadPlugins() run. */
|
|
23
|
+
private loadFailures;
|
|
24
|
+
/**
|
|
25
|
+
* Serializes plugin (re)loads. A managed-settings sync (or an early
|
|
26
|
+
* `/reload-plugins`) can ask for a reload while the startup load is still
|
|
27
|
+
* running; interleaving them would leave half-unloaded capability registries.
|
|
28
|
+
*/
|
|
29
|
+
private loadChain;
|
|
30
|
+
private serialize;
|
|
20
31
|
constructor(container: Container, options: PluginManagerOptions);
|
|
21
32
|
private get permissionManager();
|
|
22
33
|
private get skillManager();
|
|
@@ -41,8 +52,41 @@ export declare class PluginManager {
|
|
|
41
52
|
/**
|
|
42
53
|
* Load plugins from configuration
|
|
43
54
|
* @param configs Array of plugin configurations
|
|
55
|
+
* @returns the plugins that failed to load
|
|
56
|
+
*/
|
|
57
|
+
loadPlugins(configs: PluginConfig[]): Promise<PluginLoadFailure[]>;
|
|
58
|
+
/**
|
|
59
|
+
* The actual load. Reached through [loadPlugins] (which serializes concurrent
|
|
60
|
+
* requests) and directly from [reloadAllPlugins], which already holds the slot.
|
|
61
|
+
*/
|
|
62
|
+
private doLoadPlugins;
|
|
63
|
+
/**
|
|
64
|
+
* Unload a single plugin: drop everything it contributed from the six
|
|
65
|
+
* capability registries, then forget it. Other plugins and the running
|
|
66
|
+
* session are untouched.
|
|
67
|
+
* @returns true if the plugin was loaded
|
|
68
|
+
*/
|
|
69
|
+
unloadPlugin(name: string): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Re-read every plugin in place — the `/reload-plugins` command.
|
|
72
|
+
*
|
|
73
|
+
* Unloads everything currently loaded, re-reads `enabledPlugins` from the
|
|
74
|
+
* configuration chain (so a settings edit is honored), then replays the
|
|
75
|
+
* original load order (explicit configs → installed → builtin). The session,
|
|
76
|
+
* transcript and sessionId are untouched; only the capability registries are
|
|
77
|
+
* swapped, which is what makes this an in-place reload instead of a rebuild.
|
|
78
|
+
*
|
|
79
|
+
* Unload must happen before any caller-side mutation of the plugin cache dir:
|
|
80
|
+
* hooks, MCP, LSP, skills and agents all bake absolute plugin paths at
|
|
81
|
+
* registration time, so an entry left pointing at a deleted directory is
|
|
82
|
+
* worse than a merely stale one.
|
|
83
|
+
*/
|
|
84
|
+
reloadAllPlugins(): Promise<PluginReloadResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Re-read enabled plugins from the merged configuration chain, falling back
|
|
87
|
+
* to the last known value when the service is unavailable.
|
|
44
88
|
*/
|
|
45
|
-
|
|
89
|
+
private refreshEnabledPlugins;
|
|
46
90
|
/**
|
|
47
91
|
* Load built-in plugins bundled with the SDK (e.g. sdd).
|
|
48
92
|
* Opt-in via `enabledPlugins`, keyed `<name>@builtin` (default off, consistent
|