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.
- package/CHANGELOG.md +21 -0
- package/README.md +4 -2
- package/dist/agent/browser-launcher.d.ts.map +1 -1
- package/dist/agent/browser-launcher.js +18 -1
- package/dist/agent/browser-launcher.js.map +1 -1
- package/dist/agent/command-handlers.d.ts +1 -1
- package/dist/agent/command-handlers.d.ts.map +1 -1
- package/dist/agent/command-handlers.js +1 -14
- package/dist/agent/command-handlers.js.map +1 -1
- package/dist/agent/context.js +1 -1
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/interrupt-handler.d.ts +1 -0
- package/dist/agent/interrupt-handler.d.ts.map +1 -1
- package/dist/agent/interrupt-handler.js +22 -2
- package/dist/agent/interrupt-handler.js.map +1 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +2 -1
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/openai-file-session.d.ts +19 -0
- package/dist/agent/openai-file-session.d.ts.map +1 -0
- package/dist/agent/openai-file-session.js +78 -0
- package/dist/agent/openai-file-session.js.map +1 -0
- package/dist/agent/openai-session.d.ts +50 -0
- package/dist/agent/openai-session.d.ts.map +1 -0
- package/dist/agent/openai-session.js +413 -0
- package/dist/agent/openai-session.js.map +1 -0
- package/dist/agent/openai-tools.d.ts +11 -0
- package/dist/agent/openai-tools.d.ts.map +1 -0
- package/dist/agent/openai-tools.js +167 -0
- package/dist/agent/openai-tools.js.map +1 -0
- package/dist/agent/providers/claude/session.d.ts +134 -0
- package/dist/agent/providers/claude/session.d.ts.map +1 -0
- package/dist/agent/providers/claude/session.js +525 -0
- package/dist/agent/providers/claude/session.js.map +1 -0
- package/dist/agent/providers/client-factory.d.ts +41 -0
- package/dist/agent/providers/client-factory.d.ts.map +1 -0
- package/dist/agent/providers/client-factory.js +190 -0
- package/dist/agent/providers/client-factory.js.map +1 -0
- package/dist/agent/providers/openai/file-session.d.ts +19 -0
- package/dist/agent/providers/openai/file-session.d.ts.map +1 -0
- package/dist/agent/providers/openai/file-session.js +78 -0
- package/dist/agent/providers/openai/file-session.js.map +1 -0
- package/dist/agent/providers/openai/session.d.ts +47 -0
- package/dist/agent/providers/openai/session.d.ts.map +1 -0
- package/dist/agent/providers/openai/session.js +414 -0
- package/dist/agent/providers/openai/session.js.map +1 -0
- package/dist/agent/providers/openai/tools.d.ts +13 -0
- package/dist/agent/providers/openai/tools.d.ts.map +1 -0
- package/dist/agent/providers/openai/tools.js +208 -0
- package/dist/agent/providers/openai/tools.js.map +1 -0
- package/dist/agent/providers/session-types.d.ts +123 -0
- package/dist/agent/providers/session-types.d.ts.map +1 -0
- package/dist/agent/providers/session-types.js +2 -0
- package/dist/agent/providers/session-types.js.map +1 -0
- package/dist/agent/runtime-surface.d.ts +23 -0
- package/dist/agent/runtime-surface.d.ts.map +1 -0
- package/dist/agent/runtime-surface.js +48 -0
- package/dist/agent/runtime-surface.js.map +1 -0
- package/dist/agent/session-types.d.ts +111 -0
- package/dist/agent/session-types.d.ts.map +1 -0
- package/dist/agent/session-types.js +2 -0
- package/dist/agent/session-types.js.map +1 -0
- package/dist/agent/status.d.ts.map +1 -1
- package/dist/agent/status.js +0 -7
- package/dist/agent/status.js.map +1 -1
- package/dist/agent/system-prompt.js +1 -1
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/builtin-skills/macos-automation/SKILL.md +43 -26
- package/dist/builtin-skills/visionclaw-manual/SKILL.md +3 -3
- package/dist/calendar/google-calendar.d.ts.map +1 -1
- package/dist/calendar/google-calendar.js +3 -1
- package/dist/calendar/google-calendar.js.map +1 -1
- package/dist/channels/interface.d.ts +0 -10
- package/dist/channels/interface.d.ts.map +1 -1
- package/dist/channels/manager.d.ts.map +1 -1
- package/dist/channels/manager.js +0 -3
- package/dist/channels/manager.js.map +1 -1
- package/dist/channels/telegram.d.ts.map +1 -1
- package/dist/channels/telegram.js +0 -13
- package/dist/channels/telegram.js.map +1 -1
- package/dist/config/types.d.ts +5 -5
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +15 -6
- package/dist/config/types.js.map +1 -1
- package/dist/drive/google-drive.d.ts.map +1 -1
- package/dist/drive/google-drive.js +3 -1
- package/dist/drive/google-drive.js.map +1 -1
- package/dist/email/gmail-utils.d.ts.map +1 -1
- package/dist/email/gmail-utils.js +3 -1
- package/dist/email/gmail-utils.js.map +1 -1
- package/dist/google/default-oauth-app.d.ts +6 -0
- package/dist/google/default-oauth-app.d.ts.map +1 -0
- package/dist/google/default-oauth-app.js +7 -0
- package/dist/google/default-oauth-app.js.map +1 -0
- package/dist/google/oauth-credentials.d.ts +10 -0
- package/dist/google/oauth-credentials.d.ts.map +1 -0
- package/dist/google/oauth-credentials.js +39 -0
- package/dist/google/oauth-credentials.js.map +1 -0
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/obs/server.js +1 -1
- package/dist/obs/server.js.map +1 -1
- package/dist/obs/tunnel.d.ts.map +1 -1
- package/dist/obs/tunnel.js +18 -2
- package/dist/obs/tunnel.js.map +1 -1
- package/dist/onboarding/google-auth.d.ts +2 -2
- package/dist/onboarding/google-auth.d.ts.map +1 -1
- package/dist/onboarding/google-auth.js +3 -1
- package/dist/onboarding/google-auth.js.map +1 -1
- package/dist/onboarding/google-cloud-setup.d.ts.map +1 -1
- package/dist/onboarding/google-cloud-setup.js +25 -6
- package/dist/onboarding/google-cloud-setup.js.map +1 -1
- package/dist/onboarding/index.d.ts.map +1 -1
- package/dist/onboarding/index.js +70 -21
- package/dist/onboarding/index.js.map +1 -1
- package/dist/onboarding/onboarding-session.d.ts +1 -0
- package/dist/onboarding/onboarding-session.d.ts.map +1 -1
- package/dist/onboarding/onboarding-session.js +2 -3
- package/dist/onboarding/onboarding-session.js.map +1 -1
- package/dist/onboarding/onboarding-tools.d.ts +0 -1
- package/dist/onboarding/onboarding-tools.d.ts.map +1 -1
- package/dist/onboarding/onboarding-tools.js +0 -6
- package/dist/onboarding/onboarding-tools.js.map +1 -1
- package/dist/onboarding/prepare-windows.d.ts +9 -0
- package/dist/onboarding/prepare-windows.d.ts.map +1 -0
- package/dist/onboarding/prepare-windows.js +250 -0
- package/dist/onboarding/prepare-windows.js.map +1 -0
- package/dist/onboarding/set-owner.js +1 -1
- package/dist/onboarding/set-owner.js.map +1 -1
- package/dist/onboarding/telegram-onboarding.d.ts.map +1 -1
- package/dist/onboarding/telegram-onboarding.js +0 -1
- package/dist/onboarding/telegram-onboarding.js.map +1 -1
- package/dist/onboarding/windows-permissions.d.ts +20 -0
- package/dist/onboarding/windows-permissions.d.ts.map +1 -0
- package/dist/onboarding/windows-permissions.js +195 -0
- package/dist/onboarding/windows-permissions.js.map +1 -0
- package/dist/reconfigure.d.ts.map +1 -1
- package/dist/reconfigure.js +40 -9
- package/dist/reconfigure.js.map +1 -1
- package/dist/tools/computer-use.d.ts +3 -0
- package/dist/tools/computer-use.d.ts.map +1 -1
- package/dist/tools/computer-use.js +43 -11
- package/dist/tools/computer-use.js.map +1 -1
- package/dist/tools/desktop-executor-factory.d.ts +20 -0
- package/dist/tools/desktop-executor-factory.d.ts.map +1 -0
- package/dist/tools/desktop-executor-factory.js +56 -0
- package/dist/tools/desktop-executor-factory.js.map +1 -0
- package/dist/tools/desktop-executor-windows.d.ts +42 -0
- package/dist/tools/desktop-executor-windows.d.ts.map +1 -0
- package/dist/tools/desktop-executor-windows.js +359 -0
- package/dist/tools/desktop-executor-windows.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/screenshot.d.ts.map +1 -1
- package/dist/tools/screenshot.js +101 -17
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +5 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +175 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/utils/restart.d.ts.map +1 -1
- package/dist/utils/restart.js +7 -2
- package/dist/utils/restart.js.map +1 -1
- package/dist/utils/transcribe.d.ts.map +1 -1
- package/dist/utils/transcribe.js +11 -5
- package/dist/utils/transcribe.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +2 -1
- package/dist/utils/version-check.js.map +1 -1
- 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
|