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.
Files changed (175) hide show
  1. package/dist/agent.d.ts +58 -4
  2. package/dist/agent.js +91 -19
  3. package/dist/builtin/index.js +2 -0
  4. package/dist/builtin/skills/settings.js +1 -12
  5. package/dist/builtin/skills/wave-daemon.d.ts +1 -0
  6. package/dist/builtin/skills/wave-daemon.js +194 -0
  7. package/dist/constants/images.d.ts +26 -0
  8. package/dist/constants/images.js +26 -0
  9. package/dist/constants/index.d.ts +16 -0
  10. package/dist/constants/index.js +16 -0
  11. package/dist/constants/memory.d.ts +26 -0
  12. package/dist/constants/memory.js +34 -0
  13. package/dist/constants/messages.d.ts +11 -0
  14. package/dist/constants/messages.js +11 -0
  15. package/dist/constants/plugins.d.ts +8 -0
  16. package/dist/constants/plugins.js +8 -0
  17. package/dist/constants/tools.d.ts +1 -0
  18. package/dist/constants/tools.js +1 -0
  19. package/dist/core/plugin.d.ts +53 -13
  20. package/dist/core/plugin.js +134 -26
  21. package/dist/core/session.d.ts +1 -1
  22. package/dist/core/session.js +1 -1
  23. package/dist/exec/catalog.d.ts +140 -0
  24. package/dist/exec/catalog.js +470 -0
  25. package/dist/exec/catalogAnnouncement.d.ts +89 -0
  26. package/dist/exec/catalogAnnouncement.js +293 -0
  27. package/dist/exec/constants.d.ts +51 -0
  28. package/dist/exec/constants.js +51 -0
  29. package/dist/exec/execRuntime.d.ts +55 -0
  30. package/dist/exec/execRuntime.js +217 -0
  31. package/dist/exec/workerSource.d.ts +28 -0
  32. package/dist/exec/workerSource.js +299 -0
  33. package/dist/host/index.d.ts +23 -0
  34. package/dist/host/index.js +23 -0
  35. package/dist/index.d.ts +5 -0
  36. package/dist/index.js +6 -0
  37. package/dist/managers/MemoryRuleManager.d.ts +6 -0
  38. package/dist/managers/MemoryRuleManager.js +12 -0
  39. package/dist/managers/aiManager.d.ts +35 -1
  40. package/dist/managers/aiManager.js +190 -21
  41. package/dist/managers/backgroundTaskManager.js +14 -0
  42. package/dist/managers/hookManager.d.ts +13 -0
  43. package/dist/managers/hookManager.js +31 -4
  44. package/dist/managers/liveConfigManager.d.ts +33 -0
  45. package/dist/managers/liveConfigManager.js +103 -8
  46. package/dist/managers/lspManager.d.ts +9 -0
  47. package/dist/managers/lspManager.js +47 -18
  48. package/dist/managers/mcpManager.d.ts +45 -10
  49. package/dist/managers/mcpManager.js +103 -1
  50. package/dist/managers/messageManager.d.ts +48 -5
  51. package/dist/managers/messageManager.js +107 -21
  52. package/dist/managers/permissionManager.d.ts +40 -0
  53. package/dist/managers/permissionManager.js +63 -8
  54. package/dist/managers/pluginManager.d.ts +46 -2
  55. package/dist/managers/pluginManager.js +117 -11
  56. package/dist/managers/pluginScopeManager.d.ts +15 -2
  57. package/dist/managers/pluginScopeManager.js +20 -1
  58. package/dist/managers/skillManager.d.ts +19 -0
  59. package/dist/managers/skillManager.js +44 -0
  60. package/dist/managers/slashCommandManager.d.ts +10 -0
  61. package/dist/managers/slashCommandManager.js +35 -3
  62. package/dist/managers/subagentManager.d.ts +8 -0
  63. package/dist/managers/subagentManager.js +20 -0
  64. package/dist/managers/toolManager.d.ts +29 -3
  65. package/dist/managers/toolManager.js +87 -13
  66. package/dist/prompts/autoMemory.d.ts +9 -0
  67. package/dist/prompts/autoMemory.js +30 -31
  68. package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
  69. package/dist/prompts/autoMemoryExtraction.js +8 -111
  70. package/dist/prompts/memoryTypes.d.ts +63 -0
  71. package/dist/prompts/memoryTypes.js +191 -0
  72. package/dist/services/GitService.d.ts +7 -0
  73. package/dist/services/GitService.js +23 -0
  74. package/dist/services/MarketplaceService.d.ts +101 -17
  75. package/dist/services/MarketplaceService.js +318 -119
  76. package/dist/services/artifactContent.d.ts +84 -0
  77. package/dist/services/artifactContent.js +204 -0
  78. package/dist/services/artifactSession.d.ts +6 -0
  79. package/dist/services/artifactSession.js +17 -0
  80. package/dist/services/autoMemoryService.js +5 -13
  81. package/dist/services/configurationService.d.ts +60 -9
  82. package/dist/services/configurationService.js +129 -54
  83. package/dist/services/contentSummarizer.d.ts +15 -0
  84. package/dist/services/contentSummarizer.js +45 -0
  85. package/dist/services/execAvailability.d.ts +9 -0
  86. package/dist/services/execAvailability.js +32 -0
  87. package/dist/services/fileWatcher.js +61 -6
  88. package/dist/services/initializationService.js +19 -15
  89. package/dist/services/interactionService.d.ts +9 -1
  90. package/dist/services/interactionService.js +28 -8
  91. package/dist/services/jsonlHandler.d.ts +84 -0
  92. package/dist/services/jsonlHandler.js +209 -14
  93. package/dist/services/memory.d.ts +3 -1
  94. package/dist/services/memory.js +13 -9
  95. package/dist/services/officialMarketplaceMirror.js +3 -2
  96. package/dist/services/pluginLoader.d.ts +12 -4
  97. package/dist/services/pluginLoader.js +38 -7
  98. package/dist/services/remoteSettingsService.js +16 -2
  99. package/dist/services/session.d.ts +74 -0
  100. package/dist/services/session.js +144 -3
  101. package/dist/services/sessionEntries.d.ts +2 -0
  102. package/dist/services/sessionEntries.js +20 -0
  103. package/dist/stdio/index.d.ts +3 -1
  104. package/dist/stdio/index.js +3 -1
  105. package/dist/stdio/notificationRouter.js +1 -0
  106. package/dist/stdio/stdioAgent.d.ts +14 -7
  107. package/dist/stdio/stdioAgent.js +19 -0
  108. package/dist/tools/artifactTool.js +406 -273
  109. package/dist/tools/bashTool.js +8 -6
  110. package/dist/tools/editTool.js +6 -3
  111. package/dist/tools/execTool.d.ts +2 -0
  112. package/dist/tools/execTool.js +165 -0
  113. package/dist/tools/grepTool.js +7 -1
  114. package/dist/tools/readTool.js +30 -2
  115. package/dist/tools/types.d.ts +34 -8
  116. package/dist/tools/webFetchTool.js +15 -166
  117. package/dist/tools/workflowTool.js +40 -8
  118. package/dist/tools/writeTool.js +6 -3
  119. package/dist/types/agent.d.ts +20 -5
  120. package/dist/types/configuration.d.ts +39 -1
  121. package/dist/types/marketplace.d.ts +40 -2
  122. package/dist/types/mcp.d.ts +39 -0
  123. package/dist/types/permissions.d.ts +22 -0
  124. package/dist/types/permissions.js +17 -0
  125. package/dist/types/plugins.d.ts +26 -2
  126. package/dist/types/skills.d.ts +15 -0
  127. package/dist/utils/constants.d.ts +10 -0
  128. package/dist/utils/constants.js +10 -0
  129. package/dist/utils/containerSetup.js +43 -0
  130. package/dist/utils/convertMessagesForAPI.d.ts +7 -1
  131. package/dist/utils/convertMessagesForAPI.js +64 -14
  132. package/dist/utils/fileChangeReminder.d.ts +20 -0
  133. package/dist/utils/fileChangeReminder.js +153 -0
  134. package/dist/utils/fileSearch.js +4 -3
  135. package/dist/utils/fileUtils.d.ts +33 -0
  136. package/dist/utils/fileUtils.js +81 -0
  137. package/dist/utils/frontmatterYaml.d.ts +33 -0
  138. package/dist/utils/frontmatterYaml.js +192 -0
  139. package/dist/utils/imageBudget.d.ts +85 -0
  140. package/dist/utils/imageBudget.js +109 -0
  141. package/dist/utils/imageDimensions.d.ts +83 -0
  142. package/dist/utils/imageDimensions.js +232 -0
  143. package/dist/utils/imageProcessor.d.ts +66 -0
  144. package/dist/utils/imageProcessor.js +84 -0
  145. package/dist/utils/imageRewrite.d.ts +29 -0
  146. package/dist/utils/imageRewrite.js +251 -0
  147. package/dist/utils/markdownParser.d.ts +5 -1
  148. package/dist/utils/markdownParser.js +9 -51
  149. package/dist/utils/mcpInstructions.d.ts +61 -0
  150. package/dist/utils/mcpInstructions.js +126 -0
  151. package/dist/utils/mcpUtils.d.ts +7 -0
  152. package/dist/utils/mcpUtils.js +11 -2
  153. package/dist/utils/memoryAge.d.ts +32 -0
  154. package/dist/utils/memoryAge.js +47 -0
  155. package/dist/utils/memoryEntrypoint.d.ts +20 -0
  156. package/dist/utils/memoryEntrypoint.js +49 -0
  157. package/dist/utils/memoryIndex.d.ts +30 -0
  158. package/dist/utils/memoryIndex.js +76 -0
  159. package/dist/utils/messageOperations.d.ts +6 -2
  160. package/dist/utils/messageOperations.js +40 -29
  161. package/dist/utils/nestedMemory.d.ts +22 -0
  162. package/dist/utils/nestedMemory.js +61 -0
  163. package/dist/utils/npmTarball.d.ts +19 -0
  164. package/dist/utils/npmTarball.js +92 -0
  165. package/dist/utils/pluginSource.d.ts +37 -0
  166. package/dist/utils/pluginSource.js +73 -0
  167. package/dist/utils/ripgrep.d.ts +18 -4
  168. package/dist/utils/ripgrep.js +56 -4
  169. package/dist/utils/runtimeDeps.d.ts +35 -0
  170. package/dist/utils/runtimeDeps.js +426 -0
  171. package/dist/utils/skillParser.js +22 -52
  172. package/dist/utils/subagentParser.js +39 -43
  173. package/dist/utils/userSettings.d.ts +90 -0
  174. package/dist/utils/userSettings.js +291 -0
  175. package/package.json +10 -7
