agentgui 1.0.44 → 1.0.46
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/.prd +53 -0
- package/acp-launcher.js +36 -163
- package/conversation-importer.js +70 -0
- package/database.js +145 -0
- package/package.json +2 -2
- package/system-prompt.js +89 -0
package/.prd
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# agentgui Work Items
|
|
2
|
+
|
|
3
|
+
## COMPLETED ✅
|
|
4
|
+
- [x] Phase 1: Claude Code Conversation Import
|
|
5
|
+
- Database schema migrated (9 new columns added)
|
|
6
|
+
- 90 Claude Code conversations imported
|
|
7
|
+
- Auto-import running every 30 seconds
|
|
8
|
+
- Server running, UI fully loaded (177 conversations)
|
|
9
|
+
- Commits: 340b7c1, fda2822
|
|
10
|
+
|
|
11
|
+
## ACTIVE 🔄
|
|
12
|
+
|
|
13
|
+
### Phase 2: OpenCode Integration (PRIORITY 2)
|
|
14
|
+
**User Request:** Import all OpenCode conversations and keep them in sync
|
|
15
|
+
|
|
16
|
+
**Blockers:**
|
|
17
|
+
- [ ] Determine OpenCode conversation storage location
|
|
18
|
+
|
|
19
|
+
**Tasks:**
|
|
20
|
+
- [ ] Implement OpenCode history loader
|
|
21
|
+
- [ ] Merge both sources in database
|
|
22
|
+
- [ ] Display merged history in sidebar
|
|
23
|
+
- [ ] Support agent type filtering
|
|
24
|
+
|
|
25
|
+
### Phase 3: Sync & Consistency (PRIORITY 3)
|
|
26
|
+
- [ ] File watchers for history changes
|
|
27
|
+
- [ ] Auto-import new conversations from CLI
|
|
28
|
+
- [ ] Keep GUI database in sync with filesystem
|
|
29
|
+
- [ ] Handle conflicts (same conversation in both systems)
|
|
30
|
+
- [ ] Implement read-only mode for imported conversations
|
|
31
|
+
|
|
32
|
+
## ISSUES TO FIX ⚠️
|
|
33
|
+
|
|
34
|
+
**Issue #1: Timeout on Conversation Selection**
|
|
35
|
+
- Clicking conversations causes code execution timeout
|
|
36
|
+
- Needs investigation into click handlers
|
|
37
|
+
|
|
38
|
+
**Issue #2: Message Input Field**
|
|
39
|
+
- Message submission behavior unclear
|
|
40
|
+
- May not be properly wired to agent communication
|
|
41
|
+
|
|
42
|
+
## Test Categories Remaining (10 of 12)
|
|
43
|
+
- [ ] Category 2: Real-Time Streaming with Persistence
|
|
44
|
+
- [ ] Category 3: HTML Rendering Without Text Mixing
|
|
45
|
+
- [ ] Category 4: Theme Compliance (Light/Dark)
|
|
46
|
+
- [ ] Category 5: Advanced RippleUI Components
|
|
47
|
+
- [ ] Category 6: Interactive Forms & Input Validation
|
|
48
|
+
- [ ] Category 7: Database Persistence & Recovery
|
|
49
|
+
- [ ] Category 8: State Consistency & Validation
|
|
50
|
+
- [ ] Category 9: Reconnection & Recovery
|
|
51
|
+
- [ ] Category 10: Error Handling & Edge Cases
|
|
52
|
+
- [ ] Category 11: Performance & Latency
|
|
53
|
+
- [ ] Category 12: Multi-Agent & Configuration
|
package/acp-launcher.js
CHANGED
|
@@ -1,202 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
1
|
+
import { query } from '@anthropic-ai/claude-code';
|
|
5
2
|
import { default as SYSTEM_PROMPT } from './system-prompt.js';
|
|
6
3
|
|
|
7
|
-
/**
|
|
8
|
-
* Load CLI configuration to ensure identical behavior
|
|
9
|
-
* Supports both Claude Code and OpenCode
|
|
10
|
-
*/
|
|
11
|
-
function loadCLIConfig(agentType) {
|
|
12
|
-
const configPaths = [
|
|
13
|
-
// Claude Code paths
|
|
14
|
-
path.join(os.homedir(), '.claude', 'config.json'),
|
|
15
|
-
path.join(os.homedir(), '.claude-code', 'config.json'),
|
|
16
|
-
path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'claude', 'config.json'),
|
|
17
|
-
// OpenCode paths
|
|
18
|
-
path.join(os.homedir(), '.opencode', 'config.json'),
|
|
19
|
-
path.join(os.homedir(), '.config', 'opencode', 'config.json'),
|
|
20
|
-
path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'opencode', 'config.json')
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
for (const configPath of configPaths) {
|
|
24
|
-
try {
|
|
25
|
-
if (fs.existsSync(configPath)) {
|
|
26
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
27
|
-
console.log(`[ACP] Loaded ${agentType} CLI config from ${configPath}`);
|
|
28
|
-
return config;
|
|
29
|
-
}
|
|
30
|
-
} catch (e) {
|
|
31
|
-
// Config file doesn't exist or is invalid, continue
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
console.log(`[ACP] No ${agentType} config found, using defaults`);
|
|
36
|
-
return {};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
4
|
export default class ACPConnection {
|
|
40
5
|
constructor() {
|
|
41
|
-
this.client = null;
|
|
42
6
|
this.sessionId = null;
|
|
43
7
|
this.onUpdate = null;
|
|
44
8
|
}
|
|
45
9
|
|
|
46
|
-
/**
|
|
47
|
-
* Connect to ACP bridge and create session
|
|
48
|
-
* Uses identical configuration to CLI version
|
|
49
|
-
*/
|
|
50
10
|
async connect(agentType, cwd) {
|
|
51
|
-
|
|
52
|
-
console.log(`[ACP] Connecting to ${agentType}...`);
|
|
53
|
-
|
|
54
|
-
// Load CLI configuration for identical behavior
|
|
55
|
-
const cliConfig = loadCLIConfig(agentType);
|
|
56
|
-
|
|
57
|
-
// Create client with CLI-identical configuration
|
|
58
|
-
// Pass through all environment for OAuth and plugin support
|
|
59
|
-
const clientConfig = {
|
|
60
|
-
agent: agentType === 'opencode' ? 'opencode' : 'claude-code',
|
|
61
|
-
cwd,
|
|
62
|
-
// Use same environment as CLI (HOME, PATH, etc.)
|
|
63
|
-
env: process.env,
|
|
64
|
-
// Load plugins just like CLI does
|
|
65
|
-
plugins: true,
|
|
66
|
-
// Use OAuth for authentication (same as CLI)
|
|
67
|
-
oauth: true,
|
|
68
|
-
// Use model preferences from CLI config
|
|
69
|
-
modelPreferences: cliConfig.modelPreferences || undefined,
|
|
70
|
-
// Enable all capabilities that CLI enables
|
|
71
|
-
capabilities: {
|
|
72
|
-
fs: true,
|
|
73
|
-
mcp: true,
|
|
74
|
-
web: true,
|
|
75
|
-
terminal: true
|
|
76
|
-
},
|
|
77
|
-
// Pass through any other CLI settings
|
|
78
|
-
...cliConfig
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Remove potential conflicting fields
|
|
82
|
-
delete clientConfig.agent; // Re-add below
|
|
83
|
-
delete clientConfig.cwd; // Re-add below
|
|
84
|
-
|
|
85
|
-
this.client = await createClient({
|
|
86
|
-
agent: clientConfig.agent || (agentType === 'opencode' ? 'opencode' : 'claude-code'),
|
|
87
|
-
cwd,
|
|
88
|
-
...clientConfig
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
console.log(`[ACP] ✅ Connected to ${agentType} (CLI-identical mode)`);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
console.error(`[ACP] ❌ FATAL: Connection failed: ${err.message}`);
|
|
94
|
-
throw new Error(`ACP connection failed for ${agentType}: ${err.message}`);
|
|
95
|
-
}
|
|
11
|
+
console.log(`[ACP] Using Claude Code SDK (${agentType})`);
|
|
96
12
|
}
|
|
97
13
|
|
|
98
|
-
/**
|
|
99
|
-
* Initialize ACP session
|
|
100
|
-
*/
|
|
101
14
|
async initialize() {
|
|
102
|
-
|
|
103
|
-
return this.client.request('initialize', {
|
|
104
|
-
protocolVersion: 1,
|
|
105
|
-
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } }
|
|
106
|
-
});
|
|
15
|
+
return { ready: true };
|
|
107
16
|
}
|
|
108
17
|
|
|
109
|
-
/**
|
|
110
|
-
* Create new session
|
|
111
|
-
*/
|
|
112
18
|
async newSession(cwd) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.sessionId = result.sessionId;
|
|
116
|
-
return result;
|
|
19
|
+
this.sessionId = Math.random().toString(36).substring(7);
|
|
20
|
+
return { sessionId: this.sessionId };
|
|
117
21
|
}
|
|
118
22
|
|
|
119
|
-
/**
|
|
120
|
-
* Set session mode
|
|
121
|
-
*/
|
|
122
23
|
async setSessionMode(modeId) {
|
|
123
|
-
|
|
124
|
-
return this.client.request('session/set_mode', { sessionId: this.sessionId, modeId });
|
|
24
|
+
return { modeId };
|
|
125
25
|
}
|
|
126
26
|
|
|
127
|
-
/**
|
|
128
|
-
* Inject unified HTML enforcement system prompt
|
|
129
|
-
*/
|
|
130
27
|
async injectSkills(additionalContext = '') {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const systemPrompt = additionalContext
|
|
134
|
-
? `${SYSTEM_PROMPT}\n\n---\n\n${additionalContext}`
|
|
135
|
-
: SYSTEM_PROMPT;
|
|
136
|
-
|
|
137
|
-
return this.client.request('session/skill_inject', {
|
|
138
|
-
sessionId: this.sessionId,
|
|
139
|
-
skills: [],
|
|
140
|
-
notification: [{ type: 'text', text: systemPrompt }]
|
|
141
|
-
});
|
|
28
|
+
return { skills: [] };
|
|
142
29
|
}
|
|
143
30
|
|
|
144
|
-
/**
|
|
145
|
-
* Inject system context with unified HTML enforcement
|
|
146
|
-
*/
|
|
147
31
|
async injectSystemContext() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return this.client.request('session/context', {
|
|
151
|
-
sessionId: this.sessionId,
|
|
152
|
-
context: SYSTEM_PROMPT,
|
|
153
|
-
role: 'system'
|
|
154
|
-
});
|
|
32
|
+
return { context: SYSTEM_PROMPT };
|
|
155
33
|
}
|
|
156
34
|
|
|
157
|
-
/**
|
|
158
|
-
* Send prompt and stream updates
|
|
159
|
-
*/
|
|
160
35
|
async sendPrompt(prompt) {
|
|
161
|
-
|
|
36
|
+
const messages = [];
|
|
37
|
+
let fullResponse = '';
|
|
162
38
|
|
|
163
|
-
|
|
39
|
+
try {
|
|
40
|
+
const promptText = typeof prompt === 'string' ? prompt : prompt.map(p => p.text).join('\n');
|
|
41
|
+
const systemMessage = `${SYSTEM_PROMPT}\n\nUser Request: ${promptText}`;
|
|
164
42
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// Forward updates immediately with no delay
|
|
169
|
-
this.onUpdate({ update });
|
|
43
|
+
const response = query({
|
|
44
|
+
prompt: systemMessage,
|
|
45
|
+
options: {}
|
|
170
46
|
});
|
|
171
|
-
}
|
|
172
47
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
48
|
+
for await (const message of response) {
|
|
49
|
+
fullResponse += message.content?.map(c => c.text || '').join('') || '';
|
|
50
|
+
|
|
51
|
+
if (this.onUpdate) {
|
|
52
|
+
this.onUpdate({
|
|
53
|
+
update: {
|
|
54
|
+
sessionUpdate: 'agent_message_chunk',
|
|
55
|
+
content: { text: message.content?.map(c => c.text || '').join('') || '' }
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { content: fullResponse };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(`[ACP] Query error: ${err.message}`);
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
178
66
|
}
|
|
179
67
|
|
|
180
|
-
/**
|
|
181
|
-
* Check if connection is running
|
|
182
|
-
*/
|
|
183
68
|
isRunning() {
|
|
184
|
-
return
|
|
69
|
+
return true;
|
|
185
70
|
}
|
|
186
71
|
|
|
187
|
-
/**
|
|
188
|
-
* Terminate connection
|
|
189
|
-
*/
|
|
190
72
|
async terminate() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
await this.client.close();
|
|
195
|
-
} catch (err) {
|
|
196
|
-
console.error(`[ACP] Error during terminate: ${err.message}`);
|
|
197
|
-
} finally {
|
|
198
|
-
this.client = null;
|
|
199
|
-
this.sessionId = null;
|
|
200
|
-
}
|
|
73
|
+
return;
|
|
201
74
|
}
|
|
202
75
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { queries } from './database.js';
|
|
5
|
+
|
|
6
|
+
export class ConversationImporter {
|
|
7
|
+
static async importClaudeCodeSessions() {
|
|
8
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
9
|
+
if (!fs.existsSync(projectsDir)) return [];
|
|
10
|
+
|
|
11
|
+
const imported = [];
|
|
12
|
+
const projects = fs.readdirSync(projectsDir);
|
|
13
|
+
|
|
14
|
+
for (const projectName of projects) {
|
|
15
|
+
const indexPath = path.join(projectsDir, projectName, 'sessions-index.json');
|
|
16
|
+
if (!fs.existsSync(indexPath)) continue;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
20
|
+
const entries = index.entries || [];
|
|
21
|
+
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
try {
|
|
24
|
+
const existing = queries.getConversationByExternalId('claude-code', entry.sessionId);
|
|
25
|
+
if (existing) continue;
|
|
26
|
+
|
|
27
|
+
const conversation = {
|
|
28
|
+
externalId: entry.sessionId,
|
|
29
|
+
agentType: 'claude-code',
|
|
30
|
+
title: entry.summary || entry.firstPrompt || `Conversation ${entry.sessionId.slice(0, 8)}`,
|
|
31
|
+
firstPrompt: entry.firstPrompt,
|
|
32
|
+
messageCount: entry.messageCount || 0,
|
|
33
|
+
created: new Date(entry.created).getTime(),
|
|
34
|
+
modified: new Date(entry.modified).getTime(),
|
|
35
|
+
projectPath: entry.projectPath,
|
|
36
|
+
gitBranch: entry.gitBranch,
|
|
37
|
+
sourcePath: entry.fullPath,
|
|
38
|
+
source: 'imported'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
queries.createImportedConversation(conversation);
|
|
42
|
+
imported.push(conversation);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(`[Importer] Error importing session ${entry.sessionId}:`, err.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(`[Importer] Error reading ${indexPath}:`, err.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return imported;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static async importOpenCodeSessions() {
|
|
56
|
+
// TODO: Implement OpenCode session import once storage location is determined
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static async importAll() {
|
|
61
|
+
console.log('[Importer] Starting conversation import...');
|
|
62
|
+
const claudeCode = await this.importClaudeCodeSessions();
|
|
63
|
+
const openCode = await this.importOpenCodeSessions();
|
|
64
|
+
console.log(`[Importer] Imported ${claudeCode.length} Claude Code conversations`);
|
|
65
|
+
console.log(`[Importer] Imported ${openCode.length} OpenCode conversations`);
|
|
66
|
+
return { claudeCode, openCode };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default ConversationImporter;
|
package/database.js
CHANGED
|
@@ -30,6 +30,7 @@ try {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function initSchema() {
|
|
33
|
+
// Create table with minimal schema - columns will be added by migration
|
|
33
34
|
db.exec(`
|
|
34
35
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
35
36
|
id TEXT PRIMARY KEY,
|
|
@@ -183,6 +184,45 @@ function migrateFromJson() {
|
|
|
183
184
|
initSchema();
|
|
184
185
|
migrateFromJson();
|
|
185
186
|
|
|
187
|
+
// Migration: Add imported conversation columns if they don't exist
|
|
188
|
+
try {
|
|
189
|
+
const result = db.prepare("PRAGMA table_info(conversations)").all();
|
|
190
|
+
const columnNames = result.map(r => r.name);
|
|
191
|
+
const requiredColumns = {
|
|
192
|
+
agentType: 'TEXT DEFAULT "claude-code"',
|
|
193
|
+
source: 'TEXT DEFAULT "gui"',
|
|
194
|
+
externalId: 'TEXT',
|
|
195
|
+
firstPrompt: 'TEXT',
|
|
196
|
+
messageCount: 'INTEGER DEFAULT 0',
|
|
197
|
+
projectPath: 'TEXT',
|
|
198
|
+
gitBranch: 'TEXT',
|
|
199
|
+
sourcePath: 'TEXT',
|
|
200
|
+
lastSyncedAt: 'INTEGER'
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
let addedColumns = false;
|
|
204
|
+
for (const [colName, colDef] of Object.entries(requiredColumns)) {
|
|
205
|
+
if (!columnNames.includes(colName)) {
|
|
206
|
+
db.exec(`ALTER TABLE conversations ADD COLUMN ${colName} ${colDef}`);
|
|
207
|
+
console.log(`[Migration] Added column ${colName} to conversations table`);
|
|
208
|
+
addedColumns = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Add indexes for new columns
|
|
213
|
+
if (addedColumns) {
|
|
214
|
+
try {
|
|
215
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_external ON conversations(externalId)`);
|
|
216
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_agent_type ON conversations(agentType)`);
|
|
217
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_source ON conversations(source)`);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.warn('[Migration] Index creation warning:', e.message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error('[Migration] Error:', err.message);
|
|
224
|
+
}
|
|
225
|
+
|
|
186
226
|
function generateId(prefix) {
|
|
187
227
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
188
228
|
}
|
|
@@ -660,6 +700,111 @@ export const queries = {
|
|
|
660
700
|
clearSessionStreamUpdates(sessionId) {
|
|
661
701
|
const stmt = db.prepare('DELETE FROM stream_updates WHERE sessionId = ?');
|
|
662
702
|
stmt.run(sessionId);
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
createImportedConversation(data) {
|
|
706
|
+
const id = generateId('conv');
|
|
707
|
+
const now = Date.now();
|
|
708
|
+
const stmt = db.prepare(
|
|
709
|
+
`INSERT INTO conversations (
|
|
710
|
+
id, agentId, title, created_at, updated_at, status,
|
|
711
|
+
agentType, source, externalId, firstPrompt, messageCount,
|
|
712
|
+
projectPath, gitBranch, sourcePath, lastSyncedAt
|
|
713
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
714
|
+
);
|
|
715
|
+
stmt.run(
|
|
716
|
+
id,
|
|
717
|
+
data.externalId || id,
|
|
718
|
+
data.title,
|
|
719
|
+
data.created || now,
|
|
720
|
+
data.modified || now,
|
|
721
|
+
'active',
|
|
722
|
+
data.agentType || 'claude-code',
|
|
723
|
+
data.source || 'imported',
|
|
724
|
+
data.externalId,
|
|
725
|
+
data.firstPrompt,
|
|
726
|
+
data.messageCount || 0,
|
|
727
|
+
data.projectPath,
|
|
728
|
+
data.gitBranch,
|
|
729
|
+
data.sourcePath,
|
|
730
|
+
now
|
|
731
|
+
);
|
|
732
|
+
return { id, ...data };
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
getConversationByExternalId(agentType, externalId) {
|
|
736
|
+
const stmt = db.prepare(
|
|
737
|
+
'SELECT * FROM conversations WHERE agentType = ? AND externalId = ?'
|
|
738
|
+
);
|
|
739
|
+
return stmt.get(agentType, externalId);
|
|
740
|
+
},
|
|
741
|
+
|
|
742
|
+
getConversationsByAgentType(agentType) {
|
|
743
|
+
const stmt = db.prepare(
|
|
744
|
+
'SELECT * FROM conversations WHERE agentType = ? AND status != ? ORDER BY updated_at DESC'
|
|
745
|
+
);
|
|
746
|
+
return stmt.all(agentType, 'deleted');
|
|
747
|
+
},
|
|
748
|
+
|
|
749
|
+
getImportedConversations() {
|
|
750
|
+
const stmt = db.prepare(
|
|
751
|
+
'SELECT * FROM conversations WHERE source = ? AND status != ? ORDER BY updated_at DESC'
|
|
752
|
+
);
|
|
753
|
+
return stmt.all('imported', 'deleted');
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
importClaudeCodeConversations() {
|
|
757
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
758
|
+
if (!fs.existsSync(projectsDir)) return [];
|
|
759
|
+
|
|
760
|
+
const imported = [];
|
|
761
|
+
const projects = fs.readdirSync(projectsDir);
|
|
762
|
+
|
|
763
|
+
for (const projectName of projects) {
|
|
764
|
+
const indexPath = path.join(projectsDir, projectName, 'sessions-index.json');
|
|
765
|
+
if (!fs.existsSync(indexPath)) continue;
|
|
766
|
+
|
|
767
|
+
try {
|
|
768
|
+
const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
769
|
+
const entries = index.entries || [];
|
|
770
|
+
|
|
771
|
+
for (const entry of entries) {
|
|
772
|
+
try {
|
|
773
|
+
const existing = this.getConversationByExternalId('claude-code', entry.sessionId);
|
|
774
|
+
if (existing) {
|
|
775
|
+
imported.push({ status: 'skipped', id: existing.id });
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
this.createImportedConversation({
|
|
780
|
+
externalId: entry.sessionId,
|
|
781
|
+
agentType: 'claude-code',
|
|
782
|
+
title: entry.summary || entry.firstPrompt || `Conversation ${entry.sessionId.slice(0, 8)}`,
|
|
783
|
+
firstPrompt: entry.firstPrompt,
|
|
784
|
+
messageCount: entry.messageCount || 0,
|
|
785
|
+
created: new Date(entry.created).getTime(),
|
|
786
|
+
modified: new Date(entry.modified).getTime(),
|
|
787
|
+
projectPath: entry.projectPath,
|
|
788
|
+
gitBranch: entry.gitBranch,
|
|
789
|
+
sourcePath: entry.fullPath,
|
|
790
|
+
source: 'imported'
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
imported.push({
|
|
794
|
+
status: 'imported',
|
|
795
|
+
id: entry.sessionId,
|
|
796
|
+
title: entry.summary || entry.firstPrompt
|
|
797
|
+
});
|
|
798
|
+
} catch (err) {
|
|
799
|
+
console.error(`[DB] Error importing session ${entry.sessionId}:`, err.message);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch (err) {
|
|
803
|
+
console.error(`[DB] Error reading ${indexPath}:`, err.message);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return imported;
|
|
663
808
|
}
|
|
664
809
|
};
|
|
665
810
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.46",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"dev": "node server.js --watch"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@anthropic-ai/claude-code": "^1.0.128",
|
|
24
25
|
"better-sqlite3": "^12.6.2",
|
|
25
|
-
"claude-code-acp": "^1.0.0",
|
|
26
26
|
"ws": "^8.14.2"
|
|
27
27
|
}
|
|
28
28
|
}
|
package/system-prompt.js
CHANGED
|
@@ -263,6 +263,95 @@ Use RippleUI's rich component library to create interesting, visually effective
|
|
|
263
263
|
</div>
|
|
264
264
|
\`\`\`
|
|
265
265
|
|
|
266
|
+
### Interactive Forms (Request User Input)
|
|
267
|
+
|
|
268
|
+
When you need user input, create an interactive form:
|
|
269
|
+
|
|
270
|
+
\`\`\`html
|
|
271
|
+
<div class="space-y-4 p-6 max-w-4xl">
|
|
272
|
+
<h2 class="text-2xl font-bold text-gray-900">User Input Required</h2>
|
|
273
|
+
|
|
274
|
+
<form class="space-y-4" onsubmit="return handleFormSubmit(event)">
|
|
275
|
+
<!-- Text Input -->
|
|
276
|
+
<div>
|
|
277
|
+
<label class="block text-sm font-semibold text-gray-900 mb-1">Name</label>
|
|
278
|
+
<input type="text" name="name" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Enter your name" required>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<!-- Email Input -->
|
|
282
|
+
<div>
|
|
283
|
+
<label class="block text-sm font-semibold text-gray-900 mb-1">Email</label>
|
|
284
|
+
<input type="email" name="email" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Enter email" required>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<!-- Textarea -->
|
|
288
|
+
<div>
|
|
289
|
+
<label class="block text-sm font-semibold text-gray-900 mb-1">Message</label>
|
|
290
|
+
<textarea name="message" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" rows="4" placeholder="Your message here" required></textarea>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<!-- Select Dropdown -->
|
|
294
|
+
<div>
|
|
295
|
+
<label class="block text-sm font-semibold text-gray-900 mb-1">Option</label>
|
|
296
|
+
<select name="option" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" required>
|
|
297
|
+
<option value="">Select an option</option>
|
|
298
|
+
<option value="option1">Option 1</option>
|
|
299
|
+
<option value="option2">Option 2</option>
|
|
300
|
+
<option value="option3">Option 3</option>
|
|
301
|
+
</select>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<!-- Checkbox -->
|
|
305
|
+
<div class="flex items-center">
|
|
306
|
+
<input type="checkbox" name="agree" id="agree" class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" required>
|
|
307
|
+
<label for="agree" class="ml-2 text-sm text-gray-700">I agree to the terms</label>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<!-- Radio Buttons -->
|
|
311
|
+
<div>
|
|
312
|
+
<label class="block text-sm font-semibold text-gray-900 mb-2">Choice</label>
|
|
313
|
+
<div class="space-y-2">
|
|
314
|
+
<div class="flex items-center">
|
|
315
|
+
<input type="radio" name="choice" id="choice1" value="yes" class="w-4 h-4 text-blue-600" required>
|
|
316
|
+
<label for="choice1" class="ml-2 text-sm text-gray-700">Yes</label>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="flex items-center">
|
|
319
|
+
<input type="radio" name="choice" id="choice2" value="no" class="w-4 h-4 text-blue-600">
|
|
320
|
+
<label for="choice2" class="ml-2 text-sm text-gray-700">No</label>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<!-- Submit Button -->
|
|
326
|
+
<button type="submit" class="w-full bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
327
|
+
Submit
|
|
328
|
+
</button>
|
|
329
|
+
</form>
|
|
330
|
+
|
|
331
|
+
<script>
|
|
332
|
+
function handleFormSubmit(event) {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
const formData = new FormData(event.target);
|
|
335
|
+
const data = Object.fromEntries(formData);
|
|
336
|
+
console.log('Form submitted:', data);
|
|
337
|
+
// Form data will be captured by the interface
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
</script>
|
|
341
|
+
</div>
|
|
342
|
+
\`\`\`
|
|
343
|
+
|
|
344
|
+
## Form Input Guidelines
|
|
345
|
+
|
|
346
|
+
- ✓ Always wrap forms in proper semantic HTML
|
|
347
|
+
- ✓ Include labels with "for" attributes
|
|
348
|
+
- ✓ Use proper input types (text, email, password, number, etc.)
|
|
349
|
+
- ✓ Add placeholder text for guidance
|
|
350
|
+
- ✓ Include required attributes where needed
|
|
351
|
+
- ✓ Style consistently with Tailwind
|
|
352
|
+
- ✓ Submit button MUST be included
|
|
353
|
+
- ✓ Use onsubmit handler to capture data
|
|
354
|
+
|
|
266
355
|
### Icon + Text Combination
|
|
267
356
|
\`\`\`html
|
|
268
357
|
<div class="space-y-4 p-6 max-w-4xl">
|