visionclaw 0.1.91 → 0.1.92

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 (171) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +4 -2
  3. package/dist/agent/browser-launcher.d.ts.map +1 -1
  4. package/dist/agent/browser-launcher.js +18 -1
  5. package/dist/agent/browser-launcher.js.map +1 -1
  6. package/dist/agent/command-handlers.d.ts +1 -1
  7. package/dist/agent/command-handlers.d.ts.map +1 -1
  8. package/dist/agent/command-handlers.js +1 -14
  9. package/dist/agent/command-handlers.js.map +1 -1
  10. package/dist/agent/context.js +1 -1
  11. package/dist/agent/context.js.map +1 -1
  12. package/dist/agent/interrupt-handler.d.ts +1 -0
  13. package/dist/agent/interrupt-handler.d.ts.map +1 -1
  14. package/dist/agent/interrupt-handler.js +22 -2
  15. package/dist/agent/interrupt-handler.js.map +1 -1
  16. package/dist/agent/loop.d.ts.map +1 -1
  17. package/dist/agent/loop.js +2 -1
  18. package/dist/agent/loop.js.map +1 -1
  19. package/dist/agent/openai-file-session.d.ts +19 -0
  20. package/dist/agent/openai-file-session.d.ts.map +1 -0
  21. package/dist/agent/openai-file-session.js +78 -0
  22. package/dist/agent/openai-file-session.js.map +1 -0
  23. package/dist/agent/openai-session.d.ts +50 -0
  24. package/dist/agent/openai-session.d.ts.map +1 -0
  25. package/dist/agent/openai-session.js +413 -0
  26. package/dist/agent/openai-session.js.map +1 -0
  27. package/dist/agent/openai-tools.d.ts +11 -0
  28. package/dist/agent/openai-tools.d.ts.map +1 -0
  29. package/dist/agent/openai-tools.js +167 -0
  30. package/dist/agent/openai-tools.js.map +1 -0
  31. package/dist/agent/providers/claude/session.d.ts +134 -0
  32. package/dist/agent/providers/claude/session.d.ts.map +1 -0
  33. package/dist/agent/providers/claude/session.js +525 -0
  34. package/dist/agent/providers/claude/session.js.map +1 -0
  35. package/dist/agent/providers/client-factory.d.ts +41 -0
  36. package/dist/agent/providers/client-factory.d.ts.map +1 -0
  37. package/dist/agent/providers/client-factory.js +190 -0
  38. package/dist/agent/providers/client-factory.js.map +1 -0
  39. package/dist/agent/providers/openai/file-session.d.ts +19 -0
  40. package/dist/agent/providers/openai/file-session.d.ts.map +1 -0
  41. package/dist/agent/providers/openai/file-session.js +78 -0
  42. package/dist/agent/providers/openai/file-session.js.map +1 -0
  43. package/dist/agent/providers/openai/session.d.ts +47 -0
  44. package/dist/agent/providers/openai/session.d.ts.map +1 -0
  45. package/dist/agent/providers/openai/session.js +414 -0
  46. package/dist/agent/providers/openai/session.js.map +1 -0
  47. package/dist/agent/providers/openai/tools.d.ts +13 -0
  48. package/dist/agent/providers/openai/tools.d.ts.map +1 -0
  49. package/dist/agent/providers/openai/tools.js +208 -0
  50. package/dist/agent/providers/openai/tools.js.map +1 -0
  51. package/dist/agent/providers/session-types.d.ts +123 -0
  52. package/dist/agent/providers/session-types.d.ts.map +1 -0
  53. package/dist/agent/providers/session-types.js +2 -0
  54. package/dist/agent/providers/session-types.js.map +1 -0
  55. package/dist/agent/runtime-surface.d.ts +23 -0
  56. package/dist/agent/runtime-surface.d.ts.map +1 -0
  57. package/dist/agent/runtime-surface.js +48 -0
  58. package/dist/agent/runtime-surface.js.map +1 -0
  59. package/dist/agent/session-types.d.ts +111 -0
  60. package/dist/agent/session-types.d.ts.map +1 -0
  61. package/dist/agent/session-types.js +2 -0
  62. package/dist/agent/session-types.js.map +1 -0
  63. package/dist/agent/status.d.ts.map +1 -1
  64. package/dist/agent/status.js +0 -7
  65. package/dist/agent/status.js.map +1 -1
  66. package/dist/agent/system-prompt.js +1 -1
  67. package/dist/agent/system-prompt.js.map +1 -1
  68. package/dist/builtin-skills/macos-automation/SKILL.md +43 -26
  69. package/dist/builtin-skills/visionclaw-manual/SKILL.md +3 -3
  70. package/dist/calendar/google-calendar.d.ts.map +1 -1
  71. package/dist/calendar/google-calendar.js +3 -1
  72. package/dist/calendar/google-calendar.js.map +1 -1
  73. package/dist/channels/interface.d.ts +0 -10
  74. package/dist/channels/interface.d.ts.map +1 -1
  75. package/dist/channels/manager.d.ts.map +1 -1
  76. package/dist/channels/manager.js +0 -3
  77. package/dist/channels/manager.js.map +1 -1
  78. package/dist/channels/telegram.d.ts.map +1 -1
  79. package/dist/channels/telegram.js +0 -13
  80. package/dist/channels/telegram.js.map +1 -1
  81. package/dist/config/types.d.ts +5 -5
  82. package/dist/config/types.d.ts.map +1 -1
  83. package/dist/config/types.js +15 -6
  84. package/dist/config/types.js.map +1 -1
  85. package/dist/drive/google-drive.d.ts.map +1 -1
  86. package/dist/drive/google-drive.js +3 -1
  87. package/dist/drive/google-drive.js.map +1 -1
  88. package/dist/email/gmail-utils.d.ts.map +1 -1
  89. package/dist/email/gmail-utils.js +3 -1
  90. package/dist/email/gmail-utils.js.map +1 -1
  91. package/dist/google/default-oauth-app.d.ts +6 -0
  92. package/dist/google/default-oauth-app.d.ts.map +1 -0
  93. package/dist/google/default-oauth-app.js +7 -0
  94. package/dist/google/default-oauth-app.js.map +1 -0
  95. package/dist/google/oauth-credentials.d.ts +10 -0
  96. package/dist/google/oauth-credentials.d.ts.map +1 -0
  97. package/dist/google/oauth-credentials.js +39 -0
  98. package/dist/google/oauth-credentials.js.map +1 -0
  99. package/dist/index.js +19 -3
  100. package/dist/index.js.map +1 -1
  101. package/dist/obs/server.js +1 -1
  102. package/dist/obs/server.js.map +1 -1
  103. package/dist/obs/tunnel.d.ts.map +1 -1
  104. package/dist/obs/tunnel.js +18 -2
  105. package/dist/obs/tunnel.js.map +1 -1
  106. package/dist/onboarding/google-auth.d.ts +2 -2
  107. package/dist/onboarding/google-auth.d.ts.map +1 -1
  108. package/dist/onboarding/google-auth.js +3 -1
  109. package/dist/onboarding/google-auth.js.map +1 -1
  110. package/dist/onboarding/google-cloud-setup.d.ts.map +1 -1
  111. package/dist/onboarding/google-cloud-setup.js +25 -6
  112. package/dist/onboarding/google-cloud-setup.js.map +1 -1
  113. package/dist/onboarding/index.d.ts.map +1 -1
  114. package/dist/onboarding/index.js +70 -21
  115. package/dist/onboarding/index.js.map +1 -1
  116. package/dist/onboarding/onboarding-session.d.ts +1 -0
  117. package/dist/onboarding/onboarding-session.d.ts.map +1 -1
  118. package/dist/onboarding/onboarding-session.js +2 -3
  119. package/dist/onboarding/onboarding-session.js.map +1 -1
  120. package/dist/onboarding/onboarding-tools.d.ts +0 -1
  121. package/dist/onboarding/onboarding-tools.d.ts.map +1 -1
  122. package/dist/onboarding/onboarding-tools.js +0 -6
  123. package/dist/onboarding/onboarding-tools.js.map +1 -1
  124. package/dist/onboarding/prepare-windows.d.ts +9 -0
  125. package/dist/onboarding/prepare-windows.d.ts.map +1 -0
  126. package/dist/onboarding/prepare-windows.js +250 -0
  127. package/dist/onboarding/prepare-windows.js.map +1 -0
  128. package/dist/onboarding/set-owner.js +1 -1
  129. package/dist/onboarding/set-owner.js.map +1 -1
  130. package/dist/onboarding/telegram-onboarding.d.ts.map +1 -1
  131. package/dist/onboarding/telegram-onboarding.js +0 -1
  132. package/dist/onboarding/telegram-onboarding.js.map +1 -1
  133. package/dist/onboarding/windows-permissions.d.ts +20 -0
  134. package/dist/onboarding/windows-permissions.d.ts.map +1 -0
  135. package/dist/onboarding/windows-permissions.js +195 -0
  136. package/dist/onboarding/windows-permissions.js.map +1 -0
  137. package/dist/reconfigure.d.ts.map +1 -1
  138. package/dist/reconfigure.js +40 -9
  139. package/dist/reconfigure.js.map +1 -1
  140. package/dist/tools/computer-use.d.ts +3 -0
  141. package/dist/tools/computer-use.d.ts.map +1 -1
  142. package/dist/tools/computer-use.js +43 -11
  143. package/dist/tools/computer-use.js.map +1 -1
  144. package/dist/tools/desktop-executor-factory.d.ts +20 -0
  145. package/dist/tools/desktop-executor-factory.d.ts.map +1 -0
  146. package/dist/tools/desktop-executor-factory.js +56 -0
  147. package/dist/tools/desktop-executor-factory.js.map +1 -0
  148. package/dist/tools/desktop-executor-windows.d.ts +42 -0
  149. package/dist/tools/desktop-executor-windows.d.ts.map +1 -0
  150. package/dist/tools/desktop-executor-windows.js +359 -0
  151. package/dist/tools/desktop-executor-windows.js.map +1 -0
  152. package/dist/tools/index.d.ts.map +1 -1
  153. package/dist/tools/index.js +2 -1
  154. package/dist/tools/index.js.map +1 -1
  155. package/dist/tools/screenshot.d.ts.map +1 -1
  156. package/dist/tools/screenshot.js +101 -17
  157. package/dist/tools/screenshot.js.map +1 -1
  158. package/dist/tools/web-fetch.d.ts +5 -0
  159. package/dist/tools/web-fetch.d.ts.map +1 -0
  160. package/dist/tools/web-fetch.js +175 -0
  161. package/dist/tools/web-fetch.js.map +1 -0
  162. package/dist/utils/restart.d.ts.map +1 -1
  163. package/dist/utils/restart.js +7 -2
  164. package/dist/utils/restart.js.map +1 -1
  165. package/dist/utils/transcribe.d.ts.map +1 -1
  166. package/dist/utils/transcribe.js +11 -5
  167. package/dist/utils/transcribe.js.map +1 -1
  168. package/dist/utils/version-check.d.ts.map +1 -1
  169. package/dist/utils/version-check.js +2 -1
  170. package/dist/utils/version-check.js.map +1 -1
  171. package/package.json +2 -1
