wave-agent-sdk 0.14.0 → 0.14.2

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 (88) hide show
  1. package/builtin/skills/settings/HOOKS.md +69 -0
  2. package/builtin/skills/settings/PLUGINS.md +171 -0
  3. package/builtin/skills/settings/SKILL.md +8 -3
  4. package/dist/agent.d.ts +2 -2
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +12 -3
  7. package/dist/core/plugin.d.ts +2 -2
  8. package/dist/core/plugin.d.ts.map +1 -1
  9. package/dist/core/plugin.js +7 -7
  10. package/dist/managers/aiManager.d.ts +6 -6
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +122 -59
  13. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundTaskManager.js +28 -30
  15. package/dist/managers/hookManager.d.ts +16 -1
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +97 -8
  18. package/dist/managers/messageManager.d.ts +19 -4
  19. package/dist/managers/messageManager.d.ts.map +1 -1
  20. package/dist/managers/messageManager.js +63 -18
  21. package/dist/managers/pluginManager.d.ts.map +1 -1
  22. package/dist/managers/pluginManager.js +1 -1
  23. package/dist/prompts/index.d.ts +1 -1
  24. package/dist/prompts/index.d.ts.map +1 -1
  25. package/dist/prompts/index.js +1 -1
  26. package/dist/services/MarketplaceService.d.ts +42 -12
  27. package/dist/services/MarketplaceService.d.ts.map +1 -1
  28. package/dist/services/MarketplaceService.js +225 -105
  29. package/dist/services/aiService.d.ts +3 -3
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +7 -7
  32. package/dist/services/configurationService.d.ts +17 -1
  33. package/dist/services/configurationService.d.ts.map +1 -1
  34. package/dist/services/configurationService.js +104 -0
  35. package/dist/services/hook.d.ts.map +1 -1
  36. package/dist/services/hook.js +15 -0
  37. package/dist/services/initializationService.d.ts.map +1 -1
  38. package/dist/services/initializationService.js +24 -1
  39. package/dist/services/interactionService.js +1 -1
  40. package/dist/services/pluginLoader.d.ts.map +1 -1
  41. package/dist/services/pluginLoader.js +7 -1
  42. package/dist/services/session.d.ts +1 -1
  43. package/dist/services/session.js +7 -7
  44. package/dist/services/taskManager.d.ts +1 -1
  45. package/dist/services/taskManager.js +1 -1
  46. package/dist/types/configuration.d.ts +7 -0
  47. package/dist/types/configuration.d.ts.map +1 -1
  48. package/dist/types/core.d.ts +1 -1
  49. package/dist/types/core.d.ts.map +1 -1
  50. package/dist/types/hooks.d.ts +9 -1
  51. package/dist/types/hooks.d.ts.map +1 -1
  52. package/dist/types/hooks.js +2 -0
  53. package/dist/types/marketplace.d.ts +2 -0
  54. package/dist/types/marketplace.d.ts.map +1 -1
  55. package/dist/types/messaging.d.ts +3 -3
  56. package/dist/types/messaging.d.ts.map +1 -1
  57. package/dist/utils/convertMessagesForAPI.d.ts +1 -1
  58. package/dist/utils/convertMessagesForAPI.js +7 -7
  59. package/dist/utils/groupMessagesByApiRound.d.ts +1 -1
  60. package/dist/utils/groupMessagesByApiRound.js +6 -6
  61. package/dist/utils/messageOperations.d.ts.map +1 -1
  62. package/dist/utils/messageOperations.js +3 -3
  63. package/package.json +1 -1
  64. package/src/agent.ts +16 -3
  65. package/src/core/plugin.ts +13 -7
  66. package/src/managers/aiManager.ts +142 -63
  67. package/src/managers/backgroundTaskManager.ts +33 -42
  68. package/src/managers/hookManager.ts +125 -10
  69. package/src/managers/messageManager.ts +76 -22
  70. package/src/managers/pluginManager.ts +4 -1
  71. package/src/prompts/index.ts +1 -1
  72. package/src/services/MarketplaceService.ts +301 -111
  73. package/src/services/aiService.ts +11 -11
  74. package/src/services/configurationService.ts +131 -0
  75. package/src/services/hook.ts +17 -0
  76. package/src/services/initializationService.ts +33 -1
  77. package/src/services/interactionService.ts +1 -1
  78. package/src/services/pluginLoader.ts +7 -1
  79. package/src/services/session.ts +7 -7
  80. package/src/services/taskManager.ts +1 -1
  81. package/src/types/configuration.ts +8 -0
  82. package/src/types/core.ts +1 -1
  83. package/src/types/hooks.ts +16 -2
  84. package/src/types/marketplace.ts +2 -0
  85. package/src/types/messaging.ts +3 -3
  86. package/src/utils/convertMessagesForAPI.ts +8 -8
  87. package/src/utils/groupMessagesByApiRound.ts +6 -6
  88. package/src/utils/messageOperations.ts +3 -5