package/dist/agent.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type QueuedMessage } from "./managers/messageQueue.js";
2
2
  import { SlashCommand, CustomSlashCommand, AgentOptions } from "./types/index.js";
3
- import type { Message, McpServerStatus, GatewayConfig, ModelConfig, Usage, PermissionMode, ForegroundTask, SkillMetadata } from "./types/index.js";
3
+ import type { Message, McpServerStatus, GatewayConfig, ModelConfig, Usage, PermissionMode, ForegroundTask, SkillMetadata, PluginReloadResult } from "./types/index.js";
4
4
  import type { HookEvent, HookEventConfig } from "./types/hooks.js";
5
5
  import type { WorktreeSession } from "./utils/worktreeSession.js";
6
6
  export declare class Agent {
@@ -43,12 +43,39 @@ export declare class Agent {
43
43
  getGatewayConfig(): GatewayConfig;
44
44
  getModelConfig(): ModelConfig;
45
45
  getMaxInputTokens(): number;
46
- getLanguage(): string | undefined;
46
+ getLanguage(): string;
47
+ /**
48
+ * Re-read the merged configuration (user/project/local settings.json + remote
49
+ * managed settings) and apply it to this session **without rebuilding it**:
50
+ * hooks / permissions / env follow immediately, the settings.json-derived
51
+ * values the running turn reads are re-pinned at the next turn start.
52
+ *
53
+ * THE save path calls this explicitly after writing the user-level
54
+ * settings.json: that file is commonly *created* by that very write, so the
55
+ * file watcher — armed at session start on a path that did not exist yet —
56
+ * must not be the only trigger (docs/specs/core/agent-config.md
57
+ * 「设置实时重载」). Idempotent with the watcher (a concurrent reload is
58
+ * de-duplicated).
59
+ */
60
+ reloadConfiguration(): Promise<void>;
61
+ /**
62
+ * Re-read every plugin from disk and swap its contribution into this running
63
+ * session in place — the `/reload-plugins` command. Commands, skills,
64
+ * agents, hooks, MCP servers and LSP servers all follow; the session itself
65
+ * (sessionId, transcript, in-flight work) is untouched, so this is not a
66
+ * rebuild. Every live session in the process must call this to see the new
67
+ * state; sessions created afterwards pick it up from disk on their own.
68
+ *
69
+ * Busts the prompt cache for the tool-schema prefix (the skill and agent
70
+ * lists are inlined) — deliberately accepted, see
71
+ * docs/specs/ecosystem/plugin.md「插件变更的就地重载」.
72
+ */
73
+ reloadPlugins(): Promise<PluginReloadResult>;
47
74
  /**
48
75
  * Set the active model for the agent session
49
76
  * @param model - The ID of the model to use
50
77
  */
51
- setModel(model: string): void;
78
+ setModel(model: string): Promise<void>;
52
79
  /**
53
80
  * Get all configured models from settings.json and defaults
54
81
  * @returns Array of model IDs
@@ -246,8 +273,35 @@ export declare class Agent {
246
273
  /**
247
274
  * Restore a session by ID, switching to the target session without destroying the Agent instance
248
275
  * @param sessionId - The ID of the session to restore
276
+ * @param options.workdir - The directory the target session lives in. Pass it
277
+ * when the target was recorded under a different project directory (e.g.
278
+ * another worktree of the same repo); the session transcript, project rules
279
+ * and memory are then re-pointed at it.
280
+ */
281
+ restoreSession(sessionId: string, options?: {
282
+ workdir?: string;
283
+ }): Promise<void>;
284
+ /**
285
+ * Set this session's user-visible title.
286
+ *
287
+ * The title is appended to the session's own JSONL file as a `custom-title`
288
+ * entry (see `setSessionCustomTitle`), so every host that reads session files
289
+ * — this process, a later run, the IDE plugins, the desktop app — shows the
290
+ * same label. A blank title is a no-op.
291
+ */
292
+ renameSession(title: string): Promise<void>;
293
+ /** Shared wiring for the InteractionService entry points. */
294
+ private interactionContext;
295
+ /**
296
+ * Re-point every directory-derived piece of session state at `workdir`
297
+ * (used when restoring a session recorded in another directory).
298
+ *
299
+ * The memory cache needs no explicit clearing: moving to a session always
300
+ * changes the session id, and the resulting `onSessionIdChange` already
301
+ * clears it. Project rules do — they are discovered once at startup and
302
+ * resolved relative to the workdir.
249
303
  */
250
- restoreSession(sessionId: string): Promise<void>;
304
+ private switchSessionWorkdir;
251
305
  abortAIMessage(): void;
252
306
  /** Execute a bash-mode command (`!command`) */
253
307
  bang(command: string): Promise<void>;
package/dist/agent.js CHANGED
@@ -3,8 +3,9 @@ import { configValidator } from "./utils/configValidator.js";
3
3
  import path from "node:path";
4
4
  import { parseTaskNotificationXml } from "./utils/notificationXml.js";
5
5
  import { InitializationService } from "./services/initializationService.js";
6
- import { InteractionService } from "./services/interactionService.js";
6
+ import { InteractionService, } from "./services/interactionService.js";
7
7
  import { ConfigurationService } from "./services/configurationService.js";
8
+ import { setSessionCustomTitle } from "./services/session.js";
8
9
  import { setupAgentContainer } from "./utils/containerSetup.js";
9
10
  import { initializeTelemetry, shutdownTelemetry, } from "./telemetry/instrumentation.js";
10
11
  import { logOTelEvent } from "./telemetry/events.js";
@@ -26,12 +27,47 @@ export class Agent {
26
27
  getLanguage() {
27
28
  return this.configurationService.resolveLanguage();
28
29
  }
30
+ /**
31
+ * Re-read the merged configuration (user/project/local settings.json + remote
32
+ * managed settings) and apply it to this session **without rebuilding it**:
33
+ * hooks / permissions / env follow immediately, the settings.json-derived
34
+ * values the running turn reads are re-pinned at the next turn start.
35
+ *
36
+ * THE save path calls this explicitly after writing the user-level
37
+ * settings.json: that file is commonly *created* by that very write, so the
38
+ * file watcher — armed at session start on a path that did not exist yet —
39
+ * must not be the only trigger (docs/specs/core/agent-config.md
40
+ * 「设置实时重载」). Idempotent with the watcher (a concurrent reload is
41
+ * de-duplicated).
42
+ */
43
+ async reloadConfiguration() {
44
+ await this.liveConfigManager.reload();
45
+ }
46
+ /**
47
+ * Re-read every plugin from disk and swap its contribution into this running
48
+ * session in place — the `/reload-plugins` command. Commands, skills,
49
+ * agents, hooks, MCP servers and LSP servers all follow; the session itself
50
+ * (sessionId, transcript, in-flight work) is untouched, so this is not a
51
+ * rebuild. Every live session in the process must call this to see the new
52
+ * state; sessions created afterwards pick it up from disk on their own.
53
+ *
54
+ * Busts the prompt cache for the tool-schema prefix (the skill and agent
55
+ * lists are inlined) — deliberately accepted, see
56
+ * docs/specs/ecosystem/plugin.md「插件变更的就地重载」.
57
+ */
58
+ async reloadPlugins() {
59
+ const result = await this.pluginManager.reloadAllPlugins();
60
+ // Plugin skills arrive after SkillManager.initialize(), so their slash
61
+ // commands have to be re-derived explicitly (same as at startup).
62
+ this.slashCommandManager.registerSkillCommands(this.skillManager.getAvailableSkills());
63
+ return result;
64
+ }
29
65
  /**
30
66
  * Set the active model for the agent session
31
67
  * @param model - The ID of the model to use
32
68
  */
33
- setModel(model) {
34
- this.configurationService.setModel(model);
69
+ async setModel(model) {
70
+ await this.configurationService.setModel(model);
35
71
  this.options.callbacks?.onModelChange?.(model);
36
72
  }
37
73
  /**
@@ -503,6 +539,11 @@ export class Agent {
503
539
  taskManager: this.taskManager,
504
540
  resolveAndValidateConfig: () => this.resolveAndValidateConfig(),
505
541
  }, options);
542
+ // Freeze the session-level bypass authorization now that settings.json (and
543
+ // thus `permissions.defaultMode`) has been loaded. Later mode switches and
544
+ // config reloads must not change it — it records what the user authorized
545
+ // when the session started.
546
+ this.permissionManager.captureBypassAuthorization();
506
547
  // Initialize OpenTelemetry (awaited to ensure ALS context is ready before
507
548
  // startInteractionSpan is called, preventing trace context fragmentation)
508
549
  const telemetryConfig = this.configurationService.resolveTelemetryConfig();
@@ -530,9 +571,33 @@ export class Agent {
530
571
  /**
531
572
  * Restore a session by ID, switching to the target session without destroying the Agent instance
532
573
  * @param sessionId - The ID of the session to restore
574
+ * @param options.workdir - The directory the target session lives in. Pass it
575
+ * when the target was recorded under a different project directory (e.g.
576
+ * another worktree of the same repo); the session transcript, project rules
577
+ * and memory are then re-pointed at it.
578
+ */
579
+ async restoreSession(sessionId, options) {
580
+ await InteractionService.restoreSession(this.interactionContext(), sessionId, options);
581
+ }
582
+ /**
583
+ * Set this session's user-visible title.
584
+ *
585
+ * The title is appended to the session's own JSONL file as a `custom-title`
586
+ * entry (see `setSessionCustomTitle`), so every host that reads session files
587
+ * — this process, a later run, the IDE plugins, the desktop app — shows the
588
+ * same label. A blank title is a no-op.
533
589
  */
534
- async restoreSession(sessionId) {
535
- await InteractionService.restoreSession({
590
+ async renameSession(title) {
591
+ const applied = await setSessionCustomTitle(this.sessionId, this.workdir, title);
592
+ // Keep the in-memory value in step: it is what gets written back at the
593
+ // next flush point if the entry has since slid out of the tail window.
594
+ if (applied) {
595
+ this.messageManager.setCustomTitle(applied);
596
+ }
597
+ }
598
+ /** Shared wiring for the InteractionService entry points. */
599
+ interactionContext() {
600
+ return {
536
601
  messageManager: this.messageManager,
537
602
  slashCommandManager: this.slashCommandManager,
538
603
  hookManager: this.hookManager,
@@ -544,7 +609,22 @@ export class Agent {
544
609
  taskManager: this.taskManager,
545
610
  options: this.options,
546
611
  abortMessage: () => this.abortMessage(),
547
- }, sessionId);
612
+ switchWorkdir: (workdir) => this.switchSessionWorkdir(workdir),
613
+ };
614
+ }
615
+ /**
616
+ * Re-point every directory-derived piece of session state at `workdir`
617
+ * (used when restoring a session recorded in another directory).
618
+ *
619
+ * The memory cache needs no explicit clearing: moving to a session always
620
+ * changes the session id, and the resulting `onSessionIdChange` already
621
+ * clears it. Project rules do — they are discovered once at startup and
622
+ * resolved relative to the workdir.
623
+ */
624
+ async switchSessionWorkdir(workdir) {
625
+ this.setWorkdir(workdir);
626
+ this.messageManager.setWorkdir(workdir);
627
+ await this.memoryRuleManager.setWorkdir(workdir);
548
628
  }
549
629
  abortAIMessage() {
550
630
  this.aiManager.abortAIMessage();
@@ -711,6 +791,10 @@ export class Agent {
711
791
  this.logger?.warn(`SessionEnd hooks failed: ${error.message}`);
712
792
  }
713
793
  await this.messageManager.saveSession();
794
+ // Flush point: the transcript is about to be left behind for other
795
+ // processes to list, so re-append the metadata entries at EOF. Must come
796
+ // after saveSession (which returns early when nothing is unsaved).
797
+ await this.messageManager.reAppendSessionMetadata();
714
798
  this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
715
799
  this.abortBashCommand();
716
800
  this.abortSlashCommand();
@@ -832,19 +916,7 @@ export class Agent {
832
916
  }
833
917
  // Immediate slash command: fall through to InteractionService
834
918
  }
835
- await InteractionService.sendMessage({
836
- messageManager: this.messageManager,
837
- slashCommandManager: this.slashCommandManager,
838
- hookManager: this.hookManager,
839
- workdir: this.workdir,
840
- configurationService: this.configurationService,
841
- logger: this.logger,
842
- aiManager: this.aiManager,
843
- subagentManager: this.subagentManager,
844
- taskManager: this.taskManager,
845
- options: this.options,
846
- abortMessage: () => this.abortMessage(),
847
- }, content, images);
919
+ await InteractionService.sendMessage(this.interactionContext(), content, images);
848
920
  }
849
921
  // ========== Additional Directories ==========
850
922
  /**
@@ -5,6 +5,7 @@ import { initSkill } from "./skills/init.js";
5
5
  import { loopSkill } from "./skills/loop.js";
6
6
  import { simplifySkill } from "./skills/simplify.js";
7
7
  import { settingsSkills } from "./skills/settings.js";
8
+ import { waveDaemonSkill } from "./skills/wave-daemon.js";
8
9
  import { subagents } from "./subagents.js";
9
10
  import { sddPlugin } from "./plugins.js";
10
11
  export const BUILTIN_CONTENT = {
@@ -15,6 +16,7 @@ export const BUILTIN_CONTENT = {
15
16
  ...loopSkill,
16
17
  ...simplifySkill,
17
18
  ...settingsSkills,
19
+ ...waveDaemonSkill,
18
20
  ...subagents,
19
21
  ...sddPlugin,
20
22
  };
@@ -924,18 +924,7 @@ Wave clones the marketplace repo, reads the manifest, and copies the plugin to i
924
924
 
925
925
  ### Updating Plugins
926
926
 
927
- Marketplaces with \`autoUpdate: true\` are checked for updates on startup:
928
-
929
- \`\`\`json
930
- {
931
- "marketplaces": {
932
- "my-plugins": {
933
- "source": { "source": "github", "repo": "user/my-plugins" },
934
- "autoUpdate": true
935
- }
936
- }
937
- }
938
- \`\`\`
927
+ Every registered marketplace is refreshed when you open a plugin marketplace surface — the settings page's plugin marketplace view, or the CLI plugin manager (\`/plugin\`). Each checkout is pulled (or cloned if missing). Refreshing a marketplace does **not** upgrade installed plugins, and there is no per-marketplace switch for it. Plugin upgrades only happen through an explicit action: updating a single plugin, or updating a whole marketplace (\`wave plugin marketplace update [name]\`, or the batch entry in either UI) to upgrade all of its plugins in batch.
939
928
 
940
929
  ### Marketplace Scopes
941
930
 
@@ -0,0 +1 @@
1
+ export declare const waveDaemonSkill: Record<string, string>;
@@ -0,0 +1,194 @@
1
+ import { BASH_TOOL_NAME, READ_TOOL_NAME } from "../../constants/tools.js";
2
+ export const waveDaemonSkill = {
3
+ "skills/wave-daemon/SKILL.md": `---
4
+ name: wave-daemon
5
+ description: Delegate a development or research task to a background wave daemon session — session creation in an isolated worktree, dispatching messages, aborting and re-scoping, monitoring progress, approving permissions, answering AskUserQuestion, and tearing sessions down with destroy.
6
+ allowed-tools: ${BASH_TOOL_NAME}(wave daemon *), ${BASH_TOOL_NAME}(git *), ${READ_TOOL_NAME}
7
+ ---
8
+
9
+ # Wave daemon delegation
10
+
11
+ Delegate development work (code changes, bug fixes, features) and research work (reading code and producing conclusions — which often turns into code changes) to a **wave daemon session** instead of doing the long task inline. A daemon session runs in the background with no UI window, keeps generating while no client is attached, and survives client disconnects.
12
+
13
+ The authoritative command reference is the Wave documentation, CLI → *Daemon client commands* (\`docs/cli.md\` in the Wave repo). This skill is the playbook: what to run, in what order, and the traps to avoid.
14
+
15
+ ## 0. Mental model
16
+
17
+ - \`wave --daemon <socket>\` starts the daemon (server side). \`wave daemon <subcommand>\` is the client that talks to it. The two never interfere.
18
+ - Every client subcommand connects to the fixed socket \`~/.wave/daemon.sock\` (\`$HOME/.wave/daemon.sock\`); there is no \`--socket\` override, so you are always talking to this machine's daemon for the current user.
19
+ - The daemon is **resident**: once started it does not exit when idle. It goes away only when stopped (\`wave daemon stop\`), killed, restarted after a CLI upgrade, or the machine restarts. It is not supervised by pm2/systemd, so it does not come back by itself.
20
+ - Every subcommand except \`stop\` starts a daemon on demand when the socket is absent, then retries. "No daemon is running" is therefore never a problem to work around — just run the command.
21
+ - Sessions are persisted as transcripts under \`~/.wave/projects/<project>/<sessionId>.jsonl\`, so a session can be re-hosted from disk after the daemon restarts.
22
+ - All subcommands are non-interactive: results on stdout, diagnostics on stderr.
23
+
24
+ ## 1. Daemon lifecycle
25
+
26
+ \`\`\`bash
27
+ wave daemon stop # graceful: every hosted session is destroyed (each saves its transcript), the socket is removed, the process exits; idempotent ("Daemon is not running" when none is up)
28
+ wave daemon restart # stop the old daemon (if any), then start a fresh one from the current CLI
29
+ \`\`\`
30
+
31
+ **After upgrading the CLI, restart the daemon** — it runs the code it was started with and will not pick up a new build on its own. This is the main reason \`restart\` exists.
32
+
33
+ One-off edge: \`stop\` / \`restart\` cannot stop a daemon that predates the \`shutdown\` RPC (i.e. one still running an older build). The wait times out — \`daemon did not exit within 10000ms\` — because the old process ignores the request and the socket stays up. Kill that process yourself, then run any client subcommand (e.g. \`wave daemon list\`) to start a daemon from the current CLI; \`restart\` works normally from then on.
34
+
35
+ ## 2. Creating a session
36
+
37
+ \`\`\`bash
38
+ wave daemon create --worktree [name] --workdir <dir> --permission-mode bypassPermissions
39
+ \`\`\`
40
+
41
+ - Prints the new sessionId on the first line (scripts read it from there), plus a second line with the worktree path and branch when \`--worktree\` was used.
42
+ - \`--worktree\` creates the session in a fresh git worktree, so it never touches your main checkout. The name is optional — one is generated when omitted.
43
+ - \`--permission-mode\` defaults to \`bypassPermissions\` for daemon-created sessions, so a session created this way raises no approval prompts at all. Passing the flag explicitly is redundant but fine and self-documenting. Valid modes: \`default\`, \`bypassPermissions\`, \`acceptEdits\`, \`plan\`, \`dontAsk\`.
44
+ - \`--workdir\` defaults to the current directory.
45
+
46
+ ## 3. Dispatching work and following up
47
+
48
+ \`\`\`bash
49
+ wave daemon send <sessionId> <message> # async dispatch (the default)
50
+ wave daemon send <sessionId> <message> --wait 600 # wait up to 600s and print the reply
51
+ \`\`\`
52
+
53
+ - The default is fire-and-forget: the command exits 0 (printing \`Sent message to session: <sessionId>\`) as soon as the message is **delivered**. On an idle session it lands in history and the turn starts; on a busy session it is queued and takes effect when the current turn finishes.
54
+ - \`--wait <seconds>\` blocks until the reply to *that* message arrives and prints only the assistant's final reply text. On timeout it exits non-zero; if the session is stuck on a pending approval it says so and points at \`respond\`.
55
+ - A \`send --wait\` failure of \`Message aborted before producing a reply\` means the turn was interrupted mid-generation — the message was delivered, it just never produced text.
56
+
57
+ **An interrupted send is not a lost message.** If the shell or tool running \`wave daemon send\` is interrupted or times out, the message may already have been delivered and be executing in the daemon. Verify with \`wave daemon status <sessionId>\` before resending — a duplicate send duplicates the work.
58
+
59
+ To block on the work instead of checking it, follow the send with \`wave daemon wait <sessionId> --from-busy\` — the flag covers the moment right after an async send where the session still reports idle (§5).
60
+
61
+ ## 4. Changing your mind: abort first
62
+
63
+ A \`send\` to a *generating* session is queued, not applied immediately — the current turn runs to completion first. To correct a task or change its scope mid-flight:
64
+
65
+ \`\`\`bash
66
+ wave daemon abort <sessionId> # interrupt in-flight generation (subagents, bash commands and queued messages included)
67
+ wave daemon send <sessionId> <corrected task>
68
+ \`\`\`
69
+
70
+ \`abort\` is idempotent and a no-op on an idle session, so it is safe to run without checking first. It does not clear completed history, and it does **not** touch the session's permission mode — the session stays live in the daemon's memory.
71
+
72
+ ## 5. Monitoring
73
+
74
+ \`\`\`bash
75
+ wave daemon list # sessions currently live in the daemon's in-memory registry
76
+ wave daemon status <sessionId> # one session: status, pending approvals, and the last message
77
+ wave daemon wait <sessionId> # block until it settles, then print that same snapshot
78
+ wave daemon status <id> --lines 0 # status line only — no message text (single-shot snapshot)
79
+ wave daemon status <id> --lines 5 # widen the context window when the last message is not enough
80
+ \`\`\`
81
+
82
+ \`status\` reports one of three states — \`idle\`, \`generating\`, or \`waiting for approval\` (listed with the pending request ids). It is plain text; there is no \`--json\`.
83
+
84
+ \`--lines N\` prints the last N messages. **The default is 1** — the last message alone — because message text is never truncated, so a single long report is already tens of thousands of characters; the default has to stay bounded. N counts messages, not output lines.
85
+
86
+ - \`--lines 0\` prints no message text at all — just the header and the \`Status:\` line. Use it when a snapshot only needs the status (cheaper than pulling a report you will not read).
87
+ - text is whitespace-collapsed, and tool-only messages print nothing (they still count toward N);
88
+ - it is the last N **messages**, so behind a long tail of intermediate narration ("still investigating…") the final report can fall outside the window.
89
+
90
+ So the final report is what the default \`status <id>\` already gives you; raise \`--lines\` only when the last message is not the report you want.
91
+
92
+ **To wait for the report, use \`wave daemon wait <sessionId>\`** — do not build a polling loop. It attaches, blocks until your session settles, then prints exactly what \`status <id> --lines N\` prints, so \`msg=$(wave daemon wait <id>)\` captures the report itself. Your session's own \`loadingChange\` push is only the **wake-up**: the idle verdict is read from the daemon registry when it wakes, and the daemon broadcasts every session's notifications to every connection, so a concurrent session's push is ignored (it can neither end the wait nor count as the busy phase). Its exit code is the contract:
93
+
94
+ | exit | meaning |
95
+ | ---- | ------- |
96
+ | \`0\` | the session went idle — the snapshot on stdout is the final report |
97
+ | \`3\` | the session is hanging on a permission approval — the snapshot lists the pending request ids; answer them with \`respond\` (§6) and \`wait\` again |
98
+ | \`1\` | error — daemon unreachable, unknown sessionId, the session was destroyed while you waited, or \`--timeout\` elapsed |
99
+
100
+ - A session that is already idle when you call it returns \`0\` immediately (it never hangs).
101
+ - **\`--from-busy\` for the send-then-wait race.** An async \`send\` returns as soon as the message is *delivered*, so for a moment the session still reports idle and a plain \`wait\` would return before the turn even started. \`--from-busy\` makes the wait first observe a busy phase **of that session** (its own push, or the registry reporting it as generating), then idle — a concurrent session being busy does not count.
102
+ - \`--timeout <seconds>\` bounds the wait (default: wait forever); on expiry it exits \`1\`.
103
+ - \`--lines N\` (default 1) and \`--lines 0\` behave exactly as in \`status\`; progress lines go to stderr, stdout carries only the snapshot.
104
+ - **Waiting on a session someone else destroys.** If another client runs \`wave daemon destroy <id>\` while you are blocked in \`wait\` (the daemon itself staying up), the wait does not hang: it notices the session left the registry and exits \`1\` with \`Session <id> no longer exists (destroyed while waiting)\` — a gone session is never reported as finished. The daemon drops the session from its registry *before* running the teardown, so this holds for the whole destroy window (the abort that clears the loading flag cannot be mistaken for a finished turn).
105
+ - **A re-keyed or in-place-reconfigured session is not a destroyed one.** A live session can change its id (a cleared chat mints a new one): the daemon announces it and the wait follows the *same* session on the new id instead of exiting \`1\` (\`send --wait\` follows it too, so the reply is not missed). A config reload rebuilds the session's agent in place: while that runs the session stays listed, is reported busy (never "listed + idle"), reads keep answering, and writes are refused with a retryable error — so the wait keeps waiting; only a **failed** rebuild drops the session, and then \`1\` with the same "no longer exists" error is correct.
106
+
107
+ **Unattended monitoring needs no wrapper script.** Those three exits cover every way a session can end: \`0\` = finished (stdout is the report), \`3\` = blocked on an approval (go \`respond\`, then \`wait\` again), \`1\` = error, including the session being destroyed out from under you. So \`msg=$(wave daemon wait <id>)\` plus a branch on the exit code replaces the hand-rolled poller completely — there is no fourth case left to poll for.
108
+
109
+ If the CLI on that host predates \`wave daemon wait\`, fall back to a **background poll** — run it in the background rather than blocking on \`send --wait\`:
110
+
111
+ \`\`\`bash
112
+ while true; do
113
+ out=$(wave daemon status "$SESSION_ID" --lines 0)
114
+ echo "$out"
115
+ case "$out" in
116
+ *"Status: idle"*|*"waiting for approval"*) break ;;
117
+ esac
118
+ sleep 30
119
+ done
120
+ \`\`\`
121
+
122
+ That loop is a pattern to re-create per session with whatever background-execution mechanism your host offers (on Windows, PowerShell's \`Start-Sleep\` in place of \`sleep\`), and one poller per session so they do not interfere. Prefer \`wait\`: one process, no poll latency, and an exit code that tells idle (0) apart from waiting-for-approval (3) and from error (1, a destroyed session included). \`waiting for approval\` is an action signal (go answer it, §6); \`idle\` means the turn settled and is worth a look.
123
+
124
+ Do not hand-parse the transcript jsonl (\`~/.wave/projects/<project>/<sessionId>.jsonl\`) to recover a report — \`status <id>\` / \`wait <id>\` are the supported paths. If you ever do read the raw file: each line is one message (\`{"timestamp":…,"role":…,"blocks":[…]}\`) and text lives in \`blocks[].content\` on the \`{"type":"text"}\` block. There is no \`blocks[].text\` field, so a lookup by \`text\` silently returns nothing and looks like "the session never reported".
125
+
126
+ An \`idle\` reading can also be a transient pause between turns (waiting on a verification run, a CI job, or a pending approval). Re-check \`status\` before concluding the task is finished, and if the session goes back to \`generating\`, block on it again (or \`wait --from-busy\` if the busy phase has not started yet).
127
+
128
+ ## 6. Permission approvals
129
+
130
+ \`\`\`bash
131
+ wave daemon respond <sessionId> <requestId> --allow
132
+ wave daemon respond <sessionId> <requestId> --deny --reason "why"
133
+ wave daemon respond <sessionId> <requestId> --allow --rule "Bash(ls)" # persist this allow rule for the session
134
+ wave daemon respond <sessionId> <requestId> --allow --mode bypassPermissions # switch the session's permission mode
135
+ \`\`\`
136
+
137
+ - Exactly one of \`--allow\` / \`--deny\` is required. \`respond\` validates the requestId first (\`Request not found or already handled\`) and refuses to touch another session's request.
138
+ - \`--mode\` also applies a mode switch, so after that one answer the session stops asking. Requests already queued still need their own \`respond\`.
139
+ - An \`acceptEdits\` session asks for approval on every shell command; approving them one at a time cannot keep up with generation. Switch the mode with \`--mode bypassPermissions\` instead of responding in a loop.
140
+
141
+ **The root cause of an approval flood is a restarted daemon process.** The permission mode is not recorded in the session transcript, so when a new daemon process re-hosts a session from disk (after \`stop\` / a kill, a CLI-upgrade restart, or a machine reboot), the mode is re-derived from the current configuration — \`permissions.defaultMode\` from settings, else \`default\`. A session created with \`bypassPermissions\` therefore comes back as \`default\` and starts asking for approvals.
142
+
143
+ - \`abort\` does **not** cause this: the session stays in the daemon's memory with its mode intact. If approvals suddenly flood after a long interruption, look for a daemon restart, not for \`abort\`.
144
+ - Recovery is one command: \`respond <requestId> --allow --mode bypassPermissions\`.
145
+ - The symptom can be masked: if the repository's settings set \`permissions.defaultMode: bypassPermissions\`, the re-derived mode is bypass anyway and you will never see the fallback.
146
+
147
+ ## 7. AskUserQuestion requests
148
+
149
+ \`status\` renders a pending \`AskUserQuestion\` in full: every question as \`Q<i> [header] <question>\`, with its options numbered from 0. Those option numbers are what \`--answer\` accepts.
150
+
151
+ \`\`\`bash
152
+ wave daemon respond <sessionId> <requestId> --allow --answer "0" # option 0 of the only question
153
+ wave daemon respond <sessionId> <requestId> --allow --answer "1,0" # one option number per question, in order
154
+ \`\`\`
155
+
156
+ The older form still works: a JSON object keyed by the full question text, with the chosen option label as the value — exactly what the GUI dialog submits:
157
+
158
+ \`\`\`bash
159
+ wave daemon respond <sessionId> <requestId> --allow --answer '{"<full question text>":"<option label>"}'
160
+ \`\`\`
161
+
162
+ Whether to answer at all depends on whether the user is around:
163
+
164
+ - If the user can see the session (they have the desktop app open), do not answer for them — relay the question and let them choose.
165
+ - If the user has explicitly handed the work over and is away ("I'm offline, it's on you"), answer with the recommended option (the first one, or an option the question marks as recommended), then report what you answered so they can override it.
166
+
167
+ Answering resolves the request and the session resumes generating on its own — no extra nudge is needed, though \`status\` may briefly still read \`generating\`.
168
+
169
+ ## 8. Finishing: destroy, and the worktree
170
+
171
+ \`\`\`bash
172
+ wave daemon destroy <sessionId> --remove-worktree
173
+ \`\`\`
174
+
175
+ - \`destroy\` is idempotent, and does not require the session to be live in the registry.
176
+ - \`--remove-worktree\` is **two steps**: it resolves the session's worktree from its working directory and removes it (path + branch, through the worktree-removal protocol, which also fires the WorktreeRemove hook) and then destroys the session. It refuses to remove the main working tree, so a session that was not created in a linked worktree just fails that step.
177
+ - Because it is two steps, an interrupted \`destroy --remove-worktree\` can be **half-done**: the worktree directory, its branch and uncommitted changes are already gone while the session is still alive. After such an interruption verify all three: \`wave daemon list\` / \`status\` (is the session still there?), \`git worktree list\` and the repo's worktree directory (folder + branch), and \`~/.wave/projects/\` (transcript). A session that was killed but whose transcript survives is still recoverable from the jsonl.
178
+ - Destroy is destructive and irreversible: confirm with the user before running it.
179
+
180
+ Whose session is it? Only tear down sessions you created with \`wave daemon create\`. Never destroy a session created by the desktop app — the user may still be using it — or one you do not recognize. If you cannot tell who created it, ask.
181
+
182
+ ## 9. Checklist
183
+
184
+ - Create with \`--worktree\` and \`--permission-mode bypassPermissions\` (the mode default is already bypass, so no approvals appear).
185
+ - \`send\` is async by default; after any interruption, check \`status\` before resending.
186
+ - \`abort\` before re-scoping a running session.
187
+ - Read the final report by blocking: \`wave daemon wait <id>\` (exit 0 = idle, 3 = stuck on an approval, 1 = error — session unknown or destroyed mid-wait; defaults to the last message, raise \`--lines\` if that one is not it). Use \`status <id>\` when you want a snapshot without blocking.
188
+ - To watch a session unattended, loop on \`wait\` and branch on its exit code — no shell polling script to maintain (§5).
189
+ - An approval flood means the daemon process restarted and the mode fell back — recover with \`--mode bypassPermissions\`.
190
+ - After a CLI upgrade, \`wave daemon restart\` (kill the old process first if \`restart\` times out).
191
+ - \`destroy --remove-worktree\` last, after user confirmation, only for sessions you created.
192
+ - Sessions the desktop app manages churn quickly on their own; a shifting \`wave daemon list\` is normal, not a fault.
193
+ `,
194
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Outbound image dimension budget: the per-side pixel ceiling we deliberately
3
+ * keep every image under, aligned with Claude Code's `IMAGE_MAX_WIDTH` /
4
+ * `IMAGE_MAX_HEIGHT`.
5
+ *
6
+ * Two gates enforce it and they must agree on the number:
7
+ *
8
+ * - the webview paste path (`packages/webview/src/utils/imageValidation.ts`)
9
+ * downsamples an oversized paste in the browser, before it becomes a message;
10
+ * - the SDK's outbound rewrite pass (`utils/imageRewrite.ts`, via sharp) is the
11
+ * catch-all for every other source (Read tool, file paths, hosts that cannot
12
+ * re-encode).
13
+ *
14
+ * The value lives here rather than in `utils/imageBudget.ts` because the
15
+ * webview is a browser bundle: by contract it may only take *values* from
16
+ * `wave-agent-sdk/constants`, and `utils/*` is not a public subpath. Copying
17
+ * `2000` into the webview would let the two gates drift apart silently — the
18
+ * paste would shrink to one number while the SDK judged by another.
19
+ *
20
+ * Not to be confused with the gateway's *hard* bound: `MAX_IMAGE_DIMENSION_PX`
21
+ * in `utils/imageDimensions.ts` (8192px per side) is where the upstream starts
22
+ * rejecting the whole request with `HTTP 400 ... unsupported image`. That one is
23
+ * an immovable external limit; this one is our own conservative budget and can
24
+ * be raised freely when we want more image fidelity.
25
+ */
26
+ export declare const OUTBOUND_IMAGE_MAX_DIMENSION_PX = 2000;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Outbound image dimension budget: the per-side pixel ceiling we deliberately
3
+ * keep every image under, aligned with Claude Code's `IMAGE_MAX_WIDTH` /
4
+ * `IMAGE_MAX_HEIGHT`.
5
+ *
6
+ * Two gates enforce it and they must agree on the number:
7
+ *
8
+ * - the webview paste path (`packages/webview/src/utils/imageValidation.ts`)
9
+ * downsamples an oversized paste in the browser, before it becomes a message;
10
+ * - the SDK's outbound rewrite pass (`utils/imageRewrite.ts`, via sharp) is the
11
+ * catch-all for every other source (Read tool, file paths, hosts that cannot
12
+ * re-encode).
13
+ *
14
+ * The value lives here rather than in `utils/imageBudget.ts` because the
15
+ * webview is a browser bundle: by contract it may only take *values* from
16
+ * `wave-agent-sdk/constants`, and `utils/*` is not a public subpath. Copying
17
+ * `2000` into the webview would let the two gates drift apart silently — the
18
+ * paste would shrink to one number while the SDK judged by another.
19
+ *
20
+ * Not to be confused with the gateway's *hard* bound: `MAX_IMAGE_DIMENSION_PX`
21
+ * in `utils/imageDimensions.ts` (8192px per side) is where the upstream starts
22
+ * rejecting the whole request with `HTTP 400 ... unsupported image`. That one is
23
+ * an immovable external limit; this one is our own conservative budget and can
24
+ * be raised freely when we want more image fidelity.
25
+ */
26
+ export const OUTBOUND_IMAGE_MAX_DIMENSION_PX = 2000;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Constants entry (`wave-agent-sdk/constants`).
3
+ *
4
+ * Hosts that drive the agent over JSON-RPC import shared values from here rather
5
+ * than from the SDK barrel: the barrel pulls in the whole agent runtime,
6
+ * including dependencies that do work while their module body evaluates and can
7
+ * therefore take a host down at load time. Every module re-exported below must
8
+ * stay dependency-free so this entry remains cheap to bundle.
9
+ */
10
+ export * from "./images.js";
11
+ export * from "./memory.js";
12
+ export * from "./messages.js";
13
+ export * from "./plugins.js";
14
+ export * from "./subagents.js";
15
+ export * from "./toolLimits.js";
16
+ export * from "./tools.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Constants entry (`wave-agent-sdk/constants`).
3
+ *
4
+ * Hosts that drive the agent over JSON-RPC import shared values from here rather
5
+ * than from the SDK barrel: the barrel pulls in the whole agent runtime,
6
+ * including dependencies that do work while their module body evaluates and can
7
+ * therefore take a host down at load time. Every module re-exported below must
8
+ * stay dependency-free so this entry remains cheap to bundle.
9
+ */
10
+ export * from "./images.js";
11
+ export * from "./memory.js";
12
+ export * from "./messages.js";
13
+ export * from "./plugins.js";
14
+ export * from "./subagents.js";
15
+ export * from "./toolLimits.js";
16
+ export * from "./tools.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Auto-memory taxonomy and entrypoint limits.
3
+ *
4
+ * The entrypoint (`MEMORY.md`) is an index that is always loaded into the
5
+ * conversation context, so it needs a hard size bound. A line cap alone does
6
+ * not provide one: a 200-line index of very long lines was observed at close
7
+ * to 200 KB. Both caps apply and the tighter one wins.
8
+ *
9
+ * Aligned with Claude Code's `memdir/memdir.ts` (MAX_ENTRYPOINT_LINES /
10
+ * MAX_ENTRYPOINT_BYTES); the second bound is counted in characters, not bytes
11
+ * — Claude Code's constant carries the BYTES name but its value is derived
12
+ * from "~125 chars/line at 200 lines" and is compared against `String.length`.
13
+ */
14
+ export declare const MEMORY_ENTRYPOINT_NAME = "MEMORY.md";
15
+ export declare const MAX_MEMORY_ENTRYPOINT_LINES = 200;
16
+ export declare const MAX_MEMORY_ENTRYPOINT_CHARS = 25000;
17
+ /** Recommended per-line length for `MEMORY.md` index entries. */
18
+ export declare const MEMORY_INDEX_LINE_GUIDANCE_CHARS = 150;
19
+ export declare const MEMORY_TYPES: readonly ["user", "feedback", "project", "reference"];
20
+ export type MemoryType = (typeof MEMORY_TYPES)[number];
21
+ /**
22
+ * Parse a raw frontmatter value into a MemoryType. Invalid or missing values
23
+ * return undefined — files written before the taxonomy existed keep working
24
+ * and unknown types degrade gracefully.
25
+ */
26
+ export declare function parseMemoryType(raw: unknown): MemoryType | undefined;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Auto-memory taxonomy and entrypoint limits.
3
+ *
4
+ * The entrypoint (`MEMORY.md`) is an index that is always loaded into the
5
+ * conversation context, so it needs a hard size bound. A line cap alone does
6
+ * not provide one: a 200-line index of very long lines was observed at close
7
+ * to 200 KB. Both caps apply and the tighter one wins.
8
+ *
9
+ * Aligned with Claude Code's `memdir/memdir.ts` (MAX_ENTRYPOINT_LINES /
10
+ * MAX_ENTRYPOINT_BYTES); the second bound is counted in characters, not bytes
11
+ * — Claude Code's constant carries the BYTES name but its value is derived
12
+ * from "~125 chars/line at 200 lines" and is compared against `String.length`.
13
+ */
14
+ export const MEMORY_ENTRYPOINT_NAME = "MEMORY.md";
15
+ export const MAX_MEMORY_ENTRYPOINT_LINES = 200;
16
+ export const MAX_MEMORY_ENTRYPOINT_CHARS = 25000;
17
+ /** Recommended per-line length for `MEMORY.md` index entries. */
18
+ export const MEMORY_INDEX_LINE_GUIDANCE_CHARS = 150;
19
+ export const MEMORY_TYPES = [
20
+ "user",
21
+ "feedback",
22
+ "project",
23
+ "reference",
24
+ ];
25
+ /**
26
+ * Parse a raw frontmatter value into a MemoryType. Invalid or missing values
27
+ * return undefined — files written before the taxonomy existed keep working
28
+ * and unknown types degrade gracefully.
29
+ */
30
+ export function parseMemoryType(raw) {
31
+ if (typeof raw !== "string")
32
+ return undefined;
33
+ return MEMORY_TYPES.find((t) => t === raw);
34
+ }