assistme 0.0.1 → 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.
- package/.prettierrc +7 -0
- package/PLAN.md +135 -0
- package/README.md +57 -1
- package/dist/chunk-3TP3YO56.js +410 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3432 -0
- package/dist/supabase-BQRPXXNA.js +44 -0
- package/eslint.config.js +20 -0
- package/package.json +52 -7
- package/skills/form-filling/SKILL.md +35 -0
- package/skills/price-comparison/SKILL.md +39 -0
- package/skills/web-research/SKILL.md +39 -0
- package/src/agent/memory-extractor.ts +128 -0
- package/src/agent/memory.test.ts +179 -0
- package/src/agent/memory.ts +266 -0
- package/src/agent/processor.test.ts +246 -0
- package/src/agent/processor.ts +586 -0
- package/src/agent/scheduler.test.ts +67 -0
- package/src/agent/scheduler.ts +286 -0
- package/src/agent/session.test.ts +183 -0
- package/src/agent/session.ts +269 -0
- package/src/agent/skill-extractor.ts +252 -0
- package/src/agent/skills.test.ts +149 -0
- package/src/agent/skills.ts +716 -0
- package/src/db/supabase.test.ts +285 -0
- package/src/db/supabase.ts +507 -0
- package/src/index.ts +835 -0
- package/src/tools/browser.ts +551 -0
- package/src/tools/filesystem.test.ts +96 -0
- package/src/tools/filesystem.ts +142 -0
- package/src/tools/index.ts +360 -0
- package/src/tools/shell.test.ts +79 -0
- package/src/tools/shell.ts +69 -0
- package/src/tools/web.ts +73 -0
- package/src/utils/config.test.ts +102 -0
- package/src/utils/config.ts +56 -0
- package/src/utils/logger.ts +89 -0
- package/src/utils/rate-limiter.test.ts +98 -0
- package/src/utils/rate-limiter.ts +112 -0
- package/src/utils/retry.test.ts +213 -0
- package/src/utils/retry.ts +182 -0
- package/src/utils/validation.test.ts +153 -0
- package/src/utils/validation.ts +101 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +10 -0
package/.prettierrc
ADDED
package/PLAN.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# AssistMe CLI Agent - Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Vision
|
|
4
|
+
Build a CLI tool under `packages/assistme` based on Claude Agent SDK that:
|
|
5
|
+
- Runs as a local daemon, listening for tasks from the web UI
|
|
6
|
+
- Processes messages using Claude Agent SDK with full agentic capabilities (tool use, multi-turn)
|
|
7
|
+
- Streams real-time events (thinking, tool calls, text output) back to the web UI
|
|
8
|
+
- Surpasses OpenClaw by offering: streaming UI, visible tool execution, workspace awareness, and tight web integration
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
|
|
14
|
+
│ Web UI │────▶│ Supabase DB │◀────│ CLI Agent │
|
|
15
|
+
│ (web/) │◀────│ │────▶│ (packages/ │
|
|
16
|
+
│ │ │ agent_tasks │ │ assistme/) │
|
|
17
|
+
│ Send message│ │ task_events │ │ │
|
|
18
|
+
│ See results │ │ sessions │ │ Claude Agent SDK │
|
|
19
|
+
└─────────────┘ └──────────────┘ └──────────────────┘
|
|
20
|
+
|
|
21
|
+
Flow:
|
|
22
|
+
1. User sends message on web → inserts into agent_tasks (status: pending)
|
|
23
|
+
2. CLI agent polls/subscribes → picks up task (status: running)
|
|
24
|
+
3. Claude Agent SDK processes → streams events to agent_task_events
|
|
25
|
+
4. Web UI polls task_events → renders streaming output in real-time
|
|
26
|
+
5. Task completes → status: completed, final response stored
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Database Design
|
|
30
|
+
|
|
31
|
+
### New Tables Needed
|
|
32
|
+
|
|
33
|
+
#### 1. `agent_sessions` - Track running CLI agent instances
|
|
34
|
+
```sql
|
|
35
|
+
- id: UUID PK
|
|
36
|
+
- user_id: UUID FK → user_profiles
|
|
37
|
+
- session_name: VARCHAR(100) -- e.g., "My Workspace"
|
|
38
|
+
- status: VARCHAR(20) -- 'online', 'offline', 'busy'
|
|
39
|
+
- workspace_path: TEXT -- local filesystem path the agent operates in
|
|
40
|
+
- agent_version: VARCHAR(50)
|
|
41
|
+
- started_at: TIMESTAMPTZ
|
|
42
|
+
- last_heartbeat_at: TIMESTAMPTZ -- CLI sends heartbeat every 30s
|
|
43
|
+
- ended_at: TIMESTAMPTZ
|
|
44
|
+
- metadata: JSONB -- capabilities, config, etc.
|
|
45
|
+
- created_at: TIMESTAMPTZ
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### 2. `agent_tasks` - Commands/tasks from web to CLI
|
|
49
|
+
```sql
|
|
50
|
+
- id: UUID PK
|
|
51
|
+
- session_id: UUID FK → agent_sessions
|
|
52
|
+
- user_id: UUID FK → user_profiles
|
|
53
|
+
- conversation_id: UUID FK → conversations (nullable, for context threading)
|
|
54
|
+
- prompt: TEXT -- the user's message/command
|
|
55
|
+
- status: VARCHAR(20) -- 'pending', 'running', 'completed', 'failed', 'cancelled'
|
|
56
|
+
- result_summary: TEXT -- final text response
|
|
57
|
+
- error_message: TEXT
|
|
58
|
+
- token_usage: JSONB -- {prompt_tokens, completion_tokens, total_tokens}
|
|
59
|
+
- started_at: TIMESTAMPTZ
|
|
60
|
+
- completed_at: TIMESTAMPTZ
|
|
61
|
+
- created_at: TIMESTAMPTZ
|
|
62
|
+
- updated_at: TIMESTAMPTZ
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### 3. `agent_task_events` - Streaming events from CLI
|
|
66
|
+
```sql
|
|
67
|
+
- id: UUID PK
|
|
68
|
+
- task_id: UUID FK → agent_tasks
|
|
69
|
+
- event_type: VARCHAR(30) -- 'text_delta', 'tool_use_start', 'tool_use_input', 'tool_result', 'thinking', 'error', 'status_change'
|
|
70
|
+
- event_data: JSONB -- flexible payload per event type
|
|
71
|
+
- sequence_number: INTEGER -- ordering within a task
|
|
72
|
+
- created_at: TIMESTAMPTZ
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Existing Tables - No Changes Needed
|
|
76
|
+
- `conversations` - Can be reused for threading agent task history
|
|
77
|
+
- `chat_messages` - Not used directly (agent_tasks + agent_task_events replace this)
|
|
78
|
+
- `user_profiles` - No changes needed
|
|
79
|
+
- `user_credits` / `llm_token_usage` - Reused for billing
|
|
80
|
+
|
|
81
|
+
## Package Structure
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
packages/assistme/
|
|
85
|
+
├── package.json # CLI package with bin entry
|
|
86
|
+
├── tsconfig.json
|
|
87
|
+
├── src/
|
|
88
|
+
│ ├── index.ts # CLI entry point (commander.js)
|
|
89
|
+
│ ├── agent/
|
|
90
|
+
│ │ ├── session.ts # Session management (heartbeat, lifecycle)
|
|
91
|
+
│ │ └── processor.ts # Task processor using Claude Agent SDK
|
|
92
|
+
│ ├── db/
|
|
93
|
+
│ │ └── supabase.ts # Supabase client for task polling & event writing
|
|
94
|
+
│ ├── tools/
|
|
95
|
+
│ │ ├── index.ts # MCP tool registry
|
|
96
|
+
│ │ ├── filesystem.ts # File read/write/search tools
|
|
97
|
+
│ │ ├── shell.ts # Shell command execution
|
|
98
|
+
│ │ └── web.ts # Web search/fetch tools
|
|
99
|
+
│ └── utils/
|
|
100
|
+
│ ├── config.ts # Config management (.assistme.json)
|
|
101
|
+
│ └── logger.ts # Structured logging with levels
|
|
102
|
+
├── .assistme.json.example
|
|
103
|
+
└── README.md
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Web UI Addition (web/src/)
|
|
107
|
+
|
|
108
|
+
New page: `/agent` - Agent chat interface
|
|
109
|
+
- Message input with send button
|
|
110
|
+
- Streaming output display (text, tool calls, results)
|
|
111
|
+
- Session status indicator (online/offline)
|
|
112
|
+
- Task history sidebar
|
|
113
|
+
|
|
114
|
+
## Implementation Steps
|
|
115
|
+
|
|
116
|
+
### Phase 1: Database (Migration)
|
|
117
|
+
1. Create migration for agent_sessions, agent_tasks, agent_task_events
|
|
118
|
+
2. Add RLS policies and indexes
|
|
119
|
+
|
|
120
|
+
### Phase 2: CLI Package
|
|
121
|
+
1. Set up package.json with dependencies
|
|
122
|
+
2. Implement Supabase client for DB communication
|
|
123
|
+
3. Implement session management (start/stop/heartbeat)
|
|
124
|
+
4. Implement task processor with Claude Agent SDK
|
|
125
|
+
5. Implement MCP tools (filesystem, shell, web)
|
|
126
|
+
6. Wire up CLI commands (assistme start, assistme login, assistme status)
|
|
127
|
+
|
|
128
|
+
### Phase 3: Web UI
|
|
129
|
+
1. Create /agent page with chat interface
|
|
130
|
+
2. Implement task submission (insert into agent_tasks)
|
|
131
|
+
3. Implement event polling (poll agent_task_events)
|
|
132
|
+
4. Render streaming events (text, tool use, thinking)
|
|
133
|
+
|
|
134
|
+
### Phase 4: Edge Function
|
|
135
|
+
1. Create agent-submit-task edge function for mobile clients
|
package/README.md
CHANGED
|
@@ -1 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
# @assistme/cli
|
|
2
|
+
|
|
3
|
+
AI-powered CLI agent that connects to the AssistMe web UI. Send messages from the web, and the CLI agent processes them locally using Claude with full tool access (file operations, shell commands, web search).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
cd packages/assistme && npm install
|
|
10
|
+
|
|
11
|
+
# Build
|
|
12
|
+
npm run build
|
|
13
|
+
|
|
14
|
+
# Configure
|
|
15
|
+
assistme config set supabaseUrl https://your-project.supabase.co
|
|
16
|
+
assistme config set supabaseAnonKey your-anon-key
|
|
17
|
+
assistme config set anthropicApiKey sk-ant-...
|
|
18
|
+
|
|
19
|
+
# Login
|
|
20
|
+
assistme login -e your@email.com -p your-password
|
|
21
|
+
|
|
22
|
+
# Start the agent (in your project directory)
|
|
23
|
+
assistme start
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
| Command | Description |
|
|
29
|
+
|---------|-------------|
|
|
30
|
+
| `assistme start` | Start the agent daemon and listen for tasks |
|
|
31
|
+
| `assistme login` | Authenticate with your AssistMe account |
|
|
32
|
+
| `assistme status` | Check active sessions |
|
|
33
|
+
| `assistme config set <key> <value>` | Set configuration |
|
|
34
|
+
| `assistme config show` | Show current configuration |
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Web UI → Supabase DB (agent_tasks) → CLI Agent → Claude API → Tool Execution → Events back to DB → Web UI
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The CLI agent:
|
|
43
|
+
1. Registers a session in `agent_sessions`
|
|
44
|
+
2. Polls `agent_tasks` for new pending tasks
|
|
45
|
+
3. Processes each task using Claude with tool access
|
|
46
|
+
4. Streams events to `agent_task_events` (text, tool calls, results)
|
|
47
|
+
5. Web UI polls events for real-time display
|
|
48
|
+
|
|
49
|
+
## Tools Available
|
|
50
|
+
|
|
51
|
+
- **read_file** - Read file contents with line numbers
|
|
52
|
+
- **write_file** - Create or overwrite files
|
|
53
|
+
- **search_files** - Glob pattern file search
|
|
54
|
+
- **search_content** - Regex content search across files
|
|
55
|
+
- **list_directory** - List directory contents
|
|
56
|
+
- **execute_command** - Run shell commands
|
|
57
|
+
- **web_fetch** - Fetch web page content
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// src/db/supabase.ts
|
|
2
|
+
import { createClient } from "@supabase/supabase-js";
|
|
3
|
+
|
|
4
|
+
// src/utils/config.ts
|
|
5
|
+
import Conf from "conf";
|
|
6
|
+
import { resolve } from "path";
|
|
7
|
+
var CONFIG_DEFAULTS = {
|
|
8
|
+
sessionName: "Default",
|
|
9
|
+
model: "claude-sonnet-4-20250514",
|
|
10
|
+
maxTurns: 50
|
|
11
|
+
};
|
|
12
|
+
var config = new Conf({
|
|
13
|
+
projectName: "assistme",
|
|
14
|
+
defaults: CONFIG_DEFAULTS
|
|
15
|
+
});
|
|
16
|
+
function getConfig() {
|
|
17
|
+
const supabaseUrl = process.env.SUPABASE_URL || config.get("supabaseUrl") || "";
|
|
18
|
+
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY || config.get("supabaseAnonKey") || "";
|
|
19
|
+
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || config.get("anthropicApiKey") || "";
|
|
20
|
+
const workspacePath = config.get("workspacePath") || process.cwd();
|
|
21
|
+
return {
|
|
22
|
+
supabaseUrl,
|
|
23
|
+
supabaseAnonKey,
|
|
24
|
+
anthropicApiKey,
|
|
25
|
+
workspacePath: resolve(workspacePath),
|
|
26
|
+
sessionName: config.get("sessionName") || "Default",
|
|
27
|
+
model: config.get("model") || "claude-sonnet-4-20250514",
|
|
28
|
+
maxTurns: config.get("maxTurns") || 50
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function setConfig(key, value) {
|
|
32
|
+
config.set(key, value);
|
|
33
|
+
}
|
|
34
|
+
function clearConfig() {
|
|
35
|
+
config.clear();
|
|
36
|
+
}
|
|
37
|
+
function getConfigPath() {
|
|
38
|
+
return config.path;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/utils/logger.ts
|
|
42
|
+
import chalk from "chalk";
|
|
43
|
+
import { randomUUID } from "crypto";
|
|
44
|
+
var currentLevel = "info";
|
|
45
|
+
var currentCorrelationId = null;
|
|
46
|
+
var LEVEL_ORDER = {
|
|
47
|
+
debug: 0,
|
|
48
|
+
info: 1,
|
|
49
|
+
warn: 2,
|
|
50
|
+
error: 3
|
|
51
|
+
};
|
|
52
|
+
function setLogLevel(level) {
|
|
53
|
+
currentLevel = level;
|
|
54
|
+
}
|
|
55
|
+
function setCorrelationId(id) {
|
|
56
|
+
currentCorrelationId = id;
|
|
57
|
+
}
|
|
58
|
+
function newCorrelationId() {
|
|
59
|
+
const id = randomUUID().slice(0, 8);
|
|
60
|
+
currentCorrelationId = id;
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
function shouldLog(level) {
|
|
64
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];
|
|
65
|
+
}
|
|
66
|
+
function timestamp() {
|
|
67
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
68
|
+
}
|
|
69
|
+
function prefix() {
|
|
70
|
+
const ts = timestamp();
|
|
71
|
+
return currentCorrelationId ? `${ts} ${currentCorrelationId}` : ts;
|
|
72
|
+
}
|
|
73
|
+
var log = {
|
|
74
|
+
debug(msg, ...args) {
|
|
75
|
+
if (shouldLog("debug")) {
|
|
76
|
+
console.log(chalk.gray(`[${prefix()}] DEBUG`), msg, ...args);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
info(msg, ...args) {
|
|
80
|
+
if (shouldLog("info")) {
|
|
81
|
+
console.log(chalk.blue(`[${prefix()}]`), msg, ...args);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
success(msg, ...args) {
|
|
85
|
+
if (shouldLog("info")) {
|
|
86
|
+
console.log(chalk.green(`[${prefix()}] \u2713`), msg, ...args);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
warn(msg, ...args) {
|
|
90
|
+
if (shouldLog("warn")) {
|
|
91
|
+
console.log(chalk.yellow(`[${prefix()}] WARN`), msg, ...args);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
error(msg, ...args) {
|
|
95
|
+
if (shouldLog("error")) {
|
|
96
|
+
console.error(chalk.red(`[${prefix()}] ERROR`), msg, ...args);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
agent(msg) {
|
|
100
|
+
console.log(chalk.cyan(" \u25B8"), msg);
|
|
101
|
+
},
|
|
102
|
+
tool(name, msg) {
|
|
103
|
+
console.log(chalk.magenta(` \u26A1 ${name}:`), msg);
|
|
104
|
+
},
|
|
105
|
+
result(msg) {
|
|
106
|
+
console.log(chalk.green(" \u2190"), msg);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/db/supabase.ts
|
|
111
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
112
|
+
import { join } from "path";
|
|
113
|
+
import { homedir } from "os";
|
|
114
|
+
var CLI_AGENT_ID = "00000000-0000-0000-0000-000000000001";
|
|
115
|
+
var DAYBOX_AGENT_ID = "00000000-0000-0000-0000-000000000002";
|
|
116
|
+
var AUTH_DIR = join(homedir(), ".config", "assistme");
|
|
117
|
+
var AUTH_FILE = join(AUTH_DIR, "auth.json");
|
|
118
|
+
function ensureAuthDir() {
|
|
119
|
+
if (!existsSync(AUTH_DIR)) {
|
|
120
|
+
mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function readAuthStore() {
|
|
124
|
+
try {
|
|
125
|
+
if (existsSync(AUTH_FILE)) {
|
|
126
|
+
return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
function writeAuthStore(data) {
|
|
133
|
+
ensureAuthDir();
|
|
134
|
+
writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
135
|
+
}
|
|
136
|
+
var fileStorage = {
|
|
137
|
+
getItem(key) {
|
|
138
|
+
const store = readAuthStore();
|
|
139
|
+
return store[key] ?? null;
|
|
140
|
+
},
|
|
141
|
+
setItem(key, value) {
|
|
142
|
+
const store = readAuthStore();
|
|
143
|
+
store[key] = value;
|
|
144
|
+
writeAuthStore(store);
|
|
145
|
+
},
|
|
146
|
+
removeItem(key) {
|
|
147
|
+
const store = readAuthStore();
|
|
148
|
+
delete store[key];
|
|
149
|
+
writeAuthStore(store);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var supabase = null;
|
|
153
|
+
function getSupabase() {
|
|
154
|
+
if (!supabase) {
|
|
155
|
+
const config2 = getConfig();
|
|
156
|
+
if (!config2.supabaseUrl || !config2.supabaseAnonKey) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"Supabase not configured. Run `assistme config set supabaseUrl <url>` first."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
supabase = createClient(config2.supabaseUrl, config2.supabaseAnonKey, {
|
|
162
|
+
auth: {
|
|
163
|
+
storage: fileStorage,
|
|
164
|
+
autoRefreshToken: true,
|
|
165
|
+
persistSession: true
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return supabase;
|
|
170
|
+
}
|
|
171
|
+
async function loginWithToken(cliToken) {
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
const json = Buffer.from(cliToken, "base64").toString("utf-8");
|
|
175
|
+
parsed = JSON.parse(json);
|
|
176
|
+
if (!parsed.access_token || !parsed.refresh_token) {
|
|
177
|
+
throw new Error("Missing token fields");
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
throw new Error(
|
|
181
|
+
"Invalid token format. Please copy the full token from the web page."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const sb = getSupabase();
|
|
185
|
+
const { data, error } = await sb.auth.setSession({
|
|
186
|
+
access_token: parsed.access_token,
|
|
187
|
+
refresh_token: parsed.refresh_token
|
|
188
|
+
});
|
|
189
|
+
if (error) throw new Error(`Login failed: ${error.message}`);
|
|
190
|
+
if (!data.user) throw new Error("Login failed: no user returned");
|
|
191
|
+
return data.user.id;
|
|
192
|
+
}
|
|
193
|
+
async function getSession() {
|
|
194
|
+
const sb = getSupabase();
|
|
195
|
+
const { data } = await sb.auth.getSession();
|
|
196
|
+
return data.session;
|
|
197
|
+
}
|
|
198
|
+
async function getCurrentUserId() {
|
|
199
|
+
const sb = getSupabase();
|
|
200
|
+
const { data, error } = await sb.auth.getUser();
|
|
201
|
+
if (error || !data.user) {
|
|
202
|
+
throw new Error("Not authenticated. Run `assistme login`.");
|
|
203
|
+
}
|
|
204
|
+
return data.user.id;
|
|
205
|
+
}
|
|
206
|
+
async function logout() {
|
|
207
|
+
const sb = getSupabase();
|
|
208
|
+
await sb.auth.signOut();
|
|
209
|
+
try {
|
|
210
|
+
writeAuthStore({});
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function createSession(userId, sessionName, workspacePath, version) {
|
|
215
|
+
const sb = getSupabase();
|
|
216
|
+
const { data, error } = await sb.from("agent_sessions").insert({
|
|
217
|
+
user_id: userId,
|
|
218
|
+
session_name: sessionName,
|
|
219
|
+
status: "online",
|
|
220
|
+
workspace_path: workspacePath,
|
|
221
|
+
agent_version: version,
|
|
222
|
+
metadata: { model: getConfig().model }
|
|
223
|
+
}).select().single();
|
|
224
|
+
if (error) throw new Error(`Failed to create session: ${error.message}`);
|
|
225
|
+
return data;
|
|
226
|
+
}
|
|
227
|
+
async function updateHeartbeat(sessionId) {
|
|
228
|
+
const sb = getSupabase();
|
|
229
|
+
const { error } = await sb.from("agent_sessions").update({ last_heartbeat_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", sessionId);
|
|
230
|
+
if (error) log.warn(`Heartbeat update failed: ${error.message}`);
|
|
231
|
+
}
|
|
232
|
+
async function endSession(sessionId) {
|
|
233
|
+
const sb = getSupabase();
|
|
234
|
+
const { error } = await sb.from("agent_sessions").update({
|
|
235
|
+
status: "offline",
|
|
236
|
+
ended_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
237
|
+
}).eq("id", sessionId);
|
|
238
|
+
if (error) log.error(`Failed to end session: ${error.message}`);
|
|
239
|
+
}
|
|
240
|
+
async function setSessionBusy(sessionId, busy) {
|
|
241
|
+
const sb = getSupabase();
|
|
242
|
+
await sb.from("agent_sessions").update({ status: busy ? "busy" : "online" }).eq("id", sessionId);
|
|
243
|
+
}
|
|
244
|
+
async function getOrCreateCliConversation(userId, sessionId) {
|
|
245
|
+
const sb = getSupabase();
|
|
246
|
+
const { data: existing } = await sb.from("conversations").select("id").eq("agent", "cli").eq("created_by", userId).order("created_at", { ascending: false }).limit(1);
|
|
247
|
+
if (existing && existing.length > 0) {
|
|
248
|
+
return existing[0].id;
|
|
249
|
+
}
|
|
250
|
+
const { data: conv, error: convErr } = await sb.from("conversations").insert({
|
|
251
|
+
conversation_type: "direct",
|
|
252
|
+
agent: "cli",
|
|
253
|
+
created_by: userId
|
|
254
|
+
}).select("id").single();
|
|
255
|
+
if (convErr || !conv) {
|
|
256
|
+
throw new Error(`Failed to create conversation: ${convErr?.message}`);
|
|
257
|
+
}
|
|
258
|
+
await sb.from("conversation_participants").insert([
|
|
259
|
+
{ conversation_id: conv.id, user_id: userId, role: "member" },
|
|
260
|
+
{ conversation_id: conv.id, user_id: CLI_AGENT_ID, role: "member" }
|
|
261
|
+
]);
|
|
262
|
+
return conv.id;
|
|
263
|
+
}
|
|
264
|
+
async function createTask(conversationId, userId, sessionId, prompt) {
|
|
265
|
+
const sb = getSupabase();
|
|
266
|
+
const { data: rpcResult, error: rpcError } = await sb.rpc(
|
|
267
|
+
"create_task_pair",
|
|
268
|
+
{
|
|
269
|
+
p_conversation_id: conversationId,
|
|
270
|
+
p_user_id: userId,
|
|
271
|
+
p_agent_id: CLI_AGENT_ID,
|
|
272
|
+
p_session_id: sessionId,
|
|
273
|
+
p_prompt: prompt
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
if (!rpcError && rpcResult) {
|
|
277
|
+
return { ...rpcResult, prompt };
|
|
278
|
+
}
|
|
279
|
+
if (rpcError) {
|
|
280
|
+
log.debug(`create_task_pair RPC unavailable, using fallback: ${rpcError.message}`);
|
|
281
|
+
}
|
|
282
|
+
const { error: userErr } = await sb.from("conversation_messages").insert({
|
|
283
|
+
conversation_id: conversationId,
|
|
284
|
+
sender_id: userId,
|
|
285
|
+
role: "user",
|
|
286
|
+
content: prompt
|
|
287
|
+
});
|
|
288
|
+
if (userErr) throw new Error(`Failed to create user message: ${userErr.message}`);
|
|
289
|
+
const { data, error } = await sb.from("conversation_messages").insert({
|
|
290
|
+
conversation_id: conversationId,
|
|
291
|
+
sender_id: CLI_AGENT_ID,
|
|
292
|
+
role: "assistant",
|
|
293
|
+
content: "",
|
|
294
|
+
status: "pending",
|
|
295
|
+
session_id: sessionId,
|
|
296
|
+
metadata: { prompt }
|
|
297
|
+
}).select().single();
|
|
298
|
+
if (error) throw new Error(`Failed to create task: ${error.message}`);
|
|
299
|
+
return { ...data, prompt };
|
|
300
|
+
}
|
|
301
|
+
async function pollPendingTasks(sessionId) {
|
|
302
|
+
const sb = getSupabase();
|
|
303
|
+
const { data, error } = await sb.from("conversation_messages").select("*").eq("session_id", sessionId).eq("status", "pending").order("created_at", { ascending: true }).limit(1);
|
|
304
|
+
if (error) {
|
|
305
|
+
log.warn(`Task poll failed: ${error.message}`);
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
return (data || []).map((row) => ({
|
|
309
|
+
...row,
|
|
310
|
+
prompt: row.metadata?.prompt || row.content || ""
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
async function claimTask(messageId) {
|
|
314
|
+
const sb = getSupabase();
|
|
315
|
+
const { error } = await sb.from("conversation_messages").update({
|
|
316
|
+
status: "running",
|
|
317
|
+
metadata: { started_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
318
|
+
}).eq("id", messageId).eq("status", "pending");
|
|
319
|
+
if (error) throw new Error(`Failed to claim task: ${error.message}`);
|
|
320
|
+
}
|
|
321
|
+
async function completeTask(messageId, resultSummary, tokenUsage) {
|
|
322
|
+
const sb = getSupabase();
|
|
323
|
+
const { data: current } = await sb.from("conversation_messages").select("metadata").eq("id", messageId).single();
|
|
324
|
+
const existingMeta = current?.metadata || {};
|
|
325
|
+
const { error } = await sb.from("conversation_messages").update({
|
|
326
|
+
content: resultSummary,
|
|
327
|
+
status: "completed",
|
|
328
|
+
metadata: {
|
|
329
|
+
...existingMeta,
|
|
330
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
331
|
+
token_usage: tokenUsage || null
|
|
332
|
+
}
|
|
333
|
+
}).eq("id", messageId);
|
|
334
|
+
if (error) throw new Error(`Failed to complete task: ${error.message}`);
|
|
335
|
+
}
|
|
336
|
+
async function failTask(messageId, errorMessage) {
|
|
337
|
+
const sb = getSupabase();
|
|
338
|
+
const { data: current } = await sb.from("conversation_messages").select("metadata").eq("id", messageId).single();
|
|
339
|
+
const existingMeta = current?.metadata || {};
|
|
340
|
+
const { error } = await sb.from("conversation_messages").update({
|
|
341
|
+
status: "failed",
|
|
342
|
+
content: errorMessage,
|
|
343
|
+
metadata: {
|
|
344
|
+
...existingMeta,
|
|
345
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
346
|
+
error_message: errorMessage
|
|
347
|
+
}
|
|
348
|
+
}).eq("id", messageId);
|
|
349
|
+
if (error) log.error(`Failed to update task status: ${error.message}`);
|
|
350
|
+
}
|
|
351
|
+
var eventSequence = 0;
|
|
352
|
+
function resetEventSequence() {
|
|
353
|
+
eventSequence = 0;
|
|
354
|
+
}
|
|
355
|
+
async function emitEvent(messageId, eventType, eventData) {
|
|
356
|
+
const sb = getSupabase();
|
|
357
|
+
eventSequence++;
|
|
358
|
+
const { error } = await sb.from("message_events").insert({
|
|
359
|
+
message_id: messageId,
|
|
360
|
+
event_type: eventType,
|
|
361
|
+
event_data: eventData,
|
|
362
|
+
sequence_number: eventSequence
|
|
363
|
+
});
|
|
364
|
+
if (error) log.warn(`Failed to emit event: ${error.message}`);
|
|
365
|
+
}
|
|
366
|
+
async function emitEvents(messageId, events) {
|
|
367
|
+
const sb = getSupabase();
|
|
368
|
+
const rows = events.map((e) => {
|
|
369
|
+
eventSequence++;
|
|
370
|
+
return {
|
|
371
|
+
message_id: messageId,
|
|
372
|
+
event_type: e.type,
|
|
373
|
+
event_data: e.data,
|
|
374
|
+
sequence_number: eventSequence
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
const { error } = await sb.from("message_events").insert(rows);
|
|
378
|
+
if (error) log.warn(`Failed to emit events batch: ${error.message}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export {
|
|
382
|
+
getConfig,
|
|
383
|
+
setConfig,
|
|
384
|
+
clearConfig,
|
|
385
|
+
getConfigPath,
|
|
386
|
+
setLogLevel,
|
|
387
|
+
setCorrelationId,
|
|
388
|
+
newCorrelationId,
|
|
389
|
+
log,
|
|
390
|
+
CLI_AGENT_ID,
|
|
391
|
+
DAYBOX_AGENT_ID,
|
|
392
|
+
getSupabase,
|
|
393
|
+
loginWithToken,
|
|
394
|
+
getSession,
|
|
395
|
+
getCurrentUserId,
|
|
396
|
+
logout,
|
|
397
|
+
createSession,
|
|
398
|
+
updateHeartbeat,
|
|
399
|
+
endSession,
|
|
400
|
+
setSessionBusy,
|
|
401
|
+
getOrCreateCliConversation,
|
|
402
|
+
createTask,
|
|
403
|
+
pollPendingTasks,
|
|
404
|
+
claimTask,
|
|
405
|
+
completeTask,
|
|
406
|
+
failTask,
|
|
407
|
+
resetEventSequence,
|
|
408
|
+
emitEvent,
|
|
409
|
+
emitEvents
|
|
410
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|