@@ -0,0 +1,134 @@
1
+ import { type Query, type SDKMessage, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { MessageParam } from "@anthropic-ai/sdk/resources";
3
+ import type { SessionMode } from "../../mailbox.js";
4
+ import type { ScreenshotInjector } from "../../screenshot-injector.js";
5
+ import type { AgentSessionConstructorArgs, AgentSessionLike, AgentStreamMessage } from "../session-types.js";
6
+ export type { SDKMessage, SDKUserMessage };
7
+ export type { SystemPromptConfig } from "../session-types.js";
8
+ export declare const COMPACT_USED_PCT_THRESHOLD = 0.5;
9
+ export declare const MAX_BASE64_IMAGE_BLOCKS = 10;
10
+ /**
11
+ * Wraps the V1 query() API with a long-lived async generator for streaming
12
+ * input. This allows injecting additional user messages while the agent is
13
+ * processing (interrupt messages), instead of waiting for the query to
14
+ * finish before sending the next message.
15
+ */
16
+ export declare class ClaudeAgentSession implements AgentSessionLike {
17
+ private config;
18
+ private buildSystemPrompt;
19
+ private runtimeSurface;
20
+ private getDualSessionEnabled;
21
+ private nativeTools;
22
+ /** Resolved external MCP servers (playwright, serpapi, etc.) for this runtime. */
23
+ private externalMcpServers;
24
+ /** Dynamic MCP servers added at runtime via manage_mcp_servers tool. */
25
+ private dynamicMcpServers;
26
+ private currentQuery;
27
+ private sessionId;
28
+ private transcriptPath;
29
+ readonly mode: SessionMode;
30
+ /**
31
+ * Pending message queue for the long-lived generator.
32
+ * When a message is injected, it's pushed here and the waiting
33
+ * generator is woken up via the resolver.
34
+ */
35
+ private pendingMessages;
36
+ private messageResolver;
37
+ private generatorClosed;
38
+ private _orphanedInjections;
39
+ private lastCompactRequestAtMs;
40
+ private compactInFlight;
41
+ private stopRequested;
42
+ /** Optional: auto-injects screenshots after Playwright tool calls. */
43
+ private _screenshotInjector;
44
+ /** AbortController passed to the SDK; aborting it cleanly stops the running query. */
45
+ private abortController;
46
+ private lastUsageSnapshot;
47
+ constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }: AgentSessionConstructorArgs);
48
+ set screenshotInjector(injector: ScreenshotInjector | null);
49
+ /**
50
+ * Build a fresh mcpServers record with a new visionclaw tool server instance.
51
+ * This avoids the "Already connected to a transport" error when the same
52
+ * McpServer instance is reused across concurrent/sequential query() calls.
53
+ */
54
+ private buildMcpServers;
55
+ private buildNativeToolOptions;
56
+ /**
57
+ * Send the initial user message and return the query generator to stream
58
+ * responses. The underlying async generator stays open so that additional
59
+ * messages can be injected via injectMessage().
60
+ */
61
+ sendAndStream(content: MessageParam["content"]): AsyncIterable<AgentStreamMessage>;
62
+ /**
63
+ * Inject a new user message into the running query stream.
64
+ * The agent will see this as a follow-up user message in the conversation.
65
+ * Only works while a query is active (between sendAndStream and closeInput).
66
+ *
67
+ * @returns true if the message was injected, false if the generator is closed.
68
+ */
69
+ injectMessage(content: MessageParam["content"]): boolean;
70
+ /**
71
+ * Signal the input generator to close.
72
+ * Any injected messages still in the pending queue are counted as
73
+ * orphaned — they were accepted by injectMessage but never delivered
74
+ * to the SDK.
75
+ */
76
+ closeInput(): void;
77
+ /**
78
+ * Returns true if injected messages were lost when the generator closed.
79
+ */
80
+ get hasOrphanedInjections(): boolean;
81
+ get isInputClosed(): boolean;
82
+ /**
83
+ * Request that the current agent turn be stopped.
84
+ * Uses the SDK's AbortController to cleanly signal the running query to
85
+ * stop, then closes the input stream so the loop returns to idle.
86
+ */
87
+ requestStop(): void;
88
+ isStopRequested(): boolean;
89
+ clearStop(): void;
90
+ /**
91
+ * Update the persisted session ID.
92
+ */
93
+ captureSessionId(id: string | undefined): void;
94
+ captureTranscriptPath(p: string | undefined | null): void;
95
+ getSessionId(): string | null;
96
+ /**
97
+ * Proactively request compaction by running the /compact slash command as a
98
+ * separate one-turn query resumed from the current session.
99
+ *
100
+ * This is more reliable than injecting "/compact" into the streaming prompt
101
+ * because the running query may treat it as ordinary text.
102
+ */
103
+ requestCompaction(): Promise<void>;
104
+ captureUsageSnapshot(snapshot: {
105
+ usedInputTokens: number;
106
+ contextWindow: number;
107
+ usedPct: number;
108
+ }): void;
109
+ /**
110
+ * Update the usage snapshot after the SDK performs mid-query auto-compaction.
111
+ * Uses `pre_tokens` (the post-compaction token count) and the last known
112
+ * context window to compute an accurate `usedPct`, preventing the post-query
113
+ * `maybeCompactByTokens` from triggering a redundant compaction.
114
+ */
115
+ capturePostCompactionSnapshot(postCompactionTokens: number): void;
116
+ maybeCompactByTokens(options?: {
117
+ usedPctThreshold?: number;
118
+ cooldownMs?: number;
119
+ }): Promise<void>;
120
+ getTranscriptPath(): string | null;
121
+ getUsageSnapshot(): {
122
+ usedInputTokens: number;
123
+ contextWindow: number;
124
+ usedPct: number;
125
+ capturedAtMs: number;
126
+ } | null;
127
+ /** Returns the live Query object, or null if no query is active. */
128
+ getCurrentQuery(): Query | null;
129
+ /** Replace the full set of dynamic MCP servers (used when loading from disk). */
130
+ setDynamicMcpServers(servers: Record<string, Record<string, unknown>>): void;
131
+ /** Get the current dynamic MCP servers record. */
132
+ getDynamicMcpServers(): Record<string, Record<string, unknown>>;
133
+ }
134
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../src/agent/providers/claude/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,525 @@
1
+ import { query, } from "@anthropic-ai/claude-agent-sdk";
2
+ import { loadSessionId, saveSessionId, loadUsageSnapshot, saveUsageSnapshot, getConfigDir } from "../../../config/index.js";
3
+ import { logger } from "../../../logger.js";
4
+ import { createToolServer } from "../../../tools/index.js";
5
+ import { buildAgentEnv, getModelId } from "../client-factory.js";
6
+ import { getAgentState } from "../../state.js";
7
+ import { createRequire } from "module";
8
+ import path from "path";
9
+ import fs from "fs";
10
+ import { ensureBrowser } from "../../browser-launcher.js";
11
+ import * as imagePrunerUnTyped from "../../image-pruner.js";
12
+ const imagePruner = imagePrunerUnTyped;
13
+ export const COMPACT_USED_PCT_THRESHOLD = 0.5;
14
+ export const MAX_BASE64_IMAGE_BLOCKS = 10;
15
+ /**
16
+ * Wraps the V1 query() API with a long-lived async generator for streaming
17
+ * input. This allows injecting additional user messages while the agent is
18
+ * processing (interrupt messages), instead of waiting for the query to
19
+ * finish before sending the next message.
20
+ */
21
+ export class ClaudeAgentSession {
22
+ config;
23
+ buildSystemPrompt;
24
+ runtimeSurface;
25
+ getDualSessionEnabled;
26
+ nativeTools;
27
+ /** Resolved external MCP servers (playwright, serpapi, etc.) for this runtime. */
28
+ externalMcpServers;
29
+ /** Dynamic MCP servers added at runtime via manage_mcp_servers tool. */
30
+ dynamicMcpServers;
31
+ currentQuery = null;
32
+ sessionId;
33
+ transcriptPath = null;
34
+ mode;
35
+ /**
36
+ * Pending message queue for the long-lived generator.
37
+ * When a message is injected, it's pushed here and the waiting
38
+ * generator is woken up via the resolver.
39
+ */
40
+ pendingMessages = [];
41
+ messageResolver = null;
42
+ generatorClosed = true;
43
+ _orphanedInjections = 0;
44
+ lastCompactRequestAtMs = 0;
45
+ compactInFlight = false;
46
+ stopRequested = false;
47
+ /** Optional: auto-injects screenshots after Playwright tool calls. */
48
+ _screenshotInjector = null;
49
+ /** AbortController passed to the SDK; aborting it cleanly stops the running query. */
50
+ abortController = null;
51
+ lastUsageSnapshot = null;
52
+ constructor({ config, buildSystemPrompt, runtimeSurface, sessionContext, }) {
53
+ this.config = config;
54
+ this.buildSystemPrompt = buildSystemPrompt;
55
+ this.runtimeSurface = runtimeSurface;
56
+ this.getDualSessionEnabled = sessionContext.getDualSessionEnabled;
57
+ this.nativeTools = runtimeSurface.nativeTools;
58
+ this.externalMcpServers = runtimeSurface.externalMcpServers;
59
+ this.dynamicMcpServers = {};
60
+ this.mode = sessionContext.mode;
61
+ this.sessionId = loadSessionId(this.mode);
62
+ const persisted = loadUsageSnapshot(this.mode);
63
+ if (persisted && this.sessionId) {
64
+ this.lastUsageSnapshot = { ...persisted, capturedAtMs: Date.now() };
65
+ }
66
+ }
67
+ set screenshotInjector(injector) {
68
+ this._screenshotInjector = injector;
69
+ }
70
+ /**
71
+ * Build a fresh mcpServers record with a new visionclaw tool server instance.
72
+ * This avoids the "Already connected to a transport" error when the same
73
+ * McpServer instance is reused across concurrent/sequential query() calls.
74
+ */
75
+ buildMcpServers() {
76
+ if (this.runtimeSurface.visionClawToolTransport.transport !== "claude-mcp") {
77
+ throw new Error("Claude session requires claude-mcp VisionClaw tool transport");
78
+ }
79
+ return {
80
+ ...this.externalMcpServers,
81
+ ...this.dynamicMcpServers,
82
+ visionclaw: createToolServer({
83
+ dualSession: this.getDualSessionEnabled()
84
+ && this.runtimeSurface.visionClawToolTransport.supportsSwitchSessionTool,
85
+ }),
86
+ };
87
+ }
88
+ buildNativeToolOptions() {
89
+ if (this.nativeTools.kind === "claude-default") {
90
+ return {
91
+ tools: { type: "preset", preset: "claude_code" },
92
+ };
93
+ }
94
+ return {};
95
+ }
96
+ /**
97
+ * Send the initial user message and return the query generator to stream
98
+ * responses. The underlying async generator stays open so that additional
99
+ * messages can be injected via injectMessage().
100
+ */
101
+ sendAndStream(content) {
102
+ const savedSessionId = this.sessionId;
103
+ // Resolve the SDK's cli.js path so it works both in dev and bundled mode
104
+ const _require = createRequire(import.meta.url);
105
+ const sdkCliPath = path.resolve(path.dirname(_require.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
106
+ const options = {
107
+ model: getModelId(this.config),
108
+ pathToClaudeCodeExecutable: sdkCliPath,
109
+ systemPrompt: this.buildSystemPrompt(),
110
+ ...this.buildNativeToolOptions(),
111
+ permissionMode: "bypassPermissions",
112
+ allowDangerouslySkipPermissions: true,
113
+ cwd: getConfigDir(),
114
+ settingSources: ["project"],
115
+ mcpServers: this.buildMcpServers(),
116
+ hooks: {
117
+ UserPromptSubmit: [
118
+ {
119
+ hooks: [
120
+ (async (input, _toolUseID, { signal }) => {
121
+ const up = input;
122
+ try {
123
+ this.captureTranscriptPath(up.transcript_path);
124
+ if (signal.aborted)
125
+ return {};
126
+ // Prune aggressively throughout the session to prevent
127
+ // context bloat from accumulated base64 screenshots.
128
+ await imagePruner.pruneSessionImages({
129
+ transcriptPath: up.transcript_path,
130
+ keepLastNBase64Images: MAX_BASE64_IMAGE_BLOCKS,
131
+ });
132
+ }
133
+ catch (err) {
134
+ logger.warn(`UserPromptSubmit image pruning failed: ${err instanceof Error ? err.message : String(err)}`);
135
+ }
136
+ return {};
137
+ }),
138
+ ],
139
+ timeout: 120,
140
+ },
141
+ ],
142
+ PreCompact: [
143
+ {
144
+ hooks: [
145
+ (async (input, _toolUseID, { signal }) => {
146
+ const pre = input;
147
+ try {
148
+ this.captureTranscriptPath(pre.transcript_path);
149
+ // Best-effort pruning: keep only the last 10 base64 image blocks
150
+ // so compaction does not have to carry large screenshots forward.
151
+ // Abort early if hook is cancelled.
152
+ if (signal.aborted)
153
+ return {};
154
+ const res = await imagePruner.pruneSessionImages({
155
+ transcriptPath: pre.transcript_path,
156
+ keepLastNBase64Images: MAX_BASE64_IMAGE_BLOCKS,
157
+ });
158
+ if (res.fileChanged) {
159
+ logger.system(`Pruned transcript images before compaction: pruned=${res.prunedImageBlocks} resized=${res.resizedImageBlocks} kept=${res.keptImageBlocks} total=${res.totalImageBlocks}`);
160
+ }
161
+ }
162
+ catch (err) {
163
+ logger.warn(`PreCompact image pruning failed: ${err instanceof Error ? err.message : String(err)}`);
164
+ }
165
+ return {};
166
+ }),
167
+ ],
168
+ timeout: 120,
169
+ },
170
+ ],
171
+ PreToolUse: [
172
+ {
173
+ matcher: "mcp__playwright__.*",
174
+ hooks: [
175
+ (async (_input) => {
176
+ const pre = _input;
177
+ logger.debug(`[PreToolUse] ensuring browser for ${pre.tool_name}`);
178
+ await ensureBrowser();
179
+ return {};
180
+ }),
181
+ ],
182
+ timeout: 30,
183
+ },
184
+ ],
185
+ PostToolUse: [
186
+ {
187
+ hooks: [
188
+ (async (input, _toolUseID) => {
189
+ const post = input;
190
+ try {
191
+ const { resizedCount } = await imagePruner.resizeOversizedImagesInToolOutput(post.tool_response);
192
+ if (resizedCount > 0) {
193
+ logger.debug(`[PostToolUse] resized ${resizedCount} oversized image(s) in ${post.tool_name} output`);
194
+ return {
195
+ hookSpecificOutput: {
196
+ hookEventName: "PostToolUse",
197
+ updatedMCPToolOutput: post.tool_response,
198
+ },
199
+ };
200
+ }
201
+ }
202
+ catch (err) {
203
+ logger.warn(`PostToolUse image resize failed: ${err instanceof Error ? err.message : String(err)}`);
204
+ }
205
+ if (this._screenshotInjector) {
206
+ try {
207
+ await this._screenshotInjector.onToolComplete(post.tool_name, this);
208
+ }
209
+ catch (err) {
210
+ logger.warn(`PostToolUse screenshot injection failed: ${err instanceof Error ? err.message : String(err)}`);
211
+ }
212
+ }
213
+ return {};
214
+ }),
215
+ ],
216
+ timeout: 120,
217
+ },
218
+ ],
219
+ },
220
+ env: buildAgentEnv(this.config),
221
+ ...(savedSessionId ? { resume: savedSessionId } : {}),
222
+ };
223
+ // Seed the generator with the initial message
224
+ const initialMessage = {
225
+ type: "user",
226
+ session_id: savedSessionId ?? "",
227
+ message: { role: "user", content },
228
+ parent_tool_use_id: null,
229
+ };
230
+ this.pendingMessages = [initialMessage];
231
+ this.generatorClosed = false;
232
+ this._orphanedInjections = 0;
233
+ const abortController = new AbortController();
234
+ this.abortController = abortController;
235
+ // Create a long-lived async generator that yields the initial message
236
+ // and then waits for additional messages injected via injectMessage().
237
+ // We capture references to the session's pending queue and resolver
238
+ // so the generator can read from them without aliasing `this`.
239
+ const pending = this.pendingMessages;
240
+ const setResolver = (r) => {
241
+ this.messageResolver = r;
242
+ };
243
+ const isClosed = () => this.generatorClosed;
244
+ const shiftPending = () => pending.shift();
245
+ async function* messageStream() {
246
+ while (!isClosed()) {
247
+ // Yield all pending messages
248
+ let next = shiftPending();
249
+ while (next) {
250
+ yield next;
251
+ next = shiftPending();
252
+ }
253
+ // If generator is still open, wait for the next message
254
+ if (!isClosed()) {
255
+ await new Promise((resolve) => {
256
+ setResolver(resolve);
257
+ });
258
+ }
259
+ }
260
+ }
261
+ if (savedSessionId) {
262
+ logger.system(`Resuming session: ${savedSessionId}`);
263
+ }
264
+ else {
265
+ logger.system("Creating new session...");
266
+ }
267
+ this.currentQuery = query({
268
+ prompt: messageStream(),
269
+ options: {
270
+ ...options,
271
+ abortController,
272
+ ...(this.config.provider === "bedrock" ? { betas: ["context-1m-2025-08-07"] } : {}),
273
+ },
274
+ });
275
+ return this.currentQuery;
276
+ }
277
+ /**
278
+ * Inject a new user message into the running query stream.
279
+ * The agent will see this as a follow-up user message in the conversation.
280
+ * Only works while a query is active (between sendAndStream and closeInput).
281
+ *
282
+ * @returns true if the message was injected, false if the generator is closed.
283
+ */
284
+ injectMessage(content) {
285
+ const msg = {
286
+ type: "user",
287
+ session_id: this.sessionId ?? "",
288
+ message: { role: "user", content },
289
+ parent_tool_use_id: null,
290
+ };
291
+ if (this.generatorClosed) {
292
+ logger.warn("Cannot inject message: generator is closed");
293
+ return false;
294
+ }
295
+ this.pendingMessages.push(msg);
296
+ logger.info("Injected interrupt message into active session");
297
+ // Wake the generator if it's waiting
298
+ if (this.messageResolver) {
299
+ this.messageResolver();
300
+ this.messageResolver = null;
301
+ }
302
+ return true;
303
+ }
304
+ /**
305
+ * Signal the input generator to close.
306
+ * Any injected messages still in the pending queue are counted as
307
+ * orphaned — they were accepted by injectMessage but never delivered
308
+ * to the SDK.
309
+ */
310
+ closeInput() {
311
+ this._orphanedInjections = this.pendingMessages.length;
312
+ if (this._orphanedInjections > 0) {
313
+ logger.warn(`closeInput: ${this._orphanedInjections} injected message(s) orphaned`);
314
+ }
315
+ this.pendingMessages = [];
316
+ this.generatorClosed = true;
317
+ if (this.messageResolver) {
318
+ this.messageResolver();
319
+ this.messageResolver = null;
320
+ }
321
+ }
322
+ /**
323
+ * Returns true if injected messages were lost when the generator closed.
324
+ */
325
+ get hasOrphanedInjections() {
326
+ return this._orphanedInjections > 0;
327
+ }
328
+ get isInputClosed() {
329
+ return this.generatorClosed;
330
+ }
331
+ /**
332
+ * Request that the current agent turn be stopped.
333
+ * Uses the SDK's AbortController to cleanly signal the running query to
334
+ * stop, then closes the input stream so the loop returns to idle.
335
+ */
336
+ requestStop() {
337
+ this.stopRequested = true;
338
+ if (this.abortController) {
339
+ this.abortController.abort();
340
+ logger.system("Stop requested — aborting query via AbortController");
341
+ }
342
+ this.closeInput();
343
+ }
344
+ isStopRequested() {
345
+ return this.stopRequested;
346
+ }
347
+ clearStop() {
348
+ this.stopRequested = false;
349
+ this.abortController = null;
350
+ }
351
+ /**
352
+ * Update the persisted session ID.
353
+ */
354
+ captureSessionId(id) {
355
+ if (id) {
356
+ this.sessionId = id;
357
+ saveSessionId(id, this.mode);
358
+ }
359
+ }
360
+ captureTranscriptPath(p) {
361
+ if (p && typeof p === "string") {
362
+ this.transcriptPath = p;
363
+ }
364
+ }
365
+ getSessionId() {
366
+ return this.sessionId;
367
+ }
368
+ /**
369
+ * Proactively request compaction by running the /compact slash command as a
370
+ * separate one-turn query resumed from the current session.
371
+ *
372
+ * This is more reliable than injecting "/compact" into the streaming prompt
373
+ * because the running query may treat it as ordinary text.
374
+ */
375
+ async requestCompaction() {
376
+ if (this.compactInFlight)
377
+ return;
378
+ this.compactInFlight = true;
379
+ // TODO: maybe should use reply map.
380
+ const sendCompactNotice = (text) => {
381
+ try {
382
+ const state = getAgentState();
383
+ const { ownerConfig, channelManager } = state;
384
+ const channel = ownerConfig.telegramChatId ? "telegram" : ownerConfig.ownerEmail ? "gmail" : undefined;
385
+ const recipient = ownerConfig.telegramChatId ? String(ownerConfig.telegramChatId) : ownerConfig.ownerEmail;
386
+ if (channel && recipient) {
387
+ channelManager
388
+ .sendMessage(channel, recipient, text)
389
+ .catch((err) => {
390
+ logger.warn(`Failed to send compaction notification: ${err instanceof Error ? err.message : String(err)}`);
391
+ });
392
+ }
393
+ }
394
+ catch {
395
+ // Agent state not yet initialized — skip notification
396
+ }
397
+ };
398
+ try {
399
+ const savedSessionId = this.sessionId;
400
+ if (!savedSessionId) {
401
+ logger.warn("Cannot request /compact: session_id not available yet");
402
+ return;
403
+ }
404
+ // Resolve the SDK's cli.js path so it works both in dev and bundled mode
405
+ const _require = createRequire(import.meta.url);
406
+ const sdkCliPath = path.resolve(path.dirname(_require.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
407
+ if (!fs.existsSync(sdkCliPath)) {
408
+ logger.warn(`Cannot request /compact: sdk cli.js not found at ${sdkCliPath}`);
409
+ return;
410
+ }
411
+ const options = {
412
+ model: getModelId(this.config),
413
+ pathToClaudeCodeExecutable: sdkCliPath,
414
+ systemPrompt: this.buildSystemPrompt(),
415
+ permissionMode: "bypassPermissions",
416
+ allowDangerouslySkipPermissions: true,
417
+ cwd: getConfigDir(),
418
+ settingSources: ["project"],
419
+ mcpServers: this.buildMcpServers(),
420
+ env: buildAgentEnv(this.config),
421
+ resume: savedSessionId,
422
+ maxTurns: 1,
423
+ };
424
+ logger.system("Requesting /compact...");
425
+ sendCompactNotice("Context window is getting full — compacting session memory, this may take a minute. I may lose some older details.");
426
+ logger.debug(`Compaction query options: cli=${sdkCliPath} cwd=${getConfigDir()} resume=${savedSessionId}`);
427
+ for await (const msg of query({
428
+ prompt: "/compact",
429
+ options,
430
+ })) {
431
+ if (msg.type === "system" && msg.subtype === "compact_boundary") {
432
+ // Capture session id (should remain the same) and log compaction metadata.
433
+ if ("session_id" in msg && msg.session_id) {
434
+ this.captureSessionId(msg.session_id);
435
+ }
436
+ const meta = msg.compact_metadata;
437
+ logger.system(`Compaction completed (trigger=${meta?.trigger ?? "unknown"}, pre_tokens=${meta?.pre_tokens ?? "?"})`);
438
+ if (typeof meta?.pre_tokens === "number") {
439
+ this.capturePostCompactionSnapshot(meta.pre_tokens);
440
+ }
441
+ sendCompactNotice("Session memory compacted. Ready to continue.");
442
+ }
443
+ }
444
+ }
445
+ catch (err) {
446
+ logger.warn(`Request /compact failed: ${err instanceof Error ? err.message : String(err)}`);
447
+ }
448
+ finally {
449
+ this.compactInFlight = false;
450
+ }
451
+ }
452
+ captureUsageSnapshot(snapshot) {
453
+ this.lastUsageSnapshot = {
454
+ ...snapshot,
455
+ capturedAtMs: Date.now(),
456
+ };
457
+ saveUsageSnapshot(snapshot, this.mode);
458
+ }
459
+ /**
460
+ * Update the usage snapshot after the SDK performs mid-query auto-compaction.
461
+ * Uses `pre_tokens` (the post-compaction token count) and the last known
462
+ * context window to compute an accurate `usedPct`, preventing the post-query
463
+ * `maybeCompactByTokens` from triggering a redundant compaction.
464
+ */
465
+ capturePostCompactionSnapshot(postCompactionTokens) {
466
+ const contextWindow = this.lastUsageSnapshot?.contextWindow ?? 0;
467
+ if (contextWindow <= 0 || postCompactionTokens < 0) {
468
+ this.lastUsageSnapshot = null;
469
+ return;
470
+ }
471
+ this.captureUsageSnapshot({
472
+ usedInputTokens: postCompactionTokens,
473
+ contextWindow,
474
+ usedPct: postCompactionTokens / contextWindow,
475
+ });
476
+ }
477
+ async maybeCompactByTokens(options) {
478
+ const usedPctThreshold = options?.usedPctThreshold ?? COMPACT_USED_PCT_THRESHOLD;
479
+ const cooldownMs = options?.cooldownMs ?? 10 * 60 * 1000;
480
+ const snap = this.lastUsageSnapshot;
481
+ if (!snap) {
482
+ logger.debug("[compact] skip: no usage snapshot yet");
483
+ return;
484
+ }
485
+ const nowMs = Date.now();
486
+ const sinceLastCompactMs = nowMs - this.lastCompactRequestAtMs;
487
+ if (sinceLastCompactMs <= cooldownMs) {
488
+ logger.debug(`[compact] skip: cooldown active sinceLast=${sinceLastCompactMs}ms cooldown=${cooldownMs}ms (usedPct=${(snap.usedPct * 100).toFixed(1)}%)`);
489
+ return;
490
+ }
491
+ if (this.compactInFlight) {
492
+ logger.debug("[compact] skip: compaction already in flight");
493
+ return;
494
+ }
495
+ const snapshotAgeMs = nowMs - snap.capturedAtMs;
496
+ logger.debug(`[compact] check: usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(usedPctThreshold * 100).toFixed(1)}% used=${snap.usedInputTokens} window=${snap.contextWindow} snapshotAge=${snapshotAgeMs}ms`);
497
+ if (snap.contextWindow > 0 && snap.usedPct >= usedPctThreshold) {
498
+ this.lastCompactRequestAtMs = nowMs;
499
+ logger.system(`Context usage ${(snap.usedPct * 100).toFixed(1)}% (used=${snap.usedInputTokens}, window=${snap.contextWindow}); requesting /compact`);
500
+ await this.requestCompaction();
501
+ }
502
+ else {
503
+ logger.debug(`[compact] skip: under threshold (usedPct=${(snap.usedPct * 100).toFixed(1)}% threshold=${(usedPctThreshold * 100).toFixed(1)}%)`);
504
+ }
505
+ }
506
+ getTranscriptPath() {
507
+ return this.transcriptPath;
508
+ }
509
+ getUsageSnapshot() {
510
+ return this.lastUsageSnapshot ? { ...this.lastUsageSnapshot } : null;
511
+ }
512
+ /** Returns the live Query object, or null if no query is active. */
513
+ getCurrentQuery() {
514
+ return this.currentQuery;
515
+ }
516
+ /** Replace the full set of dynamic MCP servers (used when loading from disk). */
517
+ setDynamicMcpServers(servers) {
518
+ this.dynamicMcpServers = { ...servers };
519
+ }
520
+ /** Get the current dynamic MCP servers record. */
521
+ getDynamicMcpServers() {
522
+ return { ...this.dynamicMcpServers };
523
+ }
524
+ }
525
+ //# sourceMappingURL=session.js.map