wave-agent-sdk 1.1.5 → 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 +128 -28
- package/dist/agent.js +201 -49
- package/dist/builtin/index.js +2 -0
- package/dist/builtin/plugins.js +11 -20
- package/dist/builtin/skills/settings.js +7 -20
- 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 +54 -10
- package/dist/core/plugin.js +137 -23
- 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 +7 -1
- package/dist/index.js +8 -1
- package/dist/managers/MemoryRuleManager.d.ts +6 -0
- package/dist/managers/MemoryRuleManager.js +12 -0
- package/dist/managers/aiManager.d.ts +35 -25
- package/dist/managers/aiManager.js +204 -202
- package/dist/managers/backgroundTaskManager.js +14 -0
- package/dist/managers/bashModeManager.d.ts +33 -0
- package/dist/managers/bashModeManager.js +110 -0
- package/dist/managers/hookManager.d.ts +18 -0
- package/dist/managers/hookManager.js +37 -3
- package/dist/managers/liveConfigManager.d.ts +33 -0
- package/dist/managers/liveConfigManager.js +106 -11
- package/dist/managers/lspManager.d.ts +9 -0
- package/dist/managers/lspManager.js +47 -18
- package/dist/managers/mcpManager.d.ts +68 -10
- package/dist/managers/mcpManager.js +265 -15
- package/dist/managers/messageManager.d.ts +60 -18
- package/dist/managers/messageManager.js +170 -81
- package/dist/managers/permissionManager.d.ts +69 -0
- package/dist/managers/permissionManager.js +221 -78
- package/dist/managers/planManager.d.ts +9 -0
- package/dist/managers/planManager.js +19 -1
- 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 +50 -0
- package/dist/managers/skillManager.js +166 -12
- package/dist/managers/slashCommandManager.d.ts +10 -0
- package/dist/managers/slashCommandManager.js +44 -31
- package/dist/managers/subagentManager.d.ts +15 -0
- package/dist/managers/subagentManager.js +81 -9
- package/dist/managers/toolManager.d.ts +29 -3
- package/dist/managers/toolManager.js +87 -13
- package/dist/managers/workflowManager.js +6 -0
- 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/index.d.ts +0 -1
- package/dist/prompts/index.js +0 -4
- 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 +323 -102
- 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 +92 -9
- package/dist/services/configurationService.js +246 -64
- 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 +21 -17
- package/dist/services/interactionService.d.ts +9 -1
- package/dist/services/interactionService.js +28 -8
- package/dist/services/jsonlHandler.d.ts +98 -0
- package/dist/services/jsonlHandler.js +250 -12
- package/dist/services/memory.d.ts +17 -1
- package/dist/services/memory.js +44 -7
- package/dist/services/officialMarketplaceMirror.d.ts +85 -0
- package/dist/services/officialMarketplaceMirror.js +290 -0
- package/dist/services/pluginLoader.d.ts +12 -4
- package/dist/services/pluginLoader.js +38 -7
- package/dist/services/remoteSettingsService.js +20 -6
- package/dist/services/session.d.ts +74 -0
- package/dist/services/session.js +174 -16
- package/dist/services/sessionEntries.d.ts +2 -0
- package/dist/services/sessionEntries.js +20 -0
- package/dist/services/worktreeHooks.js +6 -1
- package/dist/stdio/index.d.ts +12 -0
- package/dist/stdio/index.js +12 -0
- package/dist/stdio/notificationRouter.d.ts +38 -0
- package/dist/stdio/notificationRouter.js +97 -0
- package/dist/stdio/rpcClient.d.ts +18 -0
- package/dist/stdio/rpcClient.js +10 -0
- package/dist/stdio/stdioAgent.d.ts +229 -0
- package/dist/stdio/stdioAgent.js +360 -0
- package/dist/tools/artifactTool.js +406 -273
- package/dist/tools/bashTool.js +10 -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/exitPlanMode.js +10 -2
- 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 +24 -1
- package/dist/types/commands.d.ts +7 -0
- package/dist/types/configuration.d.ts +45 -2
- package/dist/types/hooks.d.ts +1 -0
- package/dist/types/hooks.js +19 -0
- package/dist/types/marketplace.d.ts +40 -2
- package/dist/types/mcp.d.ts +42 -0
- package/dist/types/messaging.d.ts +1 -8
- 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 +26 -0
- package/dist/utils/bashParser.d.ts +17 -0
- package/dist/utils/bashParser.js +72 -0
- package/dist/utils/bashStructure/bashLexer.d.ts +96 -0
- package/dist/utils/bashStructure/bashLexer.js +676 -0
- package/dist/utils/bashStructure/bashParser.d.ts +144 -0
- package/dist/utils/bashStructure/bashParser.js +606 -0
- package/dist/utils/bashStructure/bashSemantics.d.ts +70 -0
- package/dist/utils/bashStructure/bashSemantics.js +477 -0
- package/dist/utils/bashStructure/index.d.ts +26 -0
- package/dist/utils/bashStructure/index.js +27 -0
- package/dist/utils/bashStructure/types.d.ts +62 -0
- package/dist/utils/bashStructure/types.js +47 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/containerSetup.js +48 -6
- 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 +44 -0
- package/dist/utils/fileUtils.js +118 -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 -20
- package/dist/utils/messageOperations.js +40 -91
- 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 +48 -45
- package/dist/utils/tokenCalculation.js +0 -8
- package/dist/utils/userSettings.d.ts +90 -0
- package/dist/utils/userSettings.js +291 -0
- package/dist/utils/worktreeUtils.d.ts +2 -1
- package/dist/utils/worktreeUtils.js +64 -34
- package/package.json +12 -4
- package/dist/managers/bangManager.d.ts +0 -26
- package/dist/managers/bangManager.js +0 -78
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addUserMessageToMessages, updateUserMessageInMessages,
|
|
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
|
|
@@ -16,10 +19,19 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
16
19
|
export class MessageManager {
|
|
17
20
|
constructor(container, options) {
|
|
18
21
|
this.container = container;
|
|
22
|
+
/** Full display message stream for the UI. Mirrors `messages` (context) on
|
|
23
|
+
* every normal change; the two diverge only around compaction, where
|
|
24
|
+
* `messages` is replaced by [compact, ...last API rounds] for the API
|
|
25
|
+
* context while `displayMessages` keeps the full history and appends the
|
|
26
|
+
* compact block (aligned with Claude Code desktop/VS Code: compaction
|
|
27
|
+
* folds the API context but leaves the UI transcript intact). */
|
|
28
|
+
this.displayMessages = [];
|
|
19
29
|
this.pendingFileReadTriggers = new Set(); // File paths read via Read tool, awaiting rule matching
|
|
20
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)
|
|
21
33
|
this.recentFileReads = new Map(); // Track file read contents
|
|
22
|
-
this.invokedSkills = new Map(); // Track invoked
|
|
34
|
+
this.invokedSkills = new Map(); // Track invoked skills
|
|
23
35
|
this._usages = [];
|
|
24
36
|
this.sessionId = generateSessionId();
|
|
25
37
|
this.messages = [];
|
|
@@ -55,12 +67,30 @@ export class MessageManager {
|
|
|
55
67
|
getMessages() {
|
|
56
68
|
return [...this.messages];
|
|
57
69
|
}
|
|
70
|
+
/** Full UI display message stream (includes pre-compaction history). */
|
|
71
|
+
getDisplayMessages() {
|
|
72
|
+
return [...this.displayMessages];
|
|
73
|
+
}
|
|
58
74
|
getUsages() {
|
|
59
75
|
return [...this._usages];
|
|
60
76
|
}
|
|
61
77
|
getLatestTotalTokens() {
|
|
62
78
|
return this.latestTotalTokens;
|
|
63
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
|
+
}
|
|
64
94
|
getWorkdir() {
|
|
65
95
|
return this.workdir;
|
|
66
96
|
}
|
|
@@ -163,6 +193,31 @@ export class MessageManager {
|
|
|
163
193
|
: filePath;
|
|
164
194
|
this.pendingFileReadTriggers.add(normalizedPath);
|
|
165
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
|
+
}
|
|
166
221
|
/**
|
|
167
222
|
* Check if a file has been read (for read-before-edit enforcement).
|
|
168
223
|
* Uses recentFileReads Map populated from Read tool execution.
|
|
@@ -174,9 +229,35 @@ export class MessageManager {
|
|
|
174
229
|
return (this.recentFileReads.has(normalizedPath) ||
|
|
175
230
|
this.recentFileReads.has(filePath));
|
|
176
231
|
}
|
|
177
|
-
setMessages(messages) {
|
|
232
|
+
setMessages(messages, opts) {
|
|
178
233
|
const oldLength = this.messages.length;
|
|
179
234
|
this.messages = [...messages];
|
|
235
|
+
// The UI display stream follows the context by default. Compaction /
|
|
236
|
+
// session restore / rewind pass keepDisplay and manage displayMessages
|
|
237
|
+
// explicitly (the display stream must keep the full pre-compaction
|
|
238
|
+
// history while the context folds at the last compact boundary).
|
|
239
|
+
if (!opts?.keepDisplay) {
|
|
240
|
+
if (messages.length === 0) {
|
|
241
|
+
// Clear path — nothing to merge.
|
|
242
|
+
this.displayMessages = [...messages];
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Routine changes (new messages, tool block updates) still touch the
|
|
246
|
+
// display stream, but after compaction the display holds the full
|
|
247
|
+
// pre-compaction history while the context is folded. Merge by id so
|
|
248
|
+
// new messages append to the display stream and updated messages
|
|
249
|
+
// replace their display copy without dropping that history.
|
|
250
|
+
const byId = new Map(messages.map((m) => [m.id, m]));
|
|
251
|
+
const merged = this.displayMessages.map((m) => byId.get(m.id) ?? m);
|
|
252
|
+
const known = new Set(this.displayMessages.map((m) => m.id));
|
|
253
|
+
for (const m of messages) {
|
|
254
|
+
if (!known.has(m.id)) {
|
|
255
|
+
merged.push(m);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
this.displayMessages = merged;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
180
261
|
// Incrementally extract metadata from new messages
|
|
181
262
|
const newMessages = messages.slice(oldLength);
|
|
182
263
|
for (const message of newMessages) {
|
|
@@ -231,6 +312,27 @@ export class MessageManager {
|
|
|
231
312
|
this.callbacks.onLatestTotalTokensChange?.(latestTotalTokens);
|
|
232
313
|
}
|
|
233
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
|
+
}
|
|
234
336
|
/**
|
|
235
337
|
* Clear messages
|
|
236
338
|
*/
|
|
@@ -240,6 +342,9 @@ export class MessageManager {
|
|
|
240
342
|
this.setSessionId(newSessionId);
|
|
241
343
|
this.setlatestTotalTokens(0);
|
|
242
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;
|
|
243
348
|
}
|
|
244
349
|
/**
|
|
245
350
|
* Trigger the rewind UI callback
|
|
@@ -250,13 +355,23 @@ export class MessageManager {
|
|
|
250
355
|
// Initialize state from session data
|
|
251
356
|
initializeFromSession(sessionData) {
|
|
252
357
|
this.setSessionId(sessionData.id);
|
|
253
|
-
|
|
358
|
+
// The UI displays the full transcript (pre-compaction history included);
|
|
359
|
+
// the API context folds at the last compact boundary so the model only
|
|
360
|
+
// sees messages from the latest summary forward.
|
|
361
|
+
this.setMessages(sliceFromLastCompact(sessionData.messages), {
|
|
362
|
+
keepDisplay: true,
|
|
363
|
+
});
|
|
364
|
+
this.displayMessages = [...sessionData.messages];
|
|
254
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;
|
|
255
370
|
// Rebuild loadedRuleIds from persisted meta messages (session restore)
|
|
256
371
|
this.rebuildLoadedRuleIds();
|
|
257
372
|
// Set saved message count to the number of loaded messages since they're already saved
|
|
258
373
|
// This must be done after setSessionId which resets it to 0
|
|
259
|
-
this.savedMessageCount =
|
|
374
|
+
this.savedMessageCount = this.messages.length;
|
|
260
375
|
}
|
|
261
376
|
// Encapsulated message operation functions
|
|
262
377
|
addUserMessage(params) {
|
|
@@ -398,17 +513,32 @@ export class MessageManager {
|
|
|
398
513
|
};
|
|
399
514
|
// Build new message array: keep the compacted message and last messages
|
|
400
515
|
const newMessages = [compactMessage, ...lastThreeMessages];
|
|
401
|
-
// APPEND to the
|
|
516
|
+
// APPEND only the compact block to the session file (append-only, like
|
|
517
|
+
// Claude Code). The preserved last API rounds were already written before
|
|
518
|
+
// compaction (saveSession ran at the start), so re-appending them would
|
|
519
|
+
// duplicate those lines in the transcript — the loader relies on each
|
|
520
|
+
// message id appearing exactly once (dedup-skipped, aligned with CC).
|
|
402
521
|
const { appendMessages } = await import("../services/session.js");
|
|
403
|
-
await appendMessages(this.sessionId,
|
|
404
|
-
//
|
|
405
|
-
this.
|
|
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();
|
|
526
|
+
// Update in-memory state: the API context folds to [compact, ...last
|
|
527
|
+
// rounds], while the UI display stream keeps the full history and appends
|
|
528
|
+
// the compact block at the end.
|
|
529
|
+
this.setMessages(newMessages, { keepDisplay: true });
|
|
530
|
+
this.displayMessages = [...this.displayMessages, compactMessage];
|
|
406
531
|
this.savedMessageCount = newMessages.length;
|
|
407
532
|
// Trim recent file read cache so contents compacted away are released
|
|
408
533
|
this.trimRecentFileReads();
|
|
409
534
|
// Clear and rebuild loaded rule IDs from remaining meta messages
|
|
410
535
|
this.clearLoadedRuleIds();
|
|
411
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();
|
|
412
542
|
// Trigger compaction callback
|
|
413
543
|
this.callbacks.onCompactBlockAdded?.(compactedContent);
|
|
414
544
|
}
|
|
@@ -425,54 +555,6 @@ export class MessageManager {
|
|
|
425
555
|
this.callbacks.onFileHistoryBlockAdded?.(snapshots);
|
|
426
556
|
}
|
|
427
557
|
}
|
|
428
|
-
// Bang related message operations
|
|
429
|
-
addBangMessage(command) {
|
|
430
|
-
const updatedMessages = addBangMessage({
|
|
431
|
-
messages: this.messages,
|
|
432
|
-
command,
|
|
433
|
-
});
|
|
434
|
-
this.setMessages(updatedMessages);
|
|
435
|
-
// The bang message is appended as the last message
|
|
436
|
-
const messageId = this.messages[this.messages.length - 1]?.id ?? "";
|
|
437
|
-
this.callbacks.onAddBangMessage?.(command, messageId);
|
|
438
|
-
}
|
|
439
|
-
updateBangMessage(command, output) {
|
|
440
|
-
const updatedMessages = updateBangInMessage({
|
|
441
|
-
messages: this.messages,
|
|
442
|
-
command,
|
|
443
|
-
output,
|
|
444
|
-
});
|
|
445
|
-
this.setMessages(updatedMessages);
|
|
446
|
-
const messageId = this.findBangMessageId(command) ?? "";
|
|
447
|
-
this.callbacks.onUpdateBangMessage?.(command, output, messageId);
|
|
448
|
-
}
|
|
449
|
-
completeBangMessage(command, exitCode, output) {
|
|
450
|
-
const updatedMessages = completeBangInMessage({
|
|
451
|
-
messages: this.messages,
|
|
452
|
-
command,
|
|
453
|
-
exitCode,
|
|
454
|
-
output,
|
|
455
|
-
});
|
|
456
|
-
this.setMessages(updatedMessages);
|
|
457
|
-
const messageId = this.findBangMessageId(command) ?? "";
|
|
458
|
-
this.callbacks.onCompleteBangMessage?.(command, exitCode, messageId, output?.trim());
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Find the message ID of the most recent message containing a bang block
|
|
462
|
-
* for the given command. Bang callbacks do not carry a block ID, so the
|
|
463
|
-
* message is located by matching the command against bang blocks.
|
|
464
|
-
*/
|
|
465
|
-
findBangMessageId(command) {
|
|
466
|
-
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
467
|
-
const message = this.messages[i];
|
|
468
|
-
if (message.role !== "user")
|
|
469
|
-
continue;
|
|
470
|
-
const hasBangBlock = message.blocks?.some((block) => block.type === "bang" && block.command === command);
|
|
471
|
-
if (hasBangBlock)
|
|
472
|
-
return message.id;
|
|
473
|
-
}
|
|
474
|
-
return undefined;
|
|
475
|
-
}
|
|
476
558
|
addNotificationMessage(params) {
|
|
477
559
|
const newMessages = addNotificationMessageToMessages({
|
|
478
560
|
messages: this.messages,
|
|
@@ -735,12 +817,21 @@ export class MessageManager {
|
|
|
735
817
|
if (reversionManager && messageIdsToRemove.length > 0) {
|
|
736
818
|
await reversionManager.revertTo(messageIdsToRemove, messages);
|
|
737
819
|
}
|
|
738
|
-
//
|
|
739
|
-
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
//
|
|
743
|
-
|
|
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();
|
|
830
|
+
// The UI keeps the full truncated thread; the agent context folds at the
|
|
831
|
+
// last compact boundary so the agent only sees messages from the latest
|
|
832
|
+
// compact summary forward — matching the compact and resume behaviors.
|
|
833
|
+
this.setMessages(sliceFromLastCompact(newMessages), { keepDisplay: true });
|
|
834
|
+
this.displayMessages = newMessages;
|
|
744
835
|
// savedMessageCount tracks in-memory progress, so it must be the folded
|
|
745
836
|
// length. Using the full disk count here would make saveSession's
|
|
746
837
|
// slice(savedMessageCount) empty after a rewind past a compact boundary
|
|
@@ -748,18 +839,13 @@ export class MessageManager {
|
|
|
748
839
|
this.savedMessageCount = this.messages.length;
|
|
749
840
|
}
|
|
750
841
|
/**
|
|
751
|
-
*
|
|
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.
|
|
752
845
|
*/
|
|
753
|
-
async rewriteSessionFile(
|
|
846
|
+
async rewriteSessionFile(keepMessageCount) {
|
|
754
847
|
try {
|
|
755
|
-
|
|
756
|
-
const content = messages
|
|
757
|
-
.map((m) => {
|
|
758
|
-
const { timestamp, ...rest } = m;
|
|
759
|
-
return JSON.stringify({ timestamp, ...rest });
|
|
760
|
-
})
|
|
761
|
-
.join("\n") + (messages.length > 0 ? "\n" : "");
|
|
762
|
-
await writeFile(this.transcriptPath, content, "utf8");
|
|
848
|
+
await truncateSession(this.sessionId, keepMessageCount, this.workdir, this.sessionType);
|
|
763
849
|
}
|
|
764
850
|
catch (error) {
|
|
765
851
|
logger?.error("Failed to rewrite session file:", error);
|
|
@@ -856,6 +942,10 @@ export class MessageManager {
|
|
|
856
942
|
this.invokedSkills.set(skillName, {
|
|
857
943
|
skillName,
|
|
858
944
|
timestamp: Date.now(),
|
|
945
|
+
// 模型当时实际看到的那份正文(已替换 ${WAVE_SKILL_DIR} 等占位符、
|
|
946
|
+
// 首行带 `Base directory for this skill:`)——压缩后回灌直接复用它,
|
|
947
|
+
// 不能重新读文件原文(spec core/message-compact 场景 4)。
|
|
948
|
+
content: block.result,
|
|
859
949
|
});
|
|
860
950
|
}
|
|
861
951
|
}
|
|
@@ -866,15 +956,14 @@ export class MessageManager {
|
|
|
866
956
|
}
|
|
867
957
|
}
|
|
868
958
|
/**
|
|
869
|
-
* Get recently invoked
|
|
959
|
+
* Get recently invoked skills, sorted by timestamp (newest first).
|
|
870
960
|
* @param maxSkills - Maximum number of skills to return
|
|
871
|
-
* @returns
|
|
961
|
+
* @returns Invoked skill records sorted by recency
|
|
872
962
|
*/
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
.sort((
|
|
963
|
+
getInvokedSkills(maxSkills = 10) {
|
|
964
|
+
return Array.from(this.invokedSkills.values())
|
|
965
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
876
966
|
.slice(0, maxSkills);
|
|
877
|
-
return sorted.map(([, { skillName }]) => skillName);
|
|
878
967
|
}
|
|
879
968
|
/**
|
|
880
969
|
* Clear all invoked skills (e.g., after compaction).
|
|
@@ -8,9 +8,22 @@
|
|
|
8
8
|
import type { PermissionDecision, ToolPermissionContext, PermissionCallback, PermissionMode } from "../types/permissions.js";
|
|
9
9
|
import type { Logger } from "../types/index.js";
|
|
10
10
|
import { Container } from "../utils/container.js";
|
|
11
|
+
/**
|
|
12
|
+
* Resolve an additional-directory config entry to an absolute path.
|
|
13
|
+
* Supports `~` / `~/` prefixes (expanded against the user home directory,
|
|
14
|
+
* aligned with Claude Code's expandPath) alongside absolute paths and paths
|
|
15
|
+
* relative to the working directory.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveAdditionalDirectory(dir: string, workdir: string | undefined): string;
|
|
11
18
|
export interface PermissionManagerOptions {
|
|
12
19
|
/** Configured permission mode from settings */
|
|
13
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;
|
|
14
27
|
/** Allowed rules from settings */
|
|
15
28
|
allowedRules?: string[];
|
|
16
29
|
/** Denied rules from settings */
|
|
@@ -33,6 +46,15 @@ export interface PermissionManagerOptions {
|
|
|
33
46
|
export declare class PermissionManager {
|
|
34
47
|
private container;
|
|
35
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?;
|
|
36
58
|
private allowedRules;
|
|
37
59
|
private deniedRules;
|
|
38
60
|
private instanceAllowedRules;
|
|
@@ -66,6 +88,17 @@ export declare class PermissionManager {
|
|
|
66
88
|
* Get the configured default mode
|
|
67
89
|
*/
|
|
68
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;
|
|
69
102
|
/**
|
|
70
103
|
* Get all currently allowed rules (user-defined)
|
|
71
104
|
*/
|
|
@@ -87,6 +120,13 @@ export declare class PermissionManager {
|
|
|
87
120
|
* Unlike addPermissionRule, this does NOT persist to settings.local.json.
|
|
88
121
|
*/
|
|
89
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;
|
|
90
130
|
/**
|
|
91
131
|
* Get all additional directories
|
|
92
132
|
*/
|
|
@@ -136,6 +176,13 @@ export declare class PermissionManager {
|
|
|
136
176
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
137
177
|
*/
|
|
138
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;
|
|
139
186
|
/**
|
|
140
187
|
* Set the current plan file path
|
|
141
188
|
*/
|
|
@@ -192,10 +239,32 @@ export declare class PermissionManager {
|
|
|
192
239
|
* command substitution, process substitution, or sed -i are auto-allowed.
|
|
193
240
|
*/
|
|
194
241
|
private isAutoAllowedPart;
|
|
242
|
+
/**
|
|
243
|
+
* Check whether a bash command part takes path arguments (cd or a
|
|
244
|
+
* path-taking read-only command) that escape the Safe Zone. Used to
|
|
245
|
+
* disqualify auto-allow via DEFAULT_ALLOWED_RULES and to hide the
|
|
246
|
+
* "don't ask again" persistence option for out-of-bounds access.
|
|
247
|
+
*/
|
|
248
|
+
private isOutOfSafeZonePart;
|
|
195
249
|
/**
|
|
196
250
|
* Check if a tool call is allowed by persistent or temporary rules
|
|
197
251
|
*/
|
|
198
252
|
private isAllowedByRule;
|
|
253
|
+
/**
|
|
254
|
+
* Classify one expanded leaf command. Every leaf of a compound command or
|
|
255
|
+
* command substitution is judged with the exact rules used for a plain
|
|
256
|
+
* command, with two structure-aware additions:
|
|
257
|
+
*
|
|
258
|
+
* - a leaf the parser flagged `unsafe` (a bare reference to a variable whose
|
|
259
|
+
* value is not statically known, or a command substitution inside the
|
|
260
|
+
* leaf's own argument words) is never auto-allowed and never matches the
|
|
261
|
+
* built-in default rules — only an explicit allow rule may cover it;
|
|
262
|
+
* - `read` (e.g. the condition of `while read …`) only reads stdin, so it
|
|
263
|
+
* is treated as safe when it carries no redirection of its own.
|
|
264
|
+
*/
|
|
265
|
+
private isBashLeafAllowed;
|
|
266
|
+
/** A context whose Bash command is a single part/leaf text. */
|
|
267
|
+
private ruleContext;
|
|
199
268
|
/**
|
|
200
269
|
* Expand a bash command into individual permission rules, filtering out safe commands.
|
|
201
270
|
* Used when saving permissions to the allow list.
|