@@ -23,7 +23,7 @@ import * as fs from "fs";
23
23
  import * as path from "path";
24
24
 
25
25
  import {
26
- COMPRESS_MESSAGES_SYSTEM_PROMPT,
26
+ COMPACT_MESSAGES_SYSTEM_PROMPT,
27
27
  WEB_CONTENT_SYSTEM_PROMPT,
28
28
  BTW_SYSTEM_PROMPT,
29
29
  } from "../prompts/index.js";
@@ -751,7 +751,7 @@ async function processStreamingResponse(
751
751
  return result;
752
752
  }
753
753
 
754
- export interface CompressMessagesOptions {
754
+ export interface CompactMessagesOptions {
755
755
  // Resolved configuration
756
756
  gatewayConfig: GatewayConfig;
757
757
  modelConfig: ModelConfig;
@@ -762,7 +762,7 @@ export interface CompressMessagesOptions {
762
762
  model?: string;
763
763
  }
764
764
 
765
- export interface CompressMessagesResult {
765
+ export interface CompactMessagesResult {
766
766
  content: string;
767
767
  usage?: {
768
768
  prompt_tokens: number;
@@ -771,9 +771,9 @@ export interface CompressMessagesResult {
771
771
  };
772
772
  }
773
773
 
774
- export async function compressMessages(
775
- options: CompressMessagesOptions,
776
- ): Promise<CompressMessagesResult> {
774
+ export async function compactMessages(
775
+ options: CompactMessagesOptions,
776
+ ): Promise<CompactMessagesResult> {
777
777
  const { gatewayConfig, modelConfig, messages, abortSignal } = options;
778
778
 
779
779
  // Apply global 1 QPS rate limit
@@ -832,7 +832,7 @@ export async function compressMessages(
832
832
  messages: [
833
833
  {
834
834
  role: "system",
835
- content: COMPRESS_MESSAGES_SYSTEM_PROMPT,
835
+ content: COMPACT_MESSAGES_SYSTEM_PROMPT,
836
836
  },
837
837
  ...cleanedMessages,
838
838
  {
@@ -849,7 +849,7 @@ export async function compressMessages(
849
849
  const content = response.choices[0]?.message?.content?.trim();
850
850
  if (!content) {
851
851
  throw new Error(
852
- "Failed to compress conversation history: Empty response from AI",
852
+ "Failed to compact conversation history: Empty response from AI",
853
853
  );
854
854
  }
855
855
  const usage = response.usage
@@ -866,10 +866,10 @@ export async function compressMessages(
866
866
  };
867
867
  } catch (error) {
868
868
  if ((error as Error).name === "AbortError") {
869
- logger.info("Compression request was aborted");
870
- throw new Error("Compression request was aborted");
869
+ logger.info("Compaction request was aborted");
870
+ throw new Error("Compaction request was aborted");
871
871
  }
872
- logger.error("Failed to compress messages:", error);
872
+ logger.error("Failed to compact messages:", error);
873
873
  throw error;
874
874
  }
875
875
  }
@@ -15,6 +15,7 @@ import type {
15
15
  ConfigurationPaths,
16
16
  WaveConfiguration,
17
17
  Scope,
18
+ MarketplaceConfig,
18
19
  } from "../types/configuration.js";
19
20
  import {
20
21
  getAllConfigPaths,
@@ -796,6 +797,124 @@ export class ConfigurationService {
796
797
  await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
797
798
  }
798
799
 
800
+ /**
801
+ * Get merged marketplaces from all scopes
802
+ */
803
+ getMergedMarketplaces(workdir: string): Record<string, MarketplaceConfig> {
804
+ const mergedConfig = loadMergedWaveConfig(workdir);
805
+ return mergedConfig?.marketplaces || {};
806
+ }
807
+
808
+ /**
809
+ * Get marketplaces at a specific scope
810
+ */
811
+ getScopedMarketplaces(
812
+ workdir: string,
813
+ scope: Scope,
814
+ ): Record<string, MarketplaceConfig> {
815
+ let configPath: string;
816
+ if (scope === "user") {
817
+ configPath = getUserConfigPaths()[0];
818
+ } else if (scope === "project") {
819
+ configPath = getProjectConfigPaths(workdir)[1];
820
+ } else {
821
+ configPath = getProjectConfigPaths(workdir)[0];
822
+ }
823
+ const config = loadWaveConfigFromFile(configPath);
824
+ return config?.marketplaces || {};
825
+ }
826
+
827
+ /**
828
+ * Add a marketplace to the specified scope
829
+ */
830
+ async addMarketplaceToScope(
831
+ workdir: string,
832
+ scope: Scope,
833
+ name: string,
834
+ config: MarketplaceConfig,
835
+ ): Promise<void> {
836
+ if (scope !== "user" && !existsSync(workdir)) {
837
+ throw new Error(`Working directory does not exist: ${workdir}`);
838
+ }
839
+
840
+ let configPath: string;
841
+ if (scope === "user") {
842
+ configPath = getUserConfigPaths()[0];
843
+ } else if (scope === "project") {
844
+ configPath = getProjectConfigPaths(workdir)[1];
845
+ } else {
846
+ configPath = getProjectConfigPaths(workdir)[0];
847
+ }
848
+
849
+ const configDir = path.dirname(configPath);
850
+ if (!existsSync(configDir)) {
851
+ await fs.mkdir(configDir, { recursive: true });
852
+ }
853
+
854
+ let fileConfig: WaveConfiguration = {};
855
+ if (existsSync(configPath)) {
856
+ try {
857
+ const content = await fs.readFile(configPath, "utf-8");
858
+ fileConfig = JSON.parse(content);
859
+ } catch {
860
+ // Start with empty config if file is corrupted
861
+ }
862
+ }
863
+
864
+ if (!fileConfig.marketplaces) {
865
+ fileConfig.marketplaces = {};
866
+ }
867
+ fileConfig.marketplaces[name] = config;
868
+
869
+ await fs.writeFile(
870
+ configPath,
871
+ JSON.stringify(fileConfig, null, 2),
872
+ "utf-8",
873
+ );
874
+ }
875
+
876
+ /**
877
+ * Remove a marketplace from the specified scope
878
+ */
879
+ async removeMarketplaceFromScope(
880
+ workdir: string,
881
+ scope: Scope,
882
+ name: string,
883
+ ): Promise<void> {
884
+ if (scope !== "user" && !existsSync(workdir)) {
885
+ throw new Error(`Working directory does not exist: ${workdir}`);
886
+ }
887
+
888
+ let configPath: string;
889
+ if (scope === "user") {
890
+ configPath = getUserConfigPaths()[0];
891
+ } else if (scope === "project") {
892
+ configPath = getProjectConfigPaths(workdir)[1];
893
+ } else {
894
+ configPath = getProjectConfigPaths(workdir)[0];
895
+ }
896
+
897
+ if (!existsSync(configPath)) {
898
+ return;
899
+ }
900
+
901
+ try {
902
+ const content = await fs.readFile(configPath, "utf-8");
903
+ const fileConfig: WaveConfiguration = JSON.parse(content);
904
+
905
+ if (fileConfig.marketplaces && name in fileConfig.marketplaces) {
906
+ delete fileConfig.marketplaces[name];
907
+ await fs.writeFile(
908
+ configPath,
909
+ JSON.stringify(fileConfig, null, 2),
910
+ "utf-8",
911
+ );
912
+ }
913
+ } catch {
914
+ // Ignore errors for corrupted or non-existent files
915
+ }
916
+ }
917
+
799
918
  /**
800
919
  * Remove a plugin from the enabled plugins in the specified scope
801
920
  */
@@ -991,6 +1110,7 @@ export function loadWaveConfigFromFile(
991
1110
  ? config.autoMemoryEnabled
992
1111
  : undefined,
993
1112
  models: config.models || undefined,
1113
+ marketplaces: config.marketplaces || undefined,
994
1114
  };
995
1115
  } catch (error) {
996
1116
  if (error instanceof SyntaxError) {
@@ -1124,6 +1244,12 @@ export function loadMergedWaveConfig(
1124
1244
  mergedConfig.autoMemoryFrequency = config.autoMemoryFrequency;
1125
1245
  }
1126
1246
 
1247
+ // Merge marketplaces (last one wins for same key)
1248
+ if (config.marketplaces) {
1249
+ if (!mergedConfig.marketplaces) mergedConfig.marketplaces = {};
1250
+ Object.assign(mergedConfig.marketplaces, config.marketplaces);
1251
+ }
1252
+
1127
1253
  // Merge models
1128
1254
  if (config.models) {
1129
1255
  if (!mergedConfig.models) mergedConfig.models = {};
@@ -1157,6 +1283,11 @@ export function loadMergedWaveConfig(
1157
1283
  : undefined,
1158
1284
  language: mergedConfig.language,
1159
1285
  autoMemoryEnabled: mergedConfig.autoMemoryEnabled,
1286
+ marketplaces:
1287
+ mergedConfig.marketplaces &&
1288
+ Object.keys(mergedConfig.marketplaces).length > 0
1289
+ ? mergedConfig.marketplaces
1290
+ : undefined,
1160
1291
  models:
1161
1292
  mergedConfig.models && Object.keys(mergedConfig.models).length > 0
1162
1293
  ? mergedConfig.models
@@ -84,6 +84,23 @@ async function buildHookJsonInput(
84
84
  }
85
85
  }
86
86
 
87
+ // Add SessionStart-specific fields
88
+ if (context.event === "SessionStart") {
89
+ if (context.source !== undefined) {
90
+ jsonInput.source = context.source;
91
+ }
92
+ if (context.agentType !== undefined) {
93
+ jsonInput.agent_type = context.agentType;
94
+ }
95
+ }
96
+
97
+ // Add SessionEnd-specific fields
98
+ if (context.event === "SessionEnd") {
99
+ if (context.endSource !== undefined) {
100
+ jsonInput.end_source = context.endSource;
101
+ }
102
+ }
103
+
87
104
  return jsonInput;
88
105
  }
89
106
 
@@ -178,6 +178,38 @@ export class InitializationService {
178
178
  // Don't throw error to prevent app startup failure
179
179
  }
180
180
 
181
+ // Execute SessionStart hooks
182
+ try {
183
+ const phaseStart = performance.now();
184
+ const sessionStartResult = await hookManager.executeSessionStartHooks(
185
+ "startup",
186
+ messageManager.getSessionId(),
187
+ messageManager.getTranscriptPath(),
188
+ );
189
+
190
+ // Inject additionalContext as a meta user message (matches Claude Code)
191
+ if (sessionStartResult.additionalContext) {
192
+ messageManager.addUserMessage({
193
+ content: `<system-reminder>\nSessionStart hook additional context: ${sessionStartResult.additionalContext}\n</system-reminder>`,
194
+ isMeta: true,
195
+ });
196
+ }
197
+
198
+ // Inject initialUserMessage as a meta user message
199
+ if (sessionStartResult.initialUserMessage) {
200
+ messageManager.addUserMessage({
201
+ content: sessionStartResult.initialUserMessage,
202
+ isMeta: true,
203
+ });
204
+ }
205
+
206
+ logger?.debug(
207
+ `Initialization Phase [SessionStart Hooks] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
208
+ );
209
+ } catch (error) {
210
+ logger?.warn("SessionStart hooks execution failed:", error);
211
+ }
212
+
181
213
  // Trigger WorktreeCreate hook if this is a new worktree
182
214
  if (agentOptions.isNewWorktree && hookManager) {
183
215
  try {
@@ -318,7 +350,7 @@ export class InitializationService {
318
350
  if (sessionToRestore) {
319
351
  messageManager.initializeFromSession(sessionToRestore);
320
352
 
321
- // Update task manager with the root session ID to ensure continuity across compressions
353
+ // Update task manager with the root session ID to ensure continuity across compactions
322
354
  taskManager.setTaskListId(
323
355
  sessionToRestore.rootSessionId || sessionToRestore.id,
324
356
  );
@@ -183,7 +183,7 @@ export class InteractionService {
183
183
  // 6. Initialize session state last
184
184
  messageManager.initializeFromSession(sessionData);
185
185
 
186
- // Update task manager with the root session ID to ensure continuity across compressions
186
+ // Update task manager with the root session ID to ensure continuity across compactions
187
187
  taskManager.setTaskListId(sessionData.rootSessionId || sessionData.id);
188
188
 
189
189
  // 7. Load tasks for the restored session
@@ -153,7 +153,13 @@ export class PluginLoader {
153
153
  const hooksPath = path.join(pluginPath, "hooks", "hooks.json");
154
154
  try {
155
155
  const content = await fs.readFile(hooksPath, "utf-8");
156
- return JSON.parse(content) as PartialHookConfiguration;
156
+ const parsed = JSON.parse(content);
157
+ // Claude Code wrapper format: { "hooks": { "SessionStart": [...] } }
158
+ // (optional "description" sibling key)
159
+ if (parsed && typeof parsed === "object" && "hooks" in parsed) {
160
+ return parsed.hooks as PartialHookConfiguration;
161
+ }
162
+ return undefined;
157
163
  } catch {
158
164
  return undefined;
159
165
  }
@@ -816,7 +816,7 @@ export async function sessionExistsInJsonl(
816
816
  /**
817
817
  * Get the content of the first message in a session
818
818
  * For user role: get text block content
819
- * For assistant role: get compress block content
819
+ * For assistant role: get compact block content
820
820
  * @param sessionId - Session ID to get first message from
821
821
  * @param workdir - Working directory for session operations
822
822
  * @returns Promise that resolves to the first message content or null if not found
@@ -996,18 +996,18 @@ export async function loadFullMessageThread(
996
996
 
997
997
  sessionIds.unshift(currentId);
998
998
  // Add messages from this session to the beginning of the list
999
- // But skip the "compress" block if it's not the first session in our traversal (which is the latest)
999
+ // But skip the "compact" block if it's not the first session in our traversal (which is the latest)
1000
1000
  // Actually, we should probably keep all messages and let the UI/logic handle it.
1001
- // But wait, if we are concatenating, the "compress" block in session N summarizes session N-1.
1002
- // So if we have session N-1 and session N, we should probably skip the compress block in session N.
1001
+ // But wait, if we are concatenating, the "compact" block in session N summarizes session N-1.
1002
+ // So if we have session N-1 and session N, we should probably skip the compact block in session N.
1003
1003
 
1004
1004
  const messages = sessionData.messages;
1005
1005
  if (allMessages.length > 0) {
1006
1006
  // If we already have messages (from "later" sessions),
1007
1007
  // we are now adding messages from an "earlier" session.
1008
- // The later session's first message might be a "compress" block.
1009
- if (allMessages[0].blocks.some((b) => b.type === "compress")) {
1010
- // Remove the compress block from the later session's messages
1008
+ // The later session's first message might be a "compact" block.
1009
+ if (allMessages[0].blocks.some((b) => b.type === "compact")) {
1010
+ // Remove the compact block from the later session's messages
1011
1011
  // because we are now providing the actual messages it summarized.
1012
1012
  allMessages.shift();
1013
1013
  }
@@ -30,7 +30,7 @@ export class TaskManager extends EventEmitter {
30
30
 
31
31
  /**
32
32
  * Syncs the task list ID with the current session's root session ID.
33
- * This is typically called when the session is cleared or compressed.
33
+ * This is typically called when the session is cleared or compacted.
34
34
  */
35
35
  public async syncWithSession(): Promise<void> {
36
36
  const messageManager = this.container.get<MessageManager>("MessageManager");
@@ -9,9 +9,15 @@
9
9
  import type { HookEvent, HookEventConfig } from "./hooks.js";
10
10
  import type { PermissionMode } from "./permissions.js";
11
11
  import type { ModelConfig } from "./config.js";
12
+ import type { MarketplaceSource } from "./marketplace.js";
12
13
 
13
14
  export type Scope = "user" | "project" | "local";
14
15
 
16
+ export interface MarketplaceConfig {
17
+ source: MarketplaceSource;
18
+ autoUpdate?: boolean;
19
+ }
20
+
15
21
  /**
16
22
  * Root configuration structure for all Wave Agent settings including hooks and environment variables
17
23
  */
@@ -39,6 +45,8 @@ export interface WaveConfiguration {
39
45
  autoMemoryFrequency?: number;
40
46
  /** Model-specific configuration overrides */
41
47
  models?: Record<string, Partial<ModelConfig>>;
48
+ /** Scoped marketplace declarations */
49
+ marketplaces?: Record<string, MarketplaceConfig>;
42
50
  }
43
51
 
44
52
  /**
package/src/types/core.ts CHANGED
@@ -25,7 +25,7 @@ export interface Usage {
25
25
  completion_tokens: number; // Tokens generated in completions
26
26
  total_tokens: number; // Sum of prompt + completion tokens
27
27
  model?: string; // Model used for the operation (e.g., "gpt-4", "gpt-3.5-turbo")
28
- operation_type?: "agent" | "compress"; // Type of operation that generated usage
28
+ operation_type?: "agent" | "compact"; // Type of operation that generated usage
29
29
 
30
30
  // Cache-related tokens (Claude models only)
31
31
  cache_read_input_tokens?: number; // Tokens read from cache
@@ -21,7 +21,9 @@ export type HookEvent =
21
21
  | "SubagentStop"
22
22
  | "PermissionRequest"
23
23
  | "WorktreeCreate"
24
- | "CwdChanged";
24
+ | "CwdChanged"
25
+ | "SessionStart"
26
+ | "SessionEnd";
25
27
 
26
28
  // Individual hook command configuration
27
29
  export interface HookCommand {
@@ -94,6 +96,10 @@ export class HookConfigurationError extends Error {
94
96
  }
95
97
  }
96
98
 
99
+ export type SessionStartSource = "startup" | "resume" | "compact";
100
+
101
+ export type SessionEndSource = "exit" | "stop" | "compact";
102
+
97
103
  // Type guards for runtime validation
98
104
  export function isValidHookEvent(event: string): event is HookEvent {
99
105
  return [
@@ -105,6 +111,8 @@ export function isValidHookEvent(event: string): event is HookEvent {
105
111
  "PermissionRequest",
106
112
  "WorktreeCreate",
107
113
  "CwdChanged",
114
+ "SessionStart",
115
+ "SessionEnd",
108
116
  ].includes(event);
109
117
  }
110
118
 
@@ -161,7 +169,7 @@ export interface HookJsonInput {
161
169
  session_id: string; // Format: "wave_session_{uuid}_{shortId}"
162
170
  transcript_path: string; // Format: "~/.wave/sessions/session_{shortId}.json"
163
171
  cwd: string; // Absolute path to current working directory
164
- hook_event_name: HookEvent; // "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop" | "SubagentStop" | "PermissionRequest" | "WorktreeCreate" | "CwdChanged"
172
+ hook_event_name: HookEvent; // "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop" | "SubagentStop" | "PermissionRequest" | "WorktreeCreate" | "CwdChanged" | "SessionStart"
165
173
 
166
174
  // Optional fields based on event type
167
175
  tool_name?: string; // Present for PreToolUse, PostToolUse, PermissionRequest
@@ -172,6 +180,9 @@ export interface HookJsonInput {
172
180
  name?: string; // Present for WorktreeCreate events
173
181
  old_cwd?: string; // Present for CwdChanged events
174
182
  new_cwd?: string; // Present for CwdChanged events
183
+ source?: SessionStartSource; // Present for SessionStart events
184
+ agent_type?: string; // Present for SessionStart events
185
+ end_source?: SessionEndSource; // Present for SessionEnd events
175
186
  }
176
187
 
177
188
  // Extended context interface for passing additional data to hook executor
@@ -187,6 +198,9 @@ export interface ExtendedHookExecutionContext extends HookExecutionContext {
187
198
  worktreeName?: string; // Worktree name (WorktreeCreate only)
188
199
  oldCwd?: string; // Previous working directory (CwdChanged only)
189
200
  newCwd?: string; // New working directory (CwdChanged only)
201
+ source?: SessionStartSource; // Session start source (SessionStart only)
202
+ agentType?: string; // Agent type identifier (SessionStart only)
203
+ endSource?: SessionEndSource; // Session end source (SessionEnd only)
190
204
  }
191
205
 
192
206
  // Environment variables injected into hook processes
@@ -48,6 +48,8 @@ export interface KnownMarketplace {
48
48
  isBuiltin?: boolean;
49
49
  autoUpdate?: boolean;
50
50
  lastUpdated?: string;
51
+ /** The scope where this marketplace was declared (user, project, local, or builtin) */
52
+ declaredScope?: "user" | "project" | "local" | "builtin";
51
53
  }
52
54
 
53
55
  export interface KnownMarketplacesRegistry {
@@ -25,7 +25,7 @@ export type MessageBlock =
25
25
  | ToolBlock
26
26
  | ImageBlock
27
27
  | BangBlock
28
- | CompressBlock
28
+ | CompactBlock
29
29
  | ReasoningBlock
30
30
  | FileHistoryBlock
31
31
  | TaskNotificationBlock;
@@ -85,8 +85,8 @@ export interface BangBlock {
85
85
  exitCode: number | null;
86
86
  }
87
87
 
88
- export interface CompressBlock {
89
- type: "compress";
88
+ export interface CompactBlock {
89
+ type: "compact";
90
90
  content: string;
91
91
  sessionId: string;
92
92
  }
@@ -33,7 +33,7 @@ function safeToolArguments(args: string): string {
33
33
  }
34
34
 
35
35
  /**
36
- * Convert message format to API call format, stopping when a compressed message is encountered.
36
+ * Convert message format to API call format, stopping when a compacted message is encountered.
37
37
  * Messages with no meaningful content or tool calls are filtered out.
38
38
  * @param messages Message list
39
39
  * @returns Converted API message format list
@@ -47,19 +47,19 @@ export function convertMessagesForAPI(
47
47
  for (let i = startIndex; i >= 0; i--) {
48
48
  const message = messages[i];
49
49
 
50
- // Check if a compression block is encountered, if so, stop iteration
50
+ // Check if a compaction block is encountered, if so, stop iteration
51
51
  if (
52
52
  message.role === "assistant" &&
53
- message.blocks.some((block) => block.type === "compress")
53
+ message.blocks.some((block) => block.type === "compact")
54
54
  ) {
55
- // Add the content of the compression block as an assistant message to the history
56
- const compressBlock = message.blocks.find(
57
- (block) => block.type === "compress",
55
+ // Add the content of the compaction block as an assistant message to the history
56
+ const compactBlock = message.blocks.find(
57
+ (block) => block.type === "compact",
58
58
  );
59
- if (compressBlock && compressBlock.type === "compress") {
59
+ if (compactBlock && compactBlock.type === "compact") {
60
60
  recentMessages.unshift({
61
61
  role: "user",
62
- content: compressBlock.content,
62
+ content: compactBlock.content,
63
63
  });
64
64
  }
65
65
  break;
@@ -14,7 +14,7 @@ export interface ApiRound {
14
14
  * Boundaries:
15
15
  * - A new `role: "user"` message starts a new round.
16
16
  * - A new `role: "assistant"` message with a different `id` starts a new round.
17
- * - A message with a `compress` block is pushed as its own round and starts a
17
+ * - A message with a `compact` block is pushed as its own round and starts a
18
18
  * new round after it.
19
19
  */
20
20
  export function groupMessagesByApiRound(messages: Message[]): ApiRound[] {
@@ -28,9 +28,9 @@ export function groupMessagesByApiRound(messages: Message[]): ApiRound[] {
28
28
  if (msg.role === "user") {
29
29
  startNewRound = true;
30
30
  } else if (msg.role === "assistant") {
31
- // Compress block is always its own round
32
- const hasCompress = msg.blocks.some((b) => b.type === "compress");
33
- if (hasCompress) {
31
+ // Compact block is always its own round
32
+ const hasCompact = msg.blocks.some((b) => b.type === "compact");
33
+ if (hasCompact) {
34
34
  startNewRound = true;
35
35
  } else if (msg.id !== lastAssistantId) {
36
36
  // New assistant id starts a new round.
@@ -58,10 +58,10 @@ export function groupMessagesByApiRound(messages: Message[]): ApiRound[] {
58
58
 
59
59
  currentRound.push(msg);
60
60
 
61
- // After pushing a compress message as its own round, flush immediately
61
+ // After pushing a compact message as its own round, flush immediately
62
62
  if (
63
63
  msg.role === "assistant" &&
64
- msg.blocks.some((b) => b.type === "compress")
64
+ msg.blocks.some((b) => b.type === "compact")
65
65
  ) {
66
66
  rounds.push({
67
67
  messages: currentRound,
@@ -582,11 +582,9 @@ export function getMessageContent(message: Message): string {
582
582
  return `!${bangBlock.command}`;
583
583
  }
584
584
 
585
- const compressBlock = message.blocks.find(
586
- (block) => block.type === "compress",
587
- );
588
- if (compressBlock && "content" in compressBlock) {
589
- return compressBlock.content;
585
+ const compactBlock = message.blocks.find((block) => block.type === "compact");
586
+ if (compactBlock && "content" in compactBlock) {
587
+ return compactBlock.content;
590
588
  }
591
589
 
592
590
  return "";