agentclick 0.2.0 → 0.2.1
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/bin/agentclick.mjs +24 -1
- package/package.json +1 -1
- package/packages/server/dist/index.js +31 -4
- package/packages/server/dist/store.js +30 -5
- package/packages/server/src/index.ts +33 -4
- package/packages/server/src/store.ts +35 -7
- package/packages/web/dist/assets/index-AqoWN0xJ.js +67 -0
- package/packages/web/dist/assets/index-wTVix_xH.css +1 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-BfM7acF_.js +0 -67
- package/packages/web/dist/assets/index-CrlEXNVh.css +0 -1
package/bin/agentclick.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
import { spawnSync } from 'node:child_process'
|
|
@@ -10,8 +10,18 @@ const __filename = fileURLToPath(import.meta.url)
|
|
|
10
10
|
const rootDir = dirname(dirname(__filename))
|
|
11
11
|
const webDistIndex = join(rootDir, 'packages', 'web', 'dist', 'index.html')
|
|
12
12
|
const serverDistEntry = join(rootDir, 'packages', 'server', 'dist', 'index.js')
|
|
13
|
+
const packageJsonPath = join(rootDir, 'package.json')
|
|
13
14
|
const args = process.argv.slice(2)
|
|
14
15
|
|
|
16
|
+
function readVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
|
19
|
+
return typeof pkg.version === 'string' ? pkg.version : 'unknown'
|
|
20
|
+
} catch {
|
|
21
|
+
return 'unknown'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
function printHelp() {
|
|
16
26
|
console.log(`AgentClick CLI
|
|
17
27
|
|
|
@@ -20,7 +30,16 @@ Usage:
|
|
|
20
30
|
agentclick --help
|
|
21
31
|
|
|
22
32
|
Options:
|
|
33
|
+
--version, -v Show version number
|
|
23
34
|
--help, -h Show this help message
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
agentclick Start the server (auto-detects port)
|
|
38
|
+
PORT=4000 agentclick Start on a specific port
|
|
39
|
+
|
|
40
|
+
Environment:
|
|
41
|
+
PORT Server port (default: 3001, auto-increments if busy)
|
|
42
|
+
OPENCLAW_WEBHOOK Webhook URL for agent callbacks
|
|
24
43
|
`)
|
|
25
44
|
}
|
|
26
45
|
|
|
@@ -31,6 +50,10 @@ function parseArgs(argv) {
|
|
|
31
50
|
printHelp()
|
|
32
51
|
process.exit(0)
|
|
33
52
|
}
|
|
53
|
+
if (arg === '--version' || arg === '-v') {
|
|
54
|
+
console.log(readVersion())
|
|
55
|
+
process.exit(0)
|
|
56
|
+
}
|
|
34
57
|
console.error(`[agentclick] Unknown argument: ${arg}`)
|
|
35
58
|
console.error('[agentclick] Run "agentclick --help" for usage.')
|
|
36
59
|
process.exit(1)
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync } from 'fs';
|
|
|
6
6
|
import { dirname, join } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { learnFromDeletions } from './preference.js';
|
|
9
|
-
import { createSession, getSession, listSessions, completeSession } from './store.js';
|
|
9
|
+
import { createSession, getSession, listSessions, completeSession, setSessionRewriting, updateSessionPayload } from './store.js';
|
|
10
10
|
const app = express();
|
|
11
11
|
const PORT = Number(process.env.PORT || 3001);
|
|
12
12
|
const OPENCLAW_WEBHOOK = process.env.OPENCLAW_WEBHOOK || 'http://localhost:18789/hooks/agent';
|
|
@@ -23,18 +23,23 @@ app.post('/api/review', async (req, res) => {
|
|
|
23
23
|
if (!sessionKey) {
|
|
24
24
|
console.warn('[agentclick] Warning: sessionKey missing — callback will be skipped');
|
|
25
25
|
}
|
|
26
|
-
const
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
const id = `session_${now}`;
|
|
27
28
|
createSession({
|
|
28
29
|
id,
|
|
29
30
|
type: type || 'email_review',
|
|
30
31
|
payload,
|
|
31
32
|
sessionKey,
|
|
32
33
|
status: 'pending',
|
|
33
|
-
createdAt:
|
|
34
|
+
createdAt: now,
|
|
35
|
+
updatedAt: now,
|
|
36
|
+
revision: 0,
|
|
34
37
|
});
|
|
35
38
|
const routeMap = {
|
|
36
39
|
action_approval: 'approval',
|
|
37
40
|
code_review: 'code-review',
|
|
41
|
+
form_review: 'form-review',
|
|
42
|
+
selection_review: 'selection',
|
|
38
43
|
};
|
|
39
44
|
const path = routeMap[type] ?? 'review';
|
|
40
45
|
const url = `${WEB_ORIGIN}/${path}/${id}`;
|
|
@@ -112,17 +117,39 @@ app.get('/api/sessions/:id/wait', async (req, res) => {
|
|
|
112
117
|
const session = getSession(req.params.id);
|
|
113
118
|
if (!session)
|
|
114
119
|
return res.status(404).json({ error: 'Session not found' });
|
|
115
|
-
if (session.status === 'completed')
|
|
120
|
+
if (session.status === 'completed' || session.status === 'rewriting') {
|
|
121
|
+
console.log(`[agentclick] /wait returning ${session.status} for ${session.id} (revision=${session.revision})`);
|
|
116
122
|
return res.json(session);
|
|
123
|
+
}
|
|
117
124
|
await new Promise(r => setTimeout(r, POLL_MS));
|
|
118
125
|
}
|
|
119
126
|
res.status(408).json({ error: 'timeout', message: 'User did not complete review within 5 minutes' });
|
|
120
127
|
});
|
|
128
|
+
// Agent updates session payload after rewriting
|
|
129
|
+
app.put('/api/sessions/:id/payload', (req, res) => {
|
|
130
|
+
const session = getSession(req.params.id);
|
|
131
|
+
if (!session)
|
|
132
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
133
|
+
if (session.status !== 'rewriting')
|
|
134
|
+
return res.status(400).json({ error: 'Session is not in rewriting state' });
|
|
135
|
+
console.log(`[agentclick] Payload update requested for ${session.id} (status=${session.status}, revision=${session.revision})`);
|
|
136
|
+
updateSessionPayload(req.params.id, req.body.payload);
|
|
137
|
+
const updated = getSession(req.params.id);
|
|
138
|
+
console.log(`[agentclick] Session ${session.id} payload updated, back to pending (revision=${updated?.revision ?? 'unknown'})`);
|
|
139
|
+
res.json({ ok: true });
|
|
140
|
+
});
|
|
121
141
|
// Web UI submits user actions
|
|
122
142
|
app.post('/api/sessions/:id/complete', async (req, res) => {
|
|
123
143
|
const session = getSession(req.params.id);
|
|
124
144
|
if (!session)
|
|
125
145
|
return res.status(404).json({ error: 'Session not found' });
|
|
146
|
+
// If user requested regeneration, set to rewriting (not completed) so agent can update
|
|
147
|
+
if (req.body.regenerate) {
|
|
148
|
+
setSessionRewriting(req.params.id, req.body);
|
|
149
|
+
console.log(`[agentclick] Session ${session.id} → rewriting:`, JSON.stringify(req.body, null, 2));
|
|
150
|
+
res.json({ ok: true, rewriting: true });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
126
153
|
completeSession(req.params.id, req.body);
|
|
127
154
|
console.log(`[agentclick] Session ${session.id} completed:`, JSON.stringify(req.body, null, 2));
|
|
128
155
|
// Learn from delete actions and persist rules to MEMORY.md
|
|
@@ -15,16 +15,29 @@ db.exec(`
|
|
|
15
15
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
16
16
|
result TEXT,
|
|
17
17
|
sessionKey TEXT,
|
|
18
|
-
createdAt INTEGER NOT NULL
|
|
18
|
+
createdAt INTEGER NOT NULL,
|
|
19
|
+
updatedAt INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
revision INTEGER NOT NULL DEFAULT 0
|
|
19
21
|
)
|
|
20
22
|
`);
|
|
23
|
+
// Migrate existing tables: add updatedAt and revision if missing
|
|
24
|
+
try {
|
|
25
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN updatedAt INTEGER NOT NULL DEFAULT 0`);
|
|
26
|
+
}
|
|
27
|
+
catch { /* column already exists */ }
|
|
28
|
+
try {
|
|
29
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN revision INTEGER NOT NULL DEFAULT 0`);
|
|
30
|
+
}
|
|
31
|
+
catch { /* column already exists */ }
|
|
21
32
|
export function createSession(session) {
|
|
22
33
|
db.prepare(`
|
|
23
|
-
INSERT INTO sessions (id, type, payload, status, sessionKey, createdAt)
|
|
24
|
-
VALUES (@id, @type, @payload, @status, @sessionKey, @createdAt)
|
|
34
|
+
INSERT INTO sessions (id, type, payload, status, sessionKey, createdAt, updatedAt, revision)
|
|
35
|
+
VALUES (@id, @type, @payload, @status, @sessionKey, @createdAt, @updatedAt, @revision)
|
|
25
36
|
`).run({
|
|
26
37
|
...session,
|
|
27
38
|
payload: JSON.stringify(session.payload),
|
|
39
|
+
updatedAt: session.updatedAt || Date.now(),
|
|
40
|
+
revision: session.revision || 0,
|
|
28
41
|
});
|
|
29
42
|
}
|
|
30
43
|
export function getSession(id) {
|
|
@@ -39,8 +52,18 @@ export function listSessions(limit = 20) {
|
|
|
39
52
|
}
|
|
40
53
|
export function completeSession(id, result) {
|
|
41
54
|
db.prepare(`
|
|
42
|
-
UPDATE sessions SET status = 'completed', result = ? WHERE id = ?
|
|
43
|
-
`).run(JSON.stringify(result), id);
|
|
55
|
+
UPDATE sessions SET status = 'completed', result = ?, updatedAt = ? WHERE id = ?
|
|
56
|
+
`).run(JSON.stringify(result), Date.now(), id);
|
|
57
|
+
}
|
|
58
|
+
export function setSessionRewriting(id, result) {
|
|
59
|
+
db.prepare(`
|
|
60
|
+
UPDATE sessions SET status = 'rewriting', result = ?, updatedAt = ? WHERE id = ?
|
|
61
|
+
`).run(JSON.stringify(result), Date.now(), id);
|
|
62
|
+
}
|
|
63
|
+
export function updateSessionPayload(id, payload) {
|
|
64
|
+
db.prepare(`
|
|
65
|
+
UPDATE sessions SET payload = ?, status = 'pending', result = NULL, updatedAt = ?, revision = revision + 1 WHERE id = ?
|
|
66
|
+
`).run(JSON.stringify(payload), Date.now(), id);
|
|
44
67
|
}
|
|
45
68
|
function deserialize(row) {
|
|
46
69
|
return {
|
|
@@ -51,5 +74,7 @@ function deserialize(row) {
|
|
|
51
74
|
result: row.result ? JSON.parse(row.result) : undefined,
|
|
52
75
|
sessionKey: row.sessionKey,
|
|
53
76
|
createdAt: row.createdAt,
|
|
77
|
+
updatedAt: row.updatedAt || 0,
|
|
78
|
+
revision: row.revision || 0,
|
|
54
79
|
};
|
|
55
80
|
}
|
|
@@ -6,7 +6,7 @@ import { existsSync } from 'fs'
|
|
|
6
6
|
import { dirname, join } from 'path'
|
|
7
7
|
import { fileURLToPath } from 'url'
|
|
8
8
|
import { learnFromDeletions } from './preference.js'
|
|
9
|
-
import { createSession, getSession, listSessions, completeSession } from './store.js'
|
|
9
|
+
import { createSession, getSession, listSessions, completeSession, setSessionRewriting, updateSessionPayload } from './store.js'
|
|
10
10
|
|
|
11
11
|
const app = express()
|
|
12
12
|
const PORT = Number(process.env.PORT || 3001)
|
|
@@ -28,19 +28,24 @@ app.post('/api/review', async (req, res) => {
|
|
|
28
28
|
console.warn('[agentclick] Warning: sessionKey missing — callback will be skipped')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const now = Date.now()
|
|
32
|
+
const id = `session_${now}`
|
|
32
33
|
createSession({
|
|
33
34
|
id,
|
|
34
35
|
type: type || 'email_review',
|
|
35
36
|
payload,
|
|
36
37
|
sessionKey,
|
|
37
38
|
status: 'pending',
|
|
38
|
-
createdAt:
|
|
39
|
+
createdAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
revision: 0,
|
|
39
42
|
})
|
|
40
43
|
|
|
41
44
|
const routeMap: Record<string, string> = {
|
|
42
45
|
action_approval: 'approval',
|
|
43
46
|
code_review: 'code-review',
|
|
47
|
+
form_review: 'form-review',
|
|
48
|
+
selection_review: 'selection',
|
|
44
49
|
}
|
|
45
50
|
const path = routeMap[type] ?? 'review'
|
|
46
51
|
const url = `${WEB_ORIGIN}/${path}/${id}`
|
|
@@ -126,18 +131,42 @@ app.get('/api/sessions/:id/wait', async (req, res) => {
|
|
|
126
131
|
while (Date.now() - start < TIMEOUT_MS) {
|
|
127
132
|
const session = getSession(req.params.id)
|
|
128
133
|
if (!session) return res.status(404).json({ error: 'Session not found' })
|
|
129
|
-
if (session.status === 'completed'
|
|
134
|
+
if (session.status === 'completed' || session.status === 'rewriting') {
|
|
135
|
+
console.log(`[agentclick] /wait returning ${session.status} for ${session.id} (revision=${session.revision})`)
|
|
136
|
+
return res.json(session)
|
|
137
|
+
}
|
|
130
138
|
await new Promise(r => setTimeout(r, POLL_MS))
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
res.status(408).json({ error: 'timeout', message: 'User did not complete review within 5 minutes' })
|
|
134
142
|
})
|
|
135
143
|
|
|
144
|
+
// Agent updates session payload after rewriting
|
|
145
|
+
app.put('/api/sessions/:id/payload', (req, res) => {
|
|
146
|
+
const session = getSession(req.params.id)
|
|
147
|
+
if (!session) return res.status(404).json({ error: 'Session not found' })
|
|
148
|
+
if (session.status !== 'rewriting') return res.status(400).json({ error: 'Session is not in rewriting state' })
|
|
149
|
+
|
|
150
|
+
console.log(`[agentclick] Payload update requested for ${session.id} (status=${session.status}, revision=${session.revision})`)
|
|
151
|
+
updateSessionPayload(req.params.id, req.body.payload)
|
|
152
|
+
const updated = getSession(req.params.id)
|
|
153
|
+
console.log(`[agentclick] Session ${session.id} payload updated, back to pending (revision=${updated?.revision ?? 'unknown'})`)
|
|
154
|
+
res.json({ ok: true })
|
|
155
|
+
})
|
|
156
|
+
|
|
136
157
|
// Web UI submits user actions
|
|
137
158
|
app.post('/api/sessions/:id/complete', async (req, res) => {
|
|
138
159
|
const session = getSession(req.params.id)
|
|
139
160
|
if (!session) return res.status(404).json({ error: 'Session not found' })
|
|
140
161
|
|
|
162
|
+
// If user requested regeneration, set to rewriting (not completed) so agent can update
|
|
163
|
+
if (req.body.regenerate) {
|
|
164
|
+
setSessionRewriting(req.params.id, req.body)
|
|
165
|
+
console.log(`[agentclick] Session ${session.id} → rewriting:`, JSON.stringify(req.body, null, 2))
|
|
166
|
+
res.json({ ok: true, rewriting: true })
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
141
170
|
completeSession(req.params.id, req.body)
|
|
142
171
|
|
|
143
172
|
console.log(`[agentclick] Session ${session.id} completed:`, JSON.stringify(req.body, null, 2))
|
|
@@ -18,27 +18,41 @@ db.exec(`
|
|
|
18
18
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
19
19
|
result TEXT,
|
|
20
20
|
sessionKey TEXT,
|
|
21
|
-
createdAt INTEGER NOT NULL
|
|
21
|
+
createdAt INTEGER NOT NULL,
|
|
22
|
+
updatedAt INTEGER NOT NULL DEFAULT 0,
|
|
23
|
+
revision INTEGER NOT NULL DEFAULT 0
|
|
22
24
|
)
|
|
23
25
|
`)
|
|
24
26
|
|
|
27
|
+
// Migrate existing tables: add updatedAt and revision if missing
|
|
28
|
+
try {
|
|
29
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN updatedAt INTEGER NOT NULL DEFAULT 0`)
|
|
30
|
+
} catch { /* column already exists */ }
|
|
31
|
+
try {
|
|
32
|
+
db.exec(`ALTER TABLE sessions ADD COLUMN revision INTEGER NOT NULL DEFAULT 0`)
|
|
33
|
+
} catch { /* column already exists */ }
|
|
34
|
+
|
|
25
35
|
export interface Session {
|
|
26
36
|
id: string
|
|
27
37
|
type: string
|
|
28
38
|
payload: unknown
|
|
29
|
-
status: 'pending' | 'completed'
|
|
39
|
+
status: 'pending' | 'rewriting' | 'completed'
|
|
30
40
|
result?: unknown
|
|
31
41
|
sessionKey?: string
|
|
32
42
|
createdAt: number
|
|
43
|
+
updatedAt: number
|
|
44
|
+
revision: number
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
export function createSession(session: Session): void {
|
|
36
48
|
db.prepare(`
|
|
37
|
-
INSERT INTO sessions (id, type, payload, status, sessionKey, createdAt)
|
|
38
|
-
VALUES (@id, @type, @payload, @status, @sessionKey, @createdAt)
|
|
49
|
+
INSERT INTO sessions (id, type, payload, status, sessionKey, createdAt, updatedAt, revision)
|
|
50
|
+
VALUES (@id, @type, @payload, @status, @sessionKey, @createdAt, @updatedAt, @revision)
|
|
39
51
|
`).run({
|
|
40
52
|
...session,
|
|
41
53
|
payload: JSON.stringify(session.payload),
|
|
54
|
+
updatedAt: session.updatedAt || Date.now(),
|
|
55
|
+
revision: session.revision || 0,
|
|
42
56
|
})
|
|
43
57
|
}
|
|
44
58
|
|
|
@@ -55,8 +69,20 @@ export function listSessions(limit = 20): Session[] {
|
|
|
55
69
|
|
|
56
70
|
export function completeSession(id: string, result: unknown): void {
|
|
57
71
|
db.prepare(`
|
|
58
|
-
UPDATE sessions SET status = 'completed', result = ? WHERE id = ?
|
|
59
|
-
`).run(JSON.stringify(result), id)
|
|
72
|
+
UPDATE sessions SET status = 'completed', result = ?, updatedAt = ? WHERE id = ?
|
|
73
|
+
`).run(JSON.stringify(result), Date.now(), id)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function setSessionRewriting(id: string, result: unknown): void {
|
|
77
|
+
db.prepare(`
|
|
78
|
+
UPDATE sessions SET status = 'rewriting', result = ?, updatedAt = ? WHERE id = ?
|
|
79
|
+
`).run(JSON.stringify(result), Date.now(), id)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function updateSessionPayload(id: string, payload: unknown): void {
|
|
83
|
+
db.prepare(`
|
|
84
|
+
UPDATE sessions SET payload = ?, status = 'pending', result = NULL, updatedAt = ?, revision = revision + 1 WHERE id = ?
|
|
85
|
+
`).run(JSON.stringify(payload), Date.now(), id)
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
function deserialize(row: Record<string, unknown>): Session {
|
|
@@ -64,9 +90,11 @@ function deserialize(row: Record<string, unknown>): Session {
|
|
|
64
90
|
id: row.id as string,
|
|
65
91
|
type: row.type as string,
|
|
66
92
|
payload: JSON.parse(row.payload as string),
|
|
67
|
-
status: row.status as 'pending' | 'completed',
|
|
93
|
+
status: row.status as 'pending' | 'rewriting' | 'completed',
|
|
68
94
|
result: row.result ? JSON.parse(row.result as string) : undefined,
|
|
69
95
|
sessionKey: row.sessionKey as string | undefined,
|
|
70
96
|
createdAt: row.createdAt as number,
|
|
97
|
+
updatedAt: (row.updatedAt as number) || 0,
|
|
98
|
+
revision: (row.revision as number) || 0,
|
|
71
99
|
}
|
|
72
100
|
}
|