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.
Files changed (210) hide show
  1. package/dist/agent.d.ts +128 -28
  2. package/dist/agent.js +201 -49
  3. package/dist/builtin/index.js +2 -0
  4. package/dist/builtin/plugins.js +11 -20
  5. package/dist/builtin/skills/settings.js +7 -20
  6. package/dist/builtin/skills/wave-daemon.d.ts +1 -0
  7. package/dist/builtin/skills/wave-daemon.js +194 -0
  8. package/dist/constants/images.d.ts +26 -0
  9. package/dist/constants/images.js +26 -0
  10. package/dist/constants/index.d.ts +16 -0
  11. package/dist/constants/index.js +16 -0
  12. package/dist/constants/memory.d.ts +26 -0
  13. package/dist/constants/memory.js +34 -0
  14. package/dist/constants/messages.d.ts +11 -0
  15. package/dist/constants/messages.js +11 -0
  16. package/dist/constants/plugins.d.ts +8 -0
  17. package/dist/constants/plugins.js +8 -0
  18. package/dist/constants/tools.d.ts +1 -0
  19. package/dist/constants/tools.js +1 -0
  20. package/dist/core/plugin.d.ts +54 -10
  21. package/dist/core/plugin.js +137 -23
  22. package/dist/core/session.d.ts +1 -1
  23. package/dist/core/session.js +1 -1
  24. package/dist/exec/catalog.d.ts +140 -0
  25. package/dist/exec/catalog.js +470 -0
  26. package/dist/exec/catalogAnnouncement.d.ts +89 -0
  27. package/dist/exec/catalogAnnouncement.js +293 -0
  28. package/dist/exec/constants.d.ts +51 -0
  29. package/dist/exec/constants.js +51 -0
  30. package/dist/exec/execRuntime.d.ts +55 -0
  31. package/dist/exec/execRuntime.js +217 -0
  32. package/dist/exec/workerSource.d.ts +28 -0
  33. package/dist/exec/workerSource.js +299 -0
  34. package/dist/host/index.d.ts +23 -0
  35. package/dist/host/index.js +23 -0
  36. package/dist/index.d.ts +7 -1
  37. package/dist/index.js +8 -1
  38. package/dist/managers/MemoryRuleManager.d.ts +6 -0
  39. package/dist/managers/MemoryRuleManager.js +12 -0
  40. package/dist/managers/aiManager.d.ts +35 -25
  41. package/dist/managers/aiManager.js +204 -202
  42. package/dist/managers/backgroundTaskManager.js +14 -0
  43. package/dist/managers/bashModeManager.d.ts +33 -0
  44. package/dist/managers/bashModeManager.js +110 -0
  45. package/dist/managers/hookManager.d.ts +18 -0
  46. package/dist/managers/hookManager.js +37 -3
  47. package/dist/managers/liveConfigManager.d.ts +33 -0
  48. package/dist/managers/liveConfigManager.js +106 -11
  49. package/dist/managers/lspManager.d.ts +9 -0
  50. package/dist/managers/lspManager.js +47 -18
  51. package/dist/managers/mcpManager.d.ts +68 -10
  52. package/dist/managers/mcpManager.js +265 -15
  53. package/dist/managers/messageManager.d.ts +60 -18
  54. package/dist/managers/messageManager.js +170 -81
  55. package/dist/managers/permissionManager.d.ts +69 -0
  56. package/dist/managers/permissionManager.js +221 -78
  57. package/dist/managers/planManager.d.ts +9 -0
  58. package/dist/managers/planManager.js +19 -1
  59. package/dist/managers/pluginManager.d.ts +46 -2
  60. package/dist/managers/pluginManager.js +117 -11
  61. package/dist/managers/pluginScopeManager.d.ts +15 -2
  62. package/dist/managers/pluginScopeManager.js +20 -1
  63. package/dist/managers/skillManager.d.ts +50 -0
  64. package/dist/managers/skillManager.js +166 -12
  65. package/dist/managers/slashCommandManager.d.ts +10 -0
  66. package/dist/managers/slashCommandManager.js +44 -31
  67. package/dist/managers/subagentManager.d.ts +15 -0
  68. package/dist/managers/subagentManager.js +81 -9
  69. package/dist/managers/toolManager.d.ts +29 -3
  70. package/dist/managers/toolManager.js +87 -13
  71. package/dist/managers/workflowManager.js +6 -0
  72. package/dist/prompts/autoMemory.d.ts +9 -0
  73. package/dist/prompts/autoMemory.js +30 -31
  74. package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
  75. package/dist/prompts/autoMemoryExtraction.js +8 -111
  76. package/dist/prompts/index.d.ts +0 -1
  77. package/dist/prompts/index.js +0 -4
  78. package/dist/prompts/memoryTypes.d.ts +63 -0
  79. package/dist/prompts/memoryTypes.js +191 -0
  80. package/dist/services/GitService.d.ts +7 -0
  81. package/dist/services/GitService.js +23 -0
  82. package/dist/services/MarketplaceService.d.ts +101 -17
  83. package/dist/services/MarketplaceService.js +323 -102
  84. package/dist/services/artifactContent.d.ts +84 -0
  85. package/dist/services/artifactContent.js +204 -0
  86. package/dist/services/artifactSession.d.ts +6 -0
  87. package/dist/services/artifactSession.js +17 -0
  88. package/dist/services/autoMemoryService.js +5 -13
  89. package/dist/services/configurationService.d.ts +92 -9
  90. package/dist/services/configurationService.js +246 -64
  91. package/dist/services/contentSummarizer.d.ts +15 -0
  92. package/dist/services/contentSummarizer.js +45 -0
  93. package/dist/services/execAvailability.d.ts +9 -0
  94. package/dist/services/execAvailability.js +32 -0
  95. package/dist/services/fileWatcher.js +61 -6
  96. package/dist/services/initializationService.js +21 -17
  97. package/dist/services/interactionService.d.ts +9 -1
  98. package/dist/services/interactionService.js +28 -8
  99. package/dist/services/jsonlHandler.d.ts +98 -0
  100. package/dist/services/jsonlHandler.js +250 -12
  101. package/dist/services/memory.d.ts +17 -1
  102. package/dist/services/memory.js +44 -7
  103. package/dist/services/officialMarketplaceMirror.d.ts +85 -0
  104. package/dist/services/officialMarketplaceMirror.js +290 -0
  105. package/dist/services/pluginLoader.d.ts +12 -4
  106. package/dist/services/pluginLoader.js +38 -7
  107. package/dist/services/remoteSettingsService.js +20 -6
  108. package/dist/services/session.d.ts +74 -0
  109. package/dist/services/session.js +174 -16
  110. package/dist/services/sessionEntries.d.ts +2 -0
  111. package/dist/services/sessionEntries.js +20 -0
  112. package/dist/services/worktreeHooks.js +6 -1
  113. package/dist/stdio/index.d.ts +12 -0
  114. package/dist/stdio/index.js +12 -0
  115. package/dist/stdio/notificationRouter.d.ts +38 -0
  116. package/dist/stdio/notificationRouter.js +97 -0
  117. package/dist/stdio/rpcClient.d.ts +18 -0
  118. package/dist/stdio/rpcClient.js +10 -0
  119. package/dist/stdio/stdioAgent.d.ts +229 -0
  120. package/dist/stdio/stdioAgent.js +360 -0
  121. package/dist/tools/artifactTool.js +406 -273
  122. package/dist/tools/bashTool.js +10 -6
  123. package/dist/tools/editTool.js +6 -3
  124. package/dist/tools/execTool.d.ts +2 -0
  125. package/dist/tools/execTool.js +165 -0
  126. package/dist/tools/exitPlanMode.js +10 -2
  127. package/dist/tools/grepTool.js +7 -1
  128. package/dist/tools/readTool.js +30 -2
  129. package/dist/tools/types.d.ts +34 -8
  130. package/dist/tools/webFetchTool.js +15 -166
  131. package/dist/tools/workflowTool.js +40 -8
  132. package/dist/tools/writeTool.js +6 -3
  133. package/dist/types/agent.d.ts +24 -1
  134. package/dist/types/commands.d.ts +7 -0
  135. package/dist/types/configuration.d.ts +45 -2
  136. package/dist/types/hooks.d.ts +1 -0
  137. package/dist/types/hooks.js +19 -0
  138. package/dist/types/marketplace.d.ts +40 -2
  139. package/dist/types/mcp.d.ts +42 -0
  140. package/dist/types/messaging.d.ts +1 -8
  141. package/dist/types/permissions.d.ts +22 -0
  142. package/dist/types/permissions.js +17 -0
  143. package/dist/types/plugins.d.ts +26 -2
  144. package/dist/types/skills.d.ts +26 -0
  145. package/dist/utils/bashParser.d.ts +17 -0
  146. package/dist/utils/bashParser.js +72 -0
  147. package/dist/utils/bashStructure/bashLexer.d.ts +96 -0
  148. package/dist/utils/bashStructure/bashLexer.js +676 -0
  149. package/dist/utils/bashStructure/bashParser.d.ts +144 -0
  150. package/dist/utils/bashStructure/bashParser.js +606 -0
  151. package/dist/utils/bashStructure/bashSemantics.d.ts +70 -0
  152. package/dist/utils/bashStructure/bashSemantics.js +477 -0
  153. package/dist/utils/bashStructure/index.d.ts +26 -0
  154. package/dist/utils/bashStructure/index.js +27 -0
  155. package/dist/utils/bashStructure/types.d.ts +62 -0
  156. package/dist/utils/bashStructure/types.js +47 -0
  157. package/dist/utils/constants.d.ts +10 -0
  158. package/dist/utils/constants.js +10 -0
  159. package/dist/utils/containerSetup.js +48 -6
  160. package/dist/utils/convertMessagesForAPI.d.ts +7 -1
  161. package/dist/utils/convertMessagesForAPI.js +64 -14
  162. package/dist/utils/fileChangeReminder.d.ts +20 -0
  163. package/dist/utils/fileChangeReminder.js +153 -0
  164. package/dist/utils/fileSearch.js +4 -3
  165. package/dist/utils/fileUtils.d.ts +44 -0
  166. package/dist/utils/fileUtils.js +118 -0
  167. package/dist/utils/frontmatterYaml.d.ts +33 -0
  168. package/dist/utils/frontmatterYaml.js +192 -0
  169. package/dist/utils/imageBudget.d.ts +85 -0
  170. package/dist/utils/imageBudget.js +109 -0
  171. package/dist/utils/imageDimensions.d.ts +83 -0
  172. package/dist/utils/imageDimensions.js +232 -0
  173. package/dist/utils/imageProcessor.d.ts +66 -0
  174. package/dist/utils/imageProcessor.js +84 -0
  175. package/dist/utils/imageRewrite.d.ts +29 -0
  176. package/dist/utils/imageRewrite.js +251 -0
  177. package/dist/utils/markdownParser.d.ts +5 -1
  178. package/dist/utils/markdownParser.js +9 -51
  179. package/dist/utils/mcpInstructions.d.ts +61 -0
  180. package/dist/utils/mcpInstructions.js +126 -0
  181. package/dist/utils/mcpUtils.d.ts +7 -0
  182. package/dist/utils/mcpUtils.js +11 -2
  183. package/dist/utils/memoryAge.d.ts +32 -0
  184. package/dist/utils/memoryAge.js +47 -0
  185. package/dist/utils/memoryEntrypoint.d.ts +20 -0
  186. package/dist/utils/memoryEntrypoint.js +49 -0
  187. package/dist/utils/memoryIndex.d.ts +30 -0
  188. package/dist/utils/memoryIndex.js +76 -0
  189. package/dist/utils/messageOperations.d.ts +6 -20
  190. package/dist/utils/messageOperations.js +40 -91
  191. package/dist/utils/nestedMemory.d.ts +22 -0
  192. package/dist/utils/nestedMemory.js +61 -0
  193. package/dist/utils/npmTarball.d.ts +19 -0
  194. package/dist/utils/npmTarball.js +92 -0
  195. package/dist/utils/pluginSource.d.ts +37 -0
  196. package/dist/utils/pluginSource.js +73 -0
  197. package/dist/utils/ripgrep.d.ts +18 -4
  198. package/dist/utils/ripgrep.js +56 -4
  199. package/dist/utils/runtimeDeps.d.ts +35 -0
  200. package/dist/utils/runtimeDeps.js +426 -0
  201. package/dist/utils/skillParser.js +22 -52
  202. package/dist/utils/subagentParser.js +48 -45
  203. package/dist/utils/tokenCalculation.js +0 -8
  204. package/dist/utils/userSettings.d.ts +90 -0
  205. package/dist/utils/userSettings.js +291 -0
  206. package/dist/utils/worktreeUtils.d.ts +2 -1
  207. package/dist/utils/worktreeUtils.js +64 -34
  208. package/package.json +12 -4
  209. package/dist/managers/bangManager.d.ts +0 -26
  210. package/dist/managers/bangManager.js +0 -78
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared artifact content layer.
3
+ *
4
+ * The single implementation of artifact content retrieval: slug extraction,
5
+ * metadata probe (`via=model_read`), ownership detection, Bearer-authenticated
6
+ * content fetch, HTML→markdown conversion and large-content persistence.
7
+ * Both the Artifact tool's `read` action and WebFetch's artifact URL
8
+ * interception call into it (spec: 读取实现单一化).
9
+ */
10
+ import type { ToolContext, ToolResult } from "../tools/types.js";
11
+ /** Artifact content beyond this size is persisted to a temp file (path + preview). */
12
+ export declare const ARTIFACT_PREVIEW_BYTES: number;
13
+ /** Used when the caller reads someone else's artifact without a `prompt`. */
14
+ export declare const DEFAULT_READ_SUMMARY_PROMPT = "Summarize this page: its purpose, structure, and key content.";
15
+ /** Frame metadata returned by `GET /api/frame/{slug}?via=model_read`. */
16
+ export interface FrameMeta {
17
+ slug?: string;
18
+ version?: string;
19
+ title?: string;
20
+ favicon?: string;
21
+ perm?: {
22
+ mode: "owner" | "users" | "org";
23
+ role?: string;
24
+ };
25
+ url?: string;
26
+ contentUrl?: string;
27
+ /** Empty/missing = shared-live (readers see live updates); non-empty = pinned. */
28
+ shared?: string;
29
+ }
30
+ /** The publishing user's relationship to an artifact. */
31
+ export type ArtifactOwnership = "owner" | "reader";
32
+ export interface ArtifactContent {
33
+ slug: string;
34
+ version: string;
35
+ ownership: ArtifactOwnership;
36
+ /** Raw HTML as stored server-side (inline CSS/JS preserved). */
37
+ html: string;
38
+ bytes: number;
39
+ }
40
+ export type FrameMetaResult = {
41
+ kind: "ok";
42
+ meta: FrameMeta;
43
+ } | {
44
+ kind: "error";
45
+ status: number;
46
+ error: string;
47
+ };
48
+ export type ArtifactContentResult = {
49
+ kind: "ok";
50
+ artifact: ArtifactContent;
51
+ } | {
52
+ kind: "error";
53
+ status?: number;
54
+ error: string;
55
+ };
56
+ /** Extract the artifact slug from a `{host}/code/artifact/{slug}` URL. */
57
+ export declare function extractArtifactSlug(url: string): string | null;
58
+ /** Probe artifact metadata; `via=model_read` marks model access. */
59
+ export declare function fetchFrameMeta(slug: string, opts?: {
60
+ abortSignal?: AbortSignal;
61
+ }): Promise<FrameMetaResult>;
62
+ /**
63
+ * Read an artifact's content. Owner reads come back as raw HTML (inline CSS/JS
64
+ * intact); ownership is decided from the server metadata so readers of someone
65
+ * else's page can be routed to the isolated-summary path.
66
+ */
67
+ export declare function readArtifactContent(slug: string, opts?: {
68
+ url?: string;
69
+ abortSignal?: AbortSignal;
70
+ }): Promise<ArtifactContentResult>;
71
+ /** Convert artifact HTML to markdown (the summary path feeds text, not markup). */
72
+ export declare function htmlToMarkdown(html: string): string;
73
+ /** Whether content is too large to return inline. */
74
+ export declare function isLargeArtifactContent(content: string): boolean;
75
+ /**
76
+ * Persist content to a temp file and return the `<persisted-output>` message.
77
+ * Returns null when the content is small enough to inline, or on write failure.
78
+ */
79
+ export declare function persistArtifactContent(content: string): string | null;
80
+ /**
81
+ * Reader view: run someone else's artifact through the fast model and return
82
+ * only the answer, so the full page never reaches the main conversation.
83
+ */
84
+ export declare function summarizeArtifactAsReader(url: string, prompt: string, html: string, context: ToolContext): Promise<ToolResult>;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Shared artifact content layer.
3
+ *
4
+ * The single implementation of artifact content retrieval: slug extraction,
5
+ * metadata probe (`via=model_read`), ownership detection, Bearer-authenticated
6
+ * content fetch, HTML→markdown conversion and large-content persistence.
7
+ * Both the Artifact tool's `read` action and WebFetch's artifact URL
8
+ * interception call into it (spec: 读取实现单一化).
9
+ */
10
+ import TurndownService from "turndown";
11
+ import { authService, createAuthAwareFetch } from "./authService.js";
12
+ import { logger } from "../utils/globalLogger.js";
13
+ import { buildPersistedOutputMessage, generatePreview, persistToolResult, } from "../utils/toolResultStorage.js";
14
+ import { processContentWithAI } from "./contentSummarizer.js";
15
+ /** Metadata/content request timeout. */
16
+ const ARTIFACT_READ_TIMEOUT_MS = 60000;
17
+ /** Artifact content beyond this size is persisted to a temp file (path + preview). */
18
+ export const ARTIFACT_PREVIEW_BYTES = 2 * 1024;
19
+ /** Inline fallback when persisting large content fails. */
20
+ const MAX_INLINE_ARTIFACT_CHARS = 100000;
21
+ /** Used when the caller reads someone else's artifact without a `prompt`. */
22
+ export const DEFAULT_READ_SUMMARY_PROMPT = "Summarize this page: its purpose, structure, and key content.";
23
+ /** Extract the artifact slug from a `{host}/code/artifact/{slug}` URL. */
24
+ export function extractArtifactSlug(url) {
25
+ try {
26
+ const parsed = new URL(url);
27
+ const match = parsed.pathname.match(/^\/code\/artifact\/([^/]+)\/?$/);
28
+ return match ? decodeURIComponent(match[1]) : null;
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ function readSignal(abortSignal) {
35
+ return abortSignal
36
+ ? AbortSignal.any([
37
+ abortSignal,
38
+ AbortSignal.timeout(ARTIFACT_READ_TIMEOUT_MS),
39
+ ])
40
+ : AbortSignal.timeout(ARTIFACT_READ_TIMEOUT_MS);
41
+ }
42
+ /** Probe artifact metadata; `via=model_read` marks model access. */
43
+ export async function fetchFrameMeta(slug, opts = {}) {
44
+ const serverUrl = authService.getServerUrl();
45
+ const authFetch = createAuthAwareFetch(globalThis.fetch);
46
+ try {
47
+ const res = await authFetch(`${serverUrl}/api/frame/${encodeURIComponent(slug)}?via=model_read`, { method: "GET", signal: readSignal(opts.abortSignal) });
48
+ if (!res.ok) {
49
+ return {
50
+ kind: "error",
51
+ status: res.status,
52
+ error: `Failed to fetch artifact metadata: ${res.status} ${res.statusText}`,
53
+ };
54
+ }
55
+ return { kind: "ok", meta: (await res.json()) };
56
+ }
57
+ catch (error) {
58
+ logger?.warn("Artifact metadata probe failed", {
59
+ slug,
60
+ error: String(error),
61
+ });
62
+ return {
63
+ kind: "error",
64
+ status: 0,
65
+ error: `Failed to fetch artifact metadata: ${error instanceof Error ? error.message : String(error)}`,
66
+ };
67
+ }
68
+ }
69
+ /**
70
+ * Read an artifact's content. Owner reads come back as raw HTML (inline CSS/JS
71
+ * intact); ownership is decided from the server metadata so readers of someone
72
+ * else's page can be routed to the isolated-summary path.
73
+ */
74
+ export async function readArtifactContent(slug, opts = {}) {
75
+ const displayUrl = opts.url || slug;
76
+ const metaResult = await fetchFrameMeta(slug, {
77
+ abortSignal: opts.abortSignal,
78
+ });
79
+ if (metaResult.kind === "error") {
80
+ if (metaResult.status === 404) {
81
+ return {
82
+ kind: "error",
83
+ status: 404,
84
+ error: `Artifact not found: ${displayUrl} (it may have been deleted)`,
85
+ };
86
+ }
87
+ if (metaResult.status === 403) {
88
+ return {
89
+ kind: "error",
90
+ status: 403,
91
+ error: `You do not have permission to read this artifact: ${displayUrl}`,
92
+ };
93
+ }
94
+ return {
95
+ kind: "error",
96
+ status: metaResult.status,
97
+ error: metaResult.error,
98
+ };
99
+ }
100
+ const meta = metaResult.meta;
101
+ const version = typeof meta.version === "string" ? meta.version : "";
102
+ const contentUrl = typeof meta.contentUrl === "string" ? meta.contentUrl : "";
103
+ if (!contentUrl) {
104
+ return {
105
+ kind: "error",
106
+ error: "Artifact metadata did not include a contentUrl",
107
+ };
108
+ }
109
+ // Unknown ownership is treated as reader-owned: never hand out full text of a
110
+ // page we cannot prove the user owns.
111
+ const ownership = meta.perm?.mode === "owner" || meta.perm?.role === "owner"
112
+ ? "owner"
113
+ : "reader";
114
+ const serverUrl = authService.getServerUrl();
115
+ const authFetch = createAuthAwareFetch(globalThis.fetch);
116
+ let html;
117
+ try {
118
+ const res = await authFetch(new URL(contentUrl, serverUrl).toString(), {
119
+ method: "GET",
120
+ signal: readSignal(opts.abortSignal),
121
+ });
122
+ if (res.status === 403) {
123
+ return {
124
+ kind: "error",
125
+ status: 403,
126
+ error: `You do not have permission to read this artifact: ${displayUrl}`,
127
+ };
128
+ }
129
+ if (!res.ok) {
130
+ return {
131
+ kind: "error",
132
+ status: res.status,
133
+ error: `Failed to fetch artifact content: ${res.status} ${res.statusText}`,
134
+ };
135
+ }
136
+ html = await res.text();
137
+ }
138
+ catch (error) {
139
+ logger?.warn("Artifact content fetch failed", {
140
+ slug,
141
+ error: String(error),
142
+ });
143
+ return {
144
+ kind: "error",
145
+ error: `Failed to fetch artifact content: ${error instanceof Error ? error.message : String(error)}`,
146
+ };
147
+ }
148
+ return {
149
+ kind: "ok",
150
+ artifact: {
151
+ slug,
152
+ version,
153
+ ownership,
154
+ html,
155
+ bytes: new TextEncoder().encode(html).length,
156
+ },
157
+ };
158
+ }
159
+ /** Convert artifact HTML to markdown (the summary path feeds text, not markup). */
160
+ export function htmlToMarkdown(html) {
161
+ return new TurndownService().turndown(html);
162
+ }
163
+ /** Whether content is too large to return inline. */
164
+ export function isLargeArtifactContent(content) {
165
+ return new TextEncoder().encode(content).length > ARTIFACT_PREVIEW_BYTES;
166
+ }
167
+ /**
168
+ * Persist content to a temp file and return the `<persisted-output>` message.
169
+ * Returns null when the content is small enough to inline, or on write failure.
170
+ */
171
+ export function persistArtifactContent(content) {
172
+ if (!isLargeArtifactContent(content))
173
+ return null;
174
+ const filePath = persistToolResult(content, "artifact");
175
+ if (!filePath)
176
+ return null;
177
+ return buildPersistedOutputMessage(content.length, filePath, generatePreview(content));
178
+ }
179
+ /**
180
+ * Reader view: run someone else's artifact through the fast model and return
181
+ * only the answer, so the full page never reaches the main conversation.
182
+ */
183
+ export async function summarizeArtifactAsReader(url, prompt, html, context) {
184
+ const markdown = htmlToMarkdown(html);
185
+ const bytes = new TextEncoder().encode(markdown).length;
186
+ const persisted = persistArtifactContent(markdown);
187
+ let aiInput = markdown;
188
+ if (persisted) {
189
+ aiInput = persisted;
190
+ }
191
+ else if (bytes > ARTIFACT_PREVIEW_BYTES) {
192
+ aiInput =
193
+ markdown.substring(0, MAX_INLINE_ARTIFACT_CHARS) +
194
+ "\n\n... (content truncated, failed to persist full output)";
195
+ }
196
+ const result = await processContentWithAI(url, prompt || DEFAULT_READ_SUMMARY_PROMPT, aiInput, 200, "OK", context, bytes);
197
+ if (persisted) {
198
+ // Append the persisted-output message so the model can Read the full text.
199
+ result.content = result.content
200
+ ? result.content + "\n\n" + persisted
201
+ : persisted;
202
+ }
203
+ return result;
204
+ }
@@ -7,6 +7,8 @@
7
7
  * - slug → latest known version: powers the stale-version guard
8
8
  * (local knowledge older than the live version blocks a redeploy
9
9
  * unless `force` is set) and lets WebFetch record versions it observed.
10
+ * - slug → read approval: reading someone else's artifact is confirmed once
11
+ * per session; later reads of the same page are not re-confirmed.
10
12
  *
11
13
  * State is keyed by sessionId so parallel sessions never observe each other.
12
14
  */
@@ -23,5 +25,9 @@ export declare function getArtifactByFilePath(sessionId: string, filePath: strin
23
25
  export declare function getRecordedVersion(sessionId: string, slug: string): string | undefined;
24
26
  /** Record an observed version (from a publish, a 409 conflict, or a WebFetch read). */
25
27
  export declare function recordVersion(sessionId: string, slug: string, version: string): void;
28
+ /** Remember that the user approved reading someone else's artifact this session. */
29
+ export declare function markArtifactReadApproved(sessionId: string, slug: string): void;
30
+ /** Whether the user already approved reading this artifact in this session. */
31
+ export declare function isArtifactReadApproved(sessionId: string, slug: string): boolean;
26
32
  /** Clear all session-scoped artifact state (used when a session ends). */
27
33
  export declare function clearArtifactSession(sessionId: string): void;
@@ -7,11 +7,14 @@
7
7
  * - slug → latest known version: powers the stale-version guard
8
8
  * (local knowledge older than the live version blocks a redeploy
9
9
  * unless `force` is set) and lets WebFetch record versions it observed.
10
+ * - slug → read approval: reading someone else's artifact is confirmed once
11
+ * per session; later reads of the same page are not re-confirmed.
10
12
  *
11
13
  * State is keyed by sessionId so parallel sessions never observe each other.
12
14
  */
13
15
  const sessionArtifacts = new Map();
14
16
  const sessionSlugVersions = new Map();
17
+ const sessionReadApprovals = new Map();
15
18
  function fileMapFor(sessionId) {
16
19
  let map = sessionArtifacts.get(sessionId);
17
20
  if (!map) {
@@ -45,8 +48,22 @@ export function getRecordedVersion(sessionId, slug) {
45
48
  export function recordVersion(sessionId, slug, version) {
46
49
  slugMapFor(sessionId).set(slug, version);
47
50
  }
51
+ /** Remember that the user approved reading someone else's artifact this session. */
52
+ export function markArtifactReadApproved(sessionId, slug) {
53
+ let set = sessionReadApprovals.get(sessionId);
54
+ if (!set) {
55
+ set = new Set();
56
+ sessionReadApprovals.set(sessionId, set);
57
+ }
58
+ set.add(slug);
59
+ }
60
+ /** Whether the user already approved reading this artifact in this session. */
61
+ export function isArtifactReadApproved(sessionId, slug) {
62
+ return sessionReadApprovals.get(sessionId)?.has(slug) ?? false;
63
+ }
48
64
  /** Clear all session-scoped artifact state (used when a session ends). */
49
65
  export function clearArtifactSession(sessionId) {
50
66
  sessionArtifacts.delete(sessionId);
51
67
  sessionSlugVersions.delete(sessionId);
68
+ sessionReadApprovals.delete(sessionId);
52
69
  }
@@ -1,7 +1,7 @@
1
1
  import * as path from "node:path";
2
- import * as fs from "node:fs/promises";
3
2
  import { logger } from "../utils/globalLogger.js";
4
3
  import { isPathInside } from "../utils/pathSafety.js";
4
+ import { formatMemoryManifest, scanMemoryFiles } from "../utils/memoryIndex.js";
5
5
  import { buildAutoMemoryExtractionPrompt } from "../prompts/autoMemoryExtraction.js";
6
6
  import { READ_ONLY_COMMANDS, splitBashCommand, hasWriteRedirections, hasCommandSubstitution, hasProcessSubstitution, hasSedInPlace, isDangerousFind, } from "../utils/bashParser.js";
7
7
  /**
@@ -140,18 +140,10 @@ export class AutoMemoryService {
140
140
  const memoryDir = this.memoryService.getAutoMemoryDirectory(workdir);
141
141
  // Ensure memory directory exists before starting
142
142
  await this.memoryService.ensureAutoMemoryDirectory(workdir);
143
- // Prepare manifest of existing memory files
144
- let existingMemoriesManifest = "";
145
- try {
146
- const files = await fs.readdir(memoryDir);
147
- existingMemoriesManifest = files
148
- .filter((f) => f.endsWith(".md"))
149
- .map((f) => `- ${f}`)
150
- .join("\n");
151
- }
152
- catch {
153
- // Ignore if directory doesn't exist yet
154
- }
143
+ // Prepare manifest of existing memory files: type tag, filename, timestamp
144
+ // and description, so the fork can tell whether a memory already exists
145
+ // without spending a turn on `ls`.
146
+ const existingMemoriesManifest = formatMemoryManifest(await scanMemoryFiles(memoryDir));
155
147
  const prompt = buildAutoMemoryExtractionPrompt(newMessageCount, existingMemoriesManifest);
156
148
  await this.aiManager.runAutoMemoryFork(messages, `${prompt}\n\nThe memory directory for this project is: ${memoryDir}`, {
157
149
  maxTurns: 5, // Limit turns to prevent verification rabbit-holes
@@ -4,6 +4,7 @@
4
4
  * Centralized service for loading, validating, and managing Wave configuration files.
5
5
  * Replaces distributed configuration logic previously embedded in hook.ts.
6
6
  */
7
+ import type { HookEvent, HookEventConfig } from "../types/hooks.js";
7
8
  import type { ConfigurationLoadResult, ValidationResult, ConfigurationPaths, WaveConfiguration, Scope, MarketplaceConfig } from "../types/configuration.js";
8
9
  import { type EnvironmentValidationResult, type MergedEnvironmentContext, type EnvironmentMergeOptions } from "../types/environment.js";
9
10
  import { GatewayConfig, ModelConfig, PermissionMode, AgentOptions } from "../types/index.js";
@@ -14,6 +15,15 @@ import { ClientOptions } from "openai";
14
15
  * session cleanup runs before config is loaded).
15
16
  */
16
17
  export declare function validateConfigurationObject(config: WaveConfiguration): ValidationResult;
18
+ /**
19
+ * The settings.json-derived configuration a turn reads from, captured when the
20
+ * turn started (see {@link ConfigurationService.setTurnSnapshotSource}). Held
21
+ * while a turn runs so a live reload can't shift values under it.
22
+ */
23
+ export interface TurnConfigurationSnapshot {
24
+ configuration: WaveConfiguration | null;
25
+ env: Record<string, string>;
26
+ }
17
27
  /**
18
28
  * Default ConfigurationService implementation
19
29
  *
@@ -25,10 +35,19 @@ export declare class ConfigurationService {
25
35
  private options;
26
36
  private _configuredEnvKeys;
27
37
  private envSnapshot;
38
+ private turnSnapshotSource?;
39
+ /**
40
+ * Register the turn-snapshot source (see {@link TurnConfigurationSnapshot}).
41
+ */
42
+ setTurnSnapshotSource(source: () => TurnConfigurationSnapshot | null): void;
28
43
  /**
29
44
  * Set agent options for configuration resolution
30
45
  */
31
46
  setOptions(options: AgentOptions): void;
47
+ /** Configuration the current turn reads from (live configuration outside a turn). */
48
+ private get liveConfiguration();
49
+ /** Environment snapshot the current turn reads from. */
50
+ private get liveEnvSnapshot();
32
51
  /**
33
52
  * Returns a copy of the per-session environment snapshot (settings.json `env`).
34
53
  * Priority over OS env; does NOT include OS env. For subprocess spawning use
@@ -100,17 +119,30 @@ export declare class ConfigurationService {
100
119
  resolveMaxInputTokens(constructorLimit?: number, model?: string): number;
101
120
  /**
102
121
  * Resolves preferred language with fallbacks
103
- * Resolution priority: override > options > settings.json > undefined
122
+ * Resolution priority: override > options > settings.json > default (`zh-CN`)
123
+ *
124
+ * 末尾的默认值保证「未设置」时也有明确生效值,且与设置页下拉的默认项一致
125
+ * (spec agent-config 边界说明「语言默认值」)。调用方不得再假定
126
+ * 「undefined = 不注入语言指令」。
127
+ *
104
128
  * @param constructorLanguage - Language override (optional)
105
- * @returns Resolved language or undefined
129
+ * @returns Resolved language (never empty)
106
130
  */
107
- resolveLanguage(constructorLanguage?: string): string | undefined;
131
+ resolveLanguage(constructorLanguage?: string): string;
108
132
  /**
109
133
  * Resolves auto-memory enabled state with fallbacks
110
- * Resolution priority: settings.json > WAVE_DISABLE_AUTO_MEMORY > default (true)
134
+ * Resolution priority: session options (session-level override) > settings.json > WAVE_DISABLE_AUTO_MEMORY > default (true)
111
135
  * @returns Resolved auto-memory enabled state
112
136
  */
113
137
  resolveAutoMemoryEnabled(): boolean;
138
+ /**
139
+ * Same resolution chain but read from the live merged configuration, ignoring
140
+ * the turn snapshot. The permission safe zone is a live mechanism (like
141
+ * permission rules, applied by LiveConfigManager on reload), so its sync must
142
+ * not be pinned to the turn-start value.
143
+ */
144
+ resolveAutoMemoryEnabledNow(): boolean;
145
+ private resolveAutoMemoryEnabledFrom;
114
146
  /**
115
147
  * Resolves worktree base ref with fallbacks
116
148
  * Resolution priority: settings.json > default ("fresh")
@@ -119,7 +151,7 @@ export declare class ConfigurationService {
119
151
  resolveWorktreeBaseRef(): "fresh" | "head";
120
152
  /**
121
153
  * Resolves auto-memory extraction frequency with fallbacks
122
- * Resolution priority: settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
154
+ * Resolution priority: session options (程序化会话级覆盖) > settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
123
155
  * @returns Resolved auto-memory extraction frequency (turns)
124
156
  */
125
157
  resolveAutoMemoryFrequency(): number;
@@ -131,9 +163,13 @@ export declare class ConfigurationService {
131
163
  */
132
164
  resolveMaxOutputTokens(constructorLimit?: number): number;
133
165
  /**
134
- * Set the active model in the session
166
+ * Set the active model in the session.
167
+ *
168
+ * Persisting to `~/.wave/settings.json` is best-effort: the in-memory model
169
+ * applies immediately, and a failed write (e.g. a locked file / rename EPERM
170
+ * on Windows) is logged instead of escaping as an unhandled rejection.
135
171
  */
136
- setModel(model: string): void;
172
+ setModel(model: string): Promise<void>;
137
173
  private persistModelToSettings;
138
174
  /**
139
175
  * Get all configured models from settings.json and environment
@@ -161,9 +197,38 @@ export declare class ConfigurationService {
161
197
  */
162
198
  updateEnabledPlugin(workdir: string, scope: Scope, pluginId: string, enabled: boolean): Promise<void>;
163
199
  /**
164
- * Get merged marketplaces from all scopes
200
+ * Resolve the settings.json path for a hook scope.
201
+ * user → ~/.wave/settings.json; project → <workdir>/.wave/settings.json
202
+ */
203
+ private resolveHookConfigPath;
204
+ /**
205
+ * Absolute path of the settings.json that holds hooks for `scope`
206
+ * (user → ~/.wave/settings.json, project → <workdir>/.wave/settings.json).
207
+ * Hosts surface it to the GUI so editors / file panels can open the file —
208
+ * they cannot expand a `~` prefix, so the path must already be absolute.
209
+ */
210
+ getHookConfigPath(workdir: string, scope: "user" | "project"): string;
211
+ /**
212
+ * Get hook configurations at a specific scope (user or project settings.json)
213
+ */
214
+ getHooksByScope(workdir: string, scope: Scope): Promise<Partial<Record<HookEvent, HookEventConfig[]>>>;
215
+ /**
216
+ * Delete a hook at a specific scope, identified by hookName of the form
217
+ * `Event:Matcher`. Removes the matching entry from the scope's settings.json
218
+ * hooks block.
219
+ */
220
+ deleteHook(workdir: string, scope: Scope, hookName: string): Promise<void>;
221
+ /**
222
+ * Get merged marketplaces from all scopes, with admin-managed (remote) entries
223
+ * taking precedence per key (spec enterprise server-managed-config「托管配置
224
+ * 下发插件市场与启用列表」场景 5).
165
225
  */
166
226
  getMergedMarketplaces(workdir: string): Record<string, MarketplaceConfig>;
227
+ /**
228
+ * Marketplaces declared by the remote (managed) settings. `null` means the
229
+ * managed layer declares none, leaving the local configuration chain in charge.
230
+ */
231
+ getManagedMarketplaces(): Record<string, MarketplaceConfig> | null;
167
232
  /**
168
233
  * Get marketplaces at a specific scope
169
234
  */
@@ -181,9 +246,18 @@ export declare class ConfigurationService {
181
246
  */
182
247
  removeEnabledPlugin(workdir: string, scope: Scope, pluginId: string): Promise<void>;
183
248
  /**
184
- * Get merged enabled plugins from all scopes
249
+ * Get merged enabled plugins from all scopes, with admin-managed (remote)
250
+ * entries taking precedence per key (spec enterprise server-managed-config
251
+ * 「托管配置下发插件市场与启用列表」场景 5). A managed `false` therefore
252
+ * force-disables a plugin no matter what the local files say.
185
253
  */
186
254
  getMergedEnabledPlugins(workdir: string): Record<string, boolean>;
255
+ /**
256
+ * Enabled-plugin entries declared by the remote (managed) settings. `null`
257
+ * means the managed layer declares none. Doubles as the authoritative
258
+ * "is this plugin managed by the organization" lookup (spec plugin A-024).
259
+ */
260
+ getManagedEnabledPlugins(): Record<string, boolean> | null;
187
261
  /**
188
262
  * Load Wave configuration from a JSON file
189
263
  * Supports both hooks and environment variables with proper validation
@@ -216,3 +290,12 @@ export declare function loadUserConfigEnv(): Record<string, string>;
216
290
  * Checks .local.json files first, then falls back to .json files
217
291
  */
218
292
  export declare function loadMergedWaveConfig(workdir: string): WaveConfiguration | null;
293
+ /**
294
+ * Parse a hookName of the form `Event:Matcher` (e.g. `PreToolUse:Write`)
295
+ * into its event and matcher parts. Hooks without a matcher use the event
296
+ * name alone (e.g. `SessionStart`).
297
+ */
298
+ export declare function parseHookName(hookName: string): {
299
+ event: HookEvent;
300
+ matcher: string;
301
+ };