visionclaw 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +116 -0
  2. package/dist/agent/context.d.ts +56 -0
  3. package/dist/agent/context.d.ts.map +1 -0
  4. package/dist/agent/context.js +142 -0
  5. package/dist/agent/context.js.map +1 -0
  6. package/dist/agent/loop.d.ts +18 -0
  7. package/dist/agent/loop.d.ts.map +1 -0
  8. package/dist/agent/loop.js +323 -0
  9. package/dist/agent/loop.js.map +1 -0
  10. package/dist/agent/session.d.ts +49 -0
  11. package/dist/agent/session.d.ts.map +1 -0
  12. package/dist/agent/session.js +200 -0
  13. package/dist/agent/session.js.map +1 -0
  14. package/dist/agent/system-prompt.d.ts +10 -0
  15. package/dist/agent/system-prompt.d.ts.map +1 -0
  16. package/dist/agent/system-prompt.js +167 -0
  17. package/dist/agent/system-prompt.js.map +1 -0
  18. package/dist/calendar/google-calendar.d.ts +46 -0
  19. package/dist/calendar/google-calendar.d.ts.map +1 -0
  20. package/dist/calendar/google-calendar.js +132 -0
  21. package/dist/calendar/google-calendar.js.map +1 -0
  22. package/dist/calendar/scheduler.d.ts +7 -0
  23. package/dist/calendar/scheduler.d.ts.map +1 -0
  24. package/dist/calendar/scheduler.js +33 -0
  25. package/dist/calendar/scheduler.js.map +1 -0
  26. package/dist/channels/discord.d.ts +19 -0
  27. package/dist/channels/discord.d.ts.map +1 -0
  28. package/dist/channels/discord.js +169 -0
  29. package/dist/channels/discord.js.map +1 -0
  30. package/dist/channels/gmail.d.ts +31 -0
  31. package/dist/channels/gmail.d.ts.map +1 -0
  32. package/dist/channels/gmail.js +300 -0
  33. package/dist/channels/gmail.js.map +1 -0
  34. package/dist/channels/interface.d.ts +45 -0
  35. package/dist/channels/interface.d.ts.map +1 -0
  36. package/dist/channels/interface.js +2 -0
  37. package/dist/channels/interface.js.map +1 -0
  38. package/dist/channels/manager.d.ts +36 -0
  39. package/dist/channels/manager.d.ts.map +1 -0
  40. package/dist/channels/manager.js +108 -0
  41. package/dist/channels/manager.js.map +1 -0
  42. package/dist/channels/queue.d.ts +17 -0
  43. package/dist/channels/queue.d.ts.map +1 -0
  44. package/dist/channels/queue.js +85 -0
  45. package/dist/channels/queue.js.map +1 -0
  46. package/dist/channels/slack.d.ts +17 -0
  47. package/dist/channels/slack.d.ts.map +1 -0
  48. package/dist/channels/slack.js +142 -0
  49. package/dist/channels/slack.js.map +1 -0
  50. package/dist/channels/sms.d.ts +19 -0
  51. package/dist/channels/sms.d.ts.map +1 -0
  52. package/dist/channels/sms.js +111 -0
  53. package/dist/channels/sms.js.map +1 -0
  54. package/dist/channels/telegram.d.ts +28 -0
  55. package/dist/channels/telegram.d.ts.map +1 -0
  56. package/dist/channels/telegram.js +246 -0
  57. package/dist/channels/telegram.js.map +1 -0
  58. package/dist/channels/whatsapp.d.ts +28 -0
  59. package/dist/channels/whatsapp.d.ts.map +1 -0
  60. package/dist/channels/whatsapp.js +292 -0
  61. package/dist/channels/whatsapp.js.map +1 -0
  62. package/dist/config/index.d.ts +24 -0
  63. package/dist/config/index.d.ts.map +1 -0
  64. package/dist/config/index.js +104 -0
  65. package/dist/config/index.js.map +1 -0
  66. package/dist/config/types.d.ts +227 -0
  67. package/dist/config/types.d.ts.map +1 -0
  68. package/dist/config/types.js +45 -0
  69. package/dist/config/types.js.map +1 -0
  70. package/dist/files.d.ts +20 -0
  71. package/dist/files.d.ts.map +1 -0
  72. package/dist/files.js +82 -0
  73. package/dist/files.js.map +1 -0
  74. package/dist/index.d.ts +3 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +54 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/logger.d.ts +76 -0
  79. package/dist/logger.d.ts.map +1 -0
  80. package/dist/logger.js +384 -0
  81. package/dist/logger.js.map +1 -0
  82. package/dist/memory/store.d.ts +24 -0
  83. package/dist/memory/store.d.ts.map +1 -0
  84. package/dist/memory/store.js +71 -0
  85. package/dist/memory/store.js.map +1 -0
  86. package/dist/obs/server.d.ts +10 -0
  87. package/dist/obs/server.d.ts.map +1 -0
  88. package/dist/obs/server.js +406 -0
  89. package/dist/obs/server.js.map +1 -0
  90. package/dist/onboarding/google-auth.d.ts +11 -0
  91. package/dist/onboarding/google-auth.d.ts.map +1 -0
  92. package/dist/onboarding/google-auth.js +113 -0
  93. package/dist/onboarding/google-auth.js.map +1 -0
  94. package/dist/onboarding/index.d.ts +2 -0
  95. package/dist/onboarding/index.d.ts.map +1 -0
  96. package/dist/onboarding/index.js +213 -0
  97. package/dist/onboarding/index.js.map +1 -0
  98. package/dist/onboarding/macos-permissions.d.ts +37 -0
  99. package/dist/onboarding/macos-permissions.d.ts.map +1 -0
  100. package/dist/onboarding/macos-permissions.js +207 -0
  101. package/dist/onboarding/macos-permissions.js.map +1 -0
  102. package/dist/skills/install.d.ts +7 -0
  103. package/dist/skills/install.d.ts.map +1 -0
  104. package/dist/skills/install.js +63 -0
  105. package/dist/skills/install.js.map +1 -0
  106. package/dist/tools/browser.d.ts +7 -0
  107. package/dist/tools/browser.d.ts.map +1 -0
  108. package/dist/tools/browser.js +202 -0
  109. package/dist/tools/browser.js.map +1 -0
  110. package/dist/tools/calendar.d.ts +12 -0
  111. package/dist/tools/calendar.d.ts.map +1 -0
  112. package/dist/tools/calendar.js +210 -0
  113. package/dist/tools/calendar.js.map +1 -0
  114. package/dist/tools/computer-use.d.ts +28 -0
  115. package/dist/tools/computer-use.d.ts.map +1 -0
  116. package/dist/tools/computer-use.js +311 -0
  117. package/dist/tools/computer-use.js.map +1 -0
  118. package/dist/tools/coordinate-resolver.d.ts +26 -0
  119. package/dist/tools/coordinate-resolver.d.ts.map +1 -0
  120. package/dist/tools/coordinate-resolver.js +157 -0
  121. package/dist/tools/coordinate-resolver.js.map +1 -0
  122. package/dist/tools/desktop-executor.d.ts +52 -0
  123. package/dist/tools/desktop-executor.d.ts.map +1 -0
  124. package/dist/tools/desktop-executor.js +202 -0
  125. package/dist/tools/desktop-executor.js.map +1 -0
  126. package/dist/tools/finish.d.ts +5 -0
  127. package/dist/tools/finish.d.ts.map +1 -0
  128. package/dist/tools/finish.js +18 -0
  129. package/dist/tools/finish.js.map +1 -0
  130. package/dist/tools/index.d.ts +7 -0
  131. package/dist/tools/index.d.ts.map +1 -0
  132. package/dist/tools/index.js +37 -0
  133. package/dist/tools/index.js.map +1 -0
  134. package/dist/tools/memory.d.ts +14 -0
  135. package/dist/tools/memory.d.ts.map +1 -0
  136. package/dist/tools/memory.js +269 -0
  137. package/dist/tools/memory.js.map +1 -0
  138. package/dist/tools/notify.d.ts +12 -0
  139. package/dist/tools/notify.d.ts.map +1 -0
  140. package/dist/tools/notify.js +108 -0
  141. package/dist/tools/notify.js.map +1 -0
  142. package/dist/tools/screenshot.d.ts +7 -0
  143. package/dist/tools/screenshot.d.ts.map +1 -0
  144. package/dist/tools/screenshot.js +189 -0
  145. package/dist/tools/screenshot.js.map +1 -0
  146. package/dist/tools/skill.d.ts +8 -0
  147. package/dist/tools/skill.d.ts.map +1 -0
  148. package/dist/tools/skill.js +133 -0
  149. package/dist/tools/skill.js.map +1 -0
  150. package/dist/tools/upgrade.d.ts +5 -0
  151. package/dist/tools/upgrade.d.ts.map +1 -0
  152. package/dist/tools/upgrade.js +89 -0
  153. package/dist/tools/upgrade.js.map +1 -0
  154. package/dist/tools/wait.d.ts +5 -0
  155. package/dist/tools/wait.d.ts.map +1 -0
  156. package/dist/tools/wait.js +21 -0
  157. package/dist/tools/wait.js.map +1 -0
  158. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # VisionClaw
2
+
3
+ A personal assistant agent that runs on your desktop (macOS/Windows). It receives command messages from pre-configured channels (Gmail, Telegram, Discord) and executes tasks autonomously using desktop control and browser automation. Results are sent back through the same channel.
4
+
5
+ ## Features
6
+
7
+ - **Autonomous Desktop Agent**: Runs continuously as a long-running process on your computer
8
+ - **Gmail Identity**: The agent has its own Gmail account for email and Google Calendar
9
+ - **Multi-Channel Support**: Receives commands via Gmail, Telegram, Discord
10
+ - **Desktop Control**: Takes screenshots, controls mouse/keyboard, runs terminal commands
11
+ - **Browser Automation**: Navigate and interact with web pages via Playwright
12
+ - **Google Calendar**: Manages its own schedule for recurring tasks and reminders
13
+ - **Self-Improving**: Can add new skills to itself and upgrade to new versions
14
+ - **Runtime Observability**: Built-in HTTP obs page for live logs while the agent is running
15
+
16
+ ## Architecture
17
+
18
+ VisionClaw is built on the [Claude Agent SDK V2](https://platform.claude.com/docs/en/agent-sdk/typescript-v2-preview). It runs as a single-threaded agent with a wake/sleep loop triggered by incoming messages or a periodic heartbeat.
19
+
20
+ ```
21
+ src/
22
+ index.ts # CLI entry point
23
+ onboarding/ # Interactive setup wizard (Gmail, OAuth, channels)
24
+ agent/ # Core agent loop, session management, context
25
+ tools/ # Custom tools (notify, browser, calendar, screenshot, etc.)
26
+ channels/ # Channel adapters (Gmail, Telegram, Discord)
27
+ calendar/ # Google Calendar integration
28
+ memory/ # Persistent memory store
29
+ config/ # Configuration management
30
+ obs/ # Runtime observability HTTP server
31
+ gui/ # Electron desktop GUI
32
+ ```
33
+
34
+ ## Prerequisites
35
+
36
+ - Node.js >= 20
37
+ - An Anthropic API key
38
+ - A dedicated Gmail account for the agent
39
+ - Google Cloud OAuth2 credentials (Client ID + Secret) with Gmail and Calendar API enabled
40
+
41
+ ## Setup
42
+
43
+ ```bash
44
+ # Install dependencies
45
+ pnpm install
46
+
47
+ # Build
48
+ pnpm run build
49
+
50
+ # Run (starts onboarding if not configured)
51
+ pnpm start
52
+
53
+ # Or run in development mode
54
+ pnpm run dev
55
+ ```
56
+
57
+ The first run triggers an interactive onboarding wizard that will:
58
+ 1. Ask for your Anthropic API key
59
+ 2. Ask for a dedicated Gmail address for the agent
60
+ 3. Walk through Google OAuth2 authorization (Gmail + Calendar scopes)
61
+ 4. Optionally configure Telegram and Discord
62
+
63
+ Configuration is stored per profile at `~/.visionclaw/profiles/<profile>/config.json`.
64
+
65
+ ## Observability (HTTP)
66
+
67
+ When the agent is running, it serves a local observability page showing live logs.
68
+
69
+ - URL: `http://127.0.0.1:3101/obs`
70
+ - SSE stream: `GET /obs/events`
71
+ - Snapshot: `GET /obs/snapshot`
72
+
73
+ This is controlled via **advanced config** (not asked during onboarding):
74
+
75
+ ```json
76
+ {
77
+ "obs": {
78
+ "enabled": true,
79
+ "host": "127.0.0.1",
80
+ "port": 3101,
81
+ "bufferSize": 1000
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## GUI
87
+
88
+ A desktop GUI (Electron) is available for configuration and monitoring:
89
+
90
+ ```bash
91
+ cd gui
92
+ pnpm install
93
+ pnpm start
94
+ ```
95
+
96
+ ## Channels
97
+
98
+ | Channel | Requirements | Status |
99
+ |-----------|----------------------------------|-------------|
100
+ | Gmail | Gmail account (required) | Always on |
101
+ | Telegram | Bot token from @BotFather | Optional |
102
+ | Discord | Bot token + channel allowlist | Optional |
103
+
104
+ ## Custom Tools
105
+
106
+ | Tool | Description |
107
+ |---------------------|-------------|
108
+ | `wait` | Pause execution for a specified duration |
109
+ | `notify_user` | Send a message back through a channel |
110
+ | `finish` | Signal task completion, return to sleep |
111
+ | `take_screenshot` | Capture desktop screenshot |
112
+ | `manage_skills` | Install/list skills (built-in + user) |
113
+ | `upgrade` | Check for and install updates |
114
+ | `manage_calendar` | Manage Google Calendar |
115
+ | `memory` | View/create/edit files in the agent memory store |
116
+ | `computer_use_*` | Mouse/keyboard/scroll/drag/screenshot actions |
@@ -0,0 +1,56 @@
1
+ import type { VisionClawConfig } from "../config/types.js";
2
+ import type { CommandMessage } from "../channels/interface.js";
3
+ /**
4
+ * A content block in a multimodal user message.
5
+ * Matches the Anthropic Messages API content block types.
6
+ */
7
+ export type ContentBlock = {
8
+ type: "text";
9
+ text: string;
10
+ } | {
11
+ type: "image";
12
+ source: {
13
+ type: "base64";
14
+ media_type: "image/png";
15
+ data: string;
16
+ } | {
17
+ type: "url";
18
+ url: string;
19
+ };
20
+ };
21
+ /**
22
+ * Screenshot reference: either a public URL (from Google Drive)
23
+ * or a raw base64 string as fallback.
24
+ */
25
+ export interface ScreenshotRef {
26
+ url?: string;
27
+ base64?: string;
28
+ }
29
+ /**
30
+ * Build the context message injected on each agent wake.
31
+ * Returns a multimodal content block array with text + optional image.
32
+ */
33
+ export declare function buildWakeContext(options: {
34
+ config: VisionClawConfig;
35
+ trigger: "heartbeat" | "message";
36
+ messages: CommandMessage[];
37
+ calendarSummary: string;
38
+ screenshot?: ScreenshotRef;
39
+ }): ContentBlock[];
40
+ /**
41
+ * Build context for an interrupt message — a new user message that arrives
42
+ * while the agent is already processing. Lighter than a full wake context
43
+ * (no screenshot or calendar).
44
+ */
45
+ export declare function buildInterruptContext(options: {
46
+ messages: CommandMessage[];
47
+ }): ContentBlock[];
48
+ /**
49
+ * Build a fresh context after compaction. Returns multimodal blocks.
50
+ */
51
+ export declare function buildPostCompactionContext(options: {
52
+ config: VisionClawConfig;
53
+ calendarSummary: string;
54
+ pendingMessageCount: number;
55
+ }): ContentBlock[];
56
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/agent/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EACF;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,UAAU,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GACzD;QAAE,IAAI,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CAClC,CAAC;AAEN;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACxC,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,aAAa,CAAC;CAC5B,GAAG,YAAY,EAAE,CAuFjB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B,GAAG,YAAY,EAAE,CA0CjB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IAClD,MAAM,EAAE,gBAAgB,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,YAAY,EAAE,CAqBjB"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Build the context message injected on each agent wake.
3
+ * Returns a multimodal content block array with text + optional image.
4
+ */
5
+ export function buildWakeContext(options) {
6
+ const { trigger, messages, calendarSummary, screenshot } = options;
7
+ const now = new Date().toLocaleString();
8
+ const blocks = [];
9
+ // --- Main text block ---
10
+ const parts = [];
11
+ parts.push(`[Wake Event] trigger=${trigger} time=${now}`);
12
+ parts.push("");
13
+ // Calendar summary
14
+ if (calendarSummary) {
15
+ parts.push("## Upcoming Calendar Events");
16
+ parts.push(calendarSummary);
17
+ parts.push("");
18
+ }
19
+ else {
20
+ parts.push("## Calendar");
21
+ parts.push("No upcoming events.");
22
+ parts.push("");
23
+ }
24
+ // Queued messages
25
+ if (messages.length > 0) {
26
+ parts.push("## New Messages");
27
+ for (const msg of messages) {
28
+ parts.push(`- [${msg.channel}] from ${msg.sender} at ${msg.timestamp}: ${msg.text}`);
29
+ if (msg.attachments.length > 0) {
30
+ for (const att of msg.attachments) {
31
+ const location = att.url ?? att.localPath ?? "(embedded)";
32
+ parts.push(` Attachment [${att.type}]: ${location}${att.filename ? ` (${att.filename})` : ""}`);
33
+ }
34
+ }
35
+ }
36
+ parts.push("");
37
+ }
38
+ else if (trigger === "heartbeat") {
39
+ parts.push("## Messages");
40
+ parts.push("No new messages.");
41
+ parts.push("");
42
+ }
43
+ // Screenshot note
44
+ if (screenshot?.url ?? screenshot?.base64) {
45
+ parts.push("## Desktop Screenshot");
46
+ parts.push("A screenshot of the current desktop is attached below.");
47
+ parts.push("");
48
+ }
49
+ parts.push("Review the above context and decide what to do. If there is nothing to do, call the `finish` tool.");
50
+ blocks.push({ type: "text", text: parts.join("\n") });
51
+ // --- Message attachment image blocks ---
52
+ for (const msg of messages) {
53
+ for (const att of msg.attachments) {
54
+ if (att.type === "image" && att.url) {
55
+ blocks.push({
56
+ type: "image",
57
+ source: { type: "url", url: att.url },
58
+ });
59
+ }
60
+ }
61
+ }
62
+ // --- Screenshot image block ---
63
+ if (screenshot?.url) {
64
+ blocks.push({
65
+ type: "image",
66
+ source: { type: "url", url: screenshot.url },
67
+ });
68
+ }
69
+ else if (screenshot?.base64) {
70
+ blocks.push({
71
+ type: "image",
72
+ source: {
73
+ type: "base64",
74
+ media_type: "image/png",
75
+ data: screenshot.base64,
76
+ },
77
+ });
78
+ }
79
+ return blocks;
80
+ }
81
+ /**
82
+ * Build context for an interrupt message — a new user message that arrives
83
+ * while the agent is already processing. Lighter than a full wake context
84
+ * (no screenshot or calendar).
85
+ */
86
+ export function buildInterruptContext(options) {
87
+ const { messages } = options;
88
+ const now = new Date().toLocaleString();
89
+ const blocks = [];
90
+ const parts = [];
91
+ parts.push(`[Interrupt] New message(s) received at ${now} while you are working.`);
92
+ parts.push("");
93
+ for (const msg of messages) {
94
+ parts.push(`- [${msg.channel}] from ${msg.sender} at ${msg.timestamp}: ${msg.text}`);
95
+ if (msg.attachments.length > 0) {
96
+ for (const att of msg.attachments) {
97
+ const location = att.url ?? att.localPath ?? "(embedded)";
98
+ parts.push(` Attachment [${att.type}]: ${location}${att.filename ? ` (${att.filename})` : ""}`);
99
+ }
100
+ }
101
+ }
102
+ parts.push("");
103
+ parts.push("Decide whether to acknowledge, switch tasks, or continue your current work.");
104
+ blocks.push({ type: "text", text: parts.join("\n") });
105
+ // Attach images from the new messages
106
+ for (const msg of messages) {
107
+ for (const att of msg.attachments) {
108
+ if (att.type === "image" && att.url) {
109
+ blocks.push({
110
+ type: "image",
111
+ source: { type: "url", url: att.url },
112
+ });
113
+ }
114
+ }
115
+ }
116
+ return blocks;
117
+ }
118
+ /**
119
+ * Build a fresh context after compaction. Returns multimodal blocks.
120
+ */
121
+ export function buildPostCompactionContext(options) {
122
+ const { config, calendarSummary, pendingMessageCount } = options;
123
+ const now = new Date().toLocaleString();
124
+ return [
125
+ {
126
+ type: "text",
127
+ text: `[Post-Compaction State Refresh] time=${now}
128
+
129
+ Your identity: ${config.agentName} <${config.gmail}>
130
+ Platform: ${process.platform}
131
+
132
+ ## Calendar
133
+ ${calendarSummary || "No upcoming events."}
134
+
135
+ ## Message Queue
136
+ ${pendingMessageCount > 0 ? `${pendingMessageCount} message(s) pending in queue.` : "No pending messages."}
137
+
138
+ Continue from where you left off based on the compaction summary above.`,
139
+ },
140
+ ];
141
+ }
142
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/agent/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { VisionClawConfig } from "../config/types.js";
2
+ import { MessageQueue } from "../channels/queue.js";
3
+ import { ChannelManager } from "../channels/manager.js";
4
+ /** Shared state visible to tools (e.g. notify_user needs channel access) */
5
+ export interface AgentState {
6
+ config: VisionClawConfig;
7
+ channelManager: ChannelManager;
8
+ messageQueue: MessageQueue;
9
+ busy: boolean;
10
+ lastMessageChannel?: string;
11
+ lastMessageSender?: string;
12
+ }
13
+ export declare function getAgentState(): AgentState;
14
+ /**
15
+ * Main agent loop. This is the entry point that runs forever.
16
+ */
17
+ export declare function startAgentLoop(config: VisionClawConfig): Promise<void>;
18
+ //# sourceMappingURL=loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAMxD,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAKD,wBAAgB,aAAa,IAAI,UAAU,CAK1C;AAmBD;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,IAAI,CAAC,CAiWf"}
@@ -0,0 +1,323 @@
1
+ import { buildSystemPrompt } from "./system-prompt.js";
2
+ import { AgentSession } from "./session.js";
3
+ import { buildWakeContext, buildInterruptContext } from "./context.js";
4
+ import { createToolServer } from "../tools/index.js";
5
+ import { MessageQueue } from "../channels/queue.js";
6
+ import { ChannelManager } from "../channels/manager.js";
7
+ import { takeScreenshot } from "../tools/screenshot.js";
8
+ import { getCalendarSummary } from "../calendar/scheduler.js";
9
+ import { initScreenshotUploader, uploadScreenshot } from "../files.js";
10
+ import { logger } from "../logger.js";
11
+ // Module-level state so tools can access it
12
+ let agentState = null;
13
+ export function getAgentState() {
14
+ if (!agentState) {
15
+ throw new Error("Agent state not initialized");
16
+ }
17
+ return agentState;
18
+ }
19
+ /**
20
+ * Log an SDK result message (success or error).
21
+ */
22
+ function logResult(msg) {
23
+ const cost = msg.total_cost_usd.toFixed(4);
24
+ logger.result(msg.num_turns, msg.usage.input_tokens, msg.usage.output_tokens, cost);
25
+ if (msg.is_error) {
26
+ const errors = "errors" in msg ? msg.errors.join("; ") : msg.subtype;
27
+ logger.err(`Agent ended with error (${msg.subtype}): ${errors}`);
28
+ }
29
+ }
30
+ /**
31
+ * Main agent loop. This is the entry point that runs forever.
32
+ */
33
+ export async function startAgentLoop(config) {
34
+ // --- Initialize components ---
35
+ const messageQueue = new MessageQueue();
36
+ const channelManager = new ChannelManager(config, messageQueue);
37
+ agentState = {
38
+ config,
39
+ channelManager,
40
+ messageQueue,
41
+ busy: false,
42
+ };
43
+ // Initialize S3 client for screenshot uploads
44
+ initScreenshotUploader();
45
+ // Create the in-process MCP tool server
46
+ const toolServer = createToolServer();
47
+ // Build system prompt
48
+ const systemPrompt = buildSystemPrompt(config);
49
+ // Create the agent session (uses V1 query API with full tool support)
50
+ const session = new AgentSession(config, systemPrompt, {
51
+ visionclaw: toolServer,
52
+ playwright: {
53
+ command: "npx",
54
+ args: [
55
+ "@playwright/mcp@latest",
56
+ "--browser",
57
+ process.platform === "darwin" ? "chrome" : "chromium",
58
+ ],
59
+ },
60
+ });
61
+ // Start all enabled channels
62
+ await channelManager.startAll();
63
+ // Listen for incoming messages to wake the agent or inject interrupts
64
+ let wakeResolver = null;
65
+ channelManager.on("message", () => {
66
+ // If agent is sleeping, wake it up
67
+ if (wakeResolver !== null) {
68
+ wakeResolver();
69
+ wakeResolver = null;
70
+ return;
71
+ }
72
+ // If agent is busy, inject interrupt messages into the active session
73
+ if (agentState?.busy) {
74
+ const interruptMessages = [];
75
+ while (!messageQueue.isEmpty()) {
76
+ const msg = messageQueue.dequeue();
77
+ if (msg)
78
+ interruptMessages.push(msg);
79
+ }
80
+ if (interruptMessages.length > 0) {
81
+ // Update last message tracking
82
+ const last = interruptMessages[interruptMessages.length - 1];
83
+ agentState.lastMessageChannel = last.channel;
84
+ agentState.lastMessageSender = last.sender;
85
+ // Log the interrupt
86
+ for (const msg of interruptMessages) {
87
+ logger.log("info", "incoming", `[interrupt] [${msg.channel}] ${msg.sender}: ${msg.text}`, {
88
+ channel: msg.channel,
89
+ sender: msg.sender,
90
+ text: msg.text,
91
+ id: msg.id,
92
+ interrupt: true,
93
+ });
94
+ }
95
+ // Build interrupt context and inject into session
96
+ const interruptBlocks = buildInterruptContext({ messages: interruptMessages });
97
+ const interruptContent = interruptBlocks;
98
+ session.injectMessage(interruptContent);
99
+ }
100
+ }
101
+ });
102
+ logger.system("Agent loop started. Waiting for messages or heartbeat...");
103
+ // --- Main loop ---
104
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop
105
+ while (true) {
106
+ agentState.busy = false;
107
+ // Sleep until heartbeat or new message
108
+ await new Promise((resolve) => {
109
+ wakeResolver = resolve;
110
+ const timer = setTimeout(() => {
111
+ if (wakeResolver === resolve) {
112
+ wakeResolver = null;
113
+ resolve();
114
+ }
115
+ }, config.heartbeatIntervalMs);
116
+ if (!messageQueue.isEmpty()) {
117
+ clearTimeout(timer);
118
+ wakeResolver = null;
119
+ resolve();
120
+ }
121
+ });
122
+ agentState.busy = true;
123
+ // Determine trigger
124
+ const hasMessages = !messageQueue.isEmpty();
125
+ const trigger = hasMessages ? "message" : "heartbeat";
126
+ // Dequeue all pending messages
127
+ const messages = [];
128
+ while (!messageQueue.isEmpty()) {
129
+ const msg = messageQueue.dequeue();
130
+ if (msg)
131
+ messages.push(msg);
132
+ }
133
+ // Track last message for notify_user
134
+ if (messages.length > 0) {
135
+ const last = messages[messages.length - 1];
136
+ agentState.lastMessageChannel = last.channel;
137
+ agentState.lastMessageSender = last.sender;
138
+ }
139
+ // Log the wake event
140
+ logger.wake(trigger, messages.length);
141
+ // Log each incoming message
142
+ for (const msg of messages) {
143
+ logger.log("info", "incoming", `[${msg.channel}] ${msg.sender}: ${msg.text}`, {
144
+ channel: msg.channel,
145
+ sender: msg.sender,
146
+ text: msg.text,
147
+ id: msg.id,
148
+ });
149
+ }
150
+ // --- Gather context ---
151
+ // Gather screenshot and calendar in parallel
152
+ const [screenshotResult, calendarResult] = await Promise.allSettled([
153
+ (async () => {
154
+ const t0 = Date.now();
155
+ const base64 = await takeScreenshot();
156
+ logger.debug(`Screenshot captured in ${Date.now() - t0}ms`);
157
+ const t1 = Date.now();
158
+ const url = await uploadScreenshot(base64);
159
+ logger.debug(`Screenshot uploaded in ${Date.now() - t1}ms`);
160
+ return url ? { url } : { base64 };
161
+ })(),
162
+ (async () => {
163
+ const t0 = Date.now();
164
+ const summary = await getCalendarSummary(config);
165
+ logger.debug(`Calendar fetched in ${Date.now() - t0}ms`);
166
+ return summary;
167
+ })(),
168
+ ]);
169
+ const screenshot = screenshotResult.status === "fulfilled" ? screenshotResult.value : undefined;
170
+ if (screenshotResult.status === "rejected") {
171
+ logger.warn(`Screenshot failed: ${screenshotResult.reason instanceof Error ? screenshotResult.reason.message : String(screenshotResult.reason)}`);
172
+ }
173
+ const calendarSummary = calendarResult.status === "fulfilled" ? calendarResult.value : "";
174
+ if (calendarResult.status === "rejected") {
175
+ logger.warn(`Calendar fetch failed: ${calendarResult.reason instanceof Error ? calendarResult.reason.message : String(calendarResult.reason)}`);
176
+ }
177
+ // Build multimodal content blocks
178
+ const contentBlocks = buildWakeContext({
179
+ config,
180
+ trigger,
181
+ messages,
182
+ calendarSummary,
183
+ screenshot,
184
+ });
185
+ // Map tool_use_id -> tool name for correlating results (per wake cycle)
186
+ const toolNameById = new Map();
187
+ // Log the multimodal message being sent (text + image summary)
188
+ logger.sendMultimodal(contentBlocks);
189
+ try {
190
+ // Send the multimodal content and stream the response.
191
+ // Cast through unknown because our ContentBlock type may not
192
+ // exactly match MessageParam["content"].
193
+ const messageContent = contentBlocks;
194
+ const queryStream = session.sendAndStream(messageContent);
195
+ // Process the response stream.
196
+ // We don't break on finish -- breaking calls generator.return() which
197
+ // can hang waiting for the SDK child process to exit. Instead we
198
+ // drain the stream naturally and skip logging after finish.
199
+ let finishCalled = false;
200
+ for await (const msg of queryStream) {
201
+ // Always capture session ID
202
+ if ("session_id" in msg && msg.session_id) {
203
+ session.captureSessionId(msg.session_id);
204
+ }
205
+ // After finish, only log the result message and skip everything else
206
+ if (finishCalled) {
207
+ if (msg.type === "result") {
208
+ logResult(msg);
209
+ }
210
+ continue;
211
+ }
212
+ // Handle different message types
213
+ switch (msg.type) {
214
+ case "user": {
215
+ // In Claude Agent SDK v2, tool execution results are surfaced back
216
+ // into the stream as "user" messages (not as a separate tool_result
217
+ // message type). Capture and log them for OBS.
218
+ const um = msg;
219
+ // Tool results in this SDK are encoded as user message content blocks:
220
+ // [{ type: "tool_result", tool_use_id, content: [...] }]
221
+ const blocks = um.message?.content;
222
+ if (Array.isArray(blocks)) {
223
+ for (const b of blocks) {
224
+ const block = b;
225
+ if (block.type !== "tool_result" || !block.tool_use_id)
226
+ continue;
227
+ const out = block.content;
228
+ const summary = out === undefined
229
+ ? ""
230
+ : typeof out === "string"
231
+ ? out
232
+ : JSON.stringify(out);
233
+ const resolvedName = toolNameById.get(block.tool_use_id) ?? "tool";
234
+ logger.toolResult(resolvedName, summary, {
235
+ tool_use_id: block.tool_use_id,
236
+ is_error: block.is_error,
237
+ source: "sdk_user_message_block",
238
+ });
239
+ }
240
+ }
241
+ break;
242
+ }
243
+ case "assistant": {
244
+ const assistantMsg = msg;
245
+ const content = assistantMsg.message.content;
246
+ for (const block of content) {
247
+ switch (block.type) {
248
+ case "text":
249
+ if (block.text) {
250
+ logger.assistant(block.text);
251
+ }
252
+ break;
253
+ case "thinking":
254
+ logger.debug(`[thinking] ${block.thinking.substring(0, 200)}${block.thinking.length > 200 ? "..." : ""}`);
255
+ break;
256
+ case "tool_use":
257
+ {
258
+ const toolUseId = block.id ??
259
+ block.tool_use_id;
260
+ if (toolUseId) {
261
+ toolNameById.set(toolUseId, block.name);
262
+ }
263
+ logger.toolCall(block.name, block.input, {
264
+ tool_use_id: toolUseId,
265
+ });
266
+ }
267
+ if (block.name === "finish" || block.name === "mcp__visionclaw__finish") {
268
+ finishCalled = true;
269
+ session.closeInput();
270
+ }
271
+ break;
272
+ default:
273
+ // Log other block types (server_tool_use, mcp_tool_use, etc.)
274
+ logger.debug(`Assistant block: ${block.type}`);
275
+ break;
276
+ }
277
+ }
278
+ // Log assistant-level errors
279
+ if (assistantMsg.error) {
280
+ logger.err(`Assistant error: ${assistantMsg.error}`);
281
+ }
282
+ break;
283
+ }
284
+ case "result":
285
+ logResult(msg);
286
+ break;
287
+ case "system":
288
+ logger.debug(`System message: ${JSON.stringify(msg)}`, {
289
+ subtype: msg.subtype,
290
+ });
291
+ break;
292
+ default:
293
+ // stream_event, status, task_notification, etc. — skip silently
294
+ // Also handle tool_result even if the SDK type union doesn't include it.
295
+ if (msg.type === "tool_result") {
296
+ const tr = msg;
297
+ const name = tr.name ?? "tool";
298
+ const summary = tr.content === undefined
299
+ ? ""
300
+ : typeof tr.content === "string"
301
+ ? tr.content
302
+ : JSON.stringify(tr.content);
303
+ logger.toolResult(name, summary, {
304
+ tool_use_id: tr.tool_use_id,
305
+ is_error: tr.is_error,
306
+ });
307
+ }
308
+ break;
309
+ }
310
+ }
311
+ // Ensure generator is closed after stream ends
312
+ session.closeInput();
313
+ }
314
+ catch (err) {
315
+ // Ensure generator is closed on error too
316
+ session.closeInput();
317
+ logger.err(`Agent loop error: ${err instanceof Error ? err.message : String(err)}`, { stack: err instanceof Error ? err.stack : undefined });
318
+ // Wait a bit before retrying to avoid tight error loops
319
+ await new Promise((r) => setTimeout(r, 5000));
320
+ }
321
+ }
322
+ }
323
+ //# sourceMappingURL=loop.js.map