@quantod/qq 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/bin/qq +2 -0
- package/dist/commands.d.ts +43 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +236 -0
- package/dist/commands.js.map +1 -0
- package/dist/db.d.ts +4 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +36 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +158 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
- package/src/commands.ts +284 -0
- package/src/db.ts +31 -0
- package/src/index.ts +129 -0
package/bin/qq
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare class QQError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export interface Message {
|
|
5
|
+
id: string;
|
|
6
|
+
subqueue: string;
|
|
7
|
+
claimed: boolean;
|
|
8
|
+
claimed_by: string | null;
|
|
9
|
+
seq: number;
|
|
10
|
+
created: number;
|
|
11
|
+
last_modified: number;
|
|
12
|
+
payload?: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface FilterOptions {
|
|
15
|
+
claimed?: boolean;
|
|
16
|
+
claimed_by?: string;
|
|
17
|
+
ids?: string[];
|
|
18
|
+
created_after?: number;
|
|
19
|
+
created_before?: number;
|
|
20
|
+
modified_after?: number;
|
|
21
|
+
limit?: number;
|
|
22
|
+
offset?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ReleaseOptions {
|
|
25
|
+
target?: string;
|
|
26
|
+
payload?: string;
|
|
27
|
+
replace?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function makeQueue(nameSuffix?: string): string;
|
|
30
|
+
export declare function deleteQueue(queue: string): void;
|
|
31
|
+
export declare function push(path: string, id: string, payload?: string): void;
|
|
32
|
+
export declare function claim(path: string, options?: {
|
|
33
|
+
agentId?: string;
|
|
34
|
+
}): Message | null;
|
|
35
|
+
export declare function release(queue: string, id: string, options?: ReleaseOptions): void;
|
|
36
|
+
export declare function list(path: string, filters?: FilterOptions): Message[];
|
|
37
|
+
export declare function batchRead(path: string, filters?: FilterOptions): Message[];
|
|
38
|
+
export declare function stats(queue: string): Record<string, {
|
|
39
|
+
total: number;
|
|
40
|
+
claimed: number;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function reap(queue: string, agentId?: string): number;
|
|
43
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAQA,qBAAa,OAAQ,SAAQ,KAAK;gBACpB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAmCD,wBAAgB,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CASrD;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE/C;AAID,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAkBrE;AAID,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,GAAG,IAAI,CAwBtF;AAID,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,IAAI,CA4BrF;AA8DD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,EAAE,CAEzE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,EAAE,CAE9E;AAID,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBvF;AAID,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAiB5D"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QQError = void 0;
|
|
4
|
+
exports.makeQueue = makeQueue;
|
|
5
|
+
exports.deleteQueue = deleteQueue;
|
|
6
|
+
exports.push = push;
|
|
7
|
+
exports.claim = claim;
|
|
8
|
+
exports.release = release;
|
|
9
|
+
exports.list = list;
|
|
10
|
+
exports.batchRead = batchRead;
|
|
11
|
+
exports.stats = stats;
|
|
12
|
+
exports.reap = reap;
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
const db_js_1 = require("./db.js");
|
|
17
|
+
// ── types ──────────────────────────────────────────────────────────────────────
|
|
18
|
+
class QQError extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'QQError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.QQError = QQError;
|
|
25
|
+
// ── internal helpers ───────────────────────────────────────────────────────────
|
|
26
|
+
function parsePath(path) {
|
|
27
|
+
const slash = path.indexOf('/');
|
|
28
|
+
if (slash === -1)
|
|
29
|
+
return { queue: path, subqueue: undefined };
|
|
30
|
+
return { queue: path.slice(0, slash), subqueue: path.slice(slash + 1) };
|
|
31
|
+
}
|
|
32
|
+
function toGlob(pattern) {
|
|
33
|
+
return pattern.replace(/\*/g, '%').replace(/\?/g, '_');
|
|
34
|
+
}
|
|
35
|
+
function nextSeq(db) {
|
|
36
|
+
const row = db.prepare('SELECT MAX(seq) as m FROM messages').get();
|
|
37
|
+
return (row.m ?? 0) + 1;
|
|
38
|
+
}
|
|
39
|
+
function rowToMessage(row, includePayload) {
|
|
40
|
+
const msg = {
|
|
41
|
+
id: row.id,
|
|
42
|
+
subqueue: row.subqueue,
|
|
43
|
+
claimed: Boolean(row.claimed),
|
|
44
|
+
claimed_by: row.claimed_by ?? null,
|
|
45
|
+
seq: row.seq,
|
|
46
|
+
created: row.created,
|
|
47
|
+
last_modified: row.last_modified,
|
|
48
|
+
};
|
|
49
|
+
if (includePayload)
|
|
50
|
+
msg.payload = row.payload ?? null;
|
|
51
|
+
return msg;
|
|
52
|
+
}
|
|
53
|
+
// ── makeQueue ─────────────────────────────────────────────────────────────────
|
|
54
|
+
function makeQueue(nameSuffix) {
|
|
55
|
+
const now = new Date();
|
|
56
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
57
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
58
|
+
const suffix = nameSuffix ?? (0, node_crypto_1.randomBytes)(3).toString('hex');
|
|
59
|
+
const name = `${mm}${dd}_${suffix}`;
|
|
60
|
+
const db = (0, db_js_1.openDb)(name);
|
|
61
|
+
db.close();
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
// ── deleteQueue ───────────────────────────────────────────────────────────────
|
|
65
|
+
function deleteQueue(queue) {
|
|
66
|
+
(0, node_fs_1.rmSync)((0, node_path_1.join)(process.cwd(), '.claude', 'qq', `${queue}.db`), { force: true });
|
|
67
|
+
}
|
|
68
|
+
// ── push ──────────────────────────────────────────────────────────────────────
|
|
69
|
+
function push(path, id, payload) {
|
|
70
|
+
const { queue, subqueue } = parsePath(path);
|
|
71
|
+
if (!subqueue)
|
|
72
|
+
throw new QQError('path must include a subqueue: queue/subqueue');
|
|
73
|
+
const db = (0, db_js_1.openDb)(queue);
|
|
74
|
+
try {
|
|
75
|
+
(0, db_js_1.inTransaction)(db, () => {
|
|
76
|
+
const existing = db.prepare('SELECT id FROM messages WHERE id = ?').get(id);
|
|
77
|
+
if (existing)
|
|
78
|
+
throw new QQError(`duplicate id: ${id}`);
|
|
79
|
+
const seq = nextSeq(db);
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
db.prepare('INSERT INTO messages (id, subqueue, claimed, seq, payload, created, last_modified) VALUES (?, ?, 0, ?, ?, ?, ?)').run(id, subqueue, seq, payload ?? null, now, now);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
db.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ── claim ─────────────────────────────────────────────────────────────────────
|
|
89
|
+
function claim(path, options = {}) {
|
|
90
|
+
const { queue, subqueue } = parsePath(path);
|
|
91
|
+
if (!subqueue)
|
|
92
|
+
throw new QQError('path must include a subqueue: queue/subqueue');
|
|
93
|
+
const db = (0, db_js_1.openDb)(queue);
|
|
94
|
+
try {
|
|
95
|
+
return (0, db_js_1.inTransaction)(db, () => {
|
|
96
|
+
const pattern = toGlob(subqueue);
|
|
97
|
+
const row = db.prepare('SELECT * FROM messages WHERE subqueue LIKE ? AND claimed = 0 ORDER BY seq ASC LIMIT 1').get(pattern);
|
|
98
|
+
if (!row)
|
|
99
|
+
return null;
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
db.prepare('UPDATE messages SET claimed = 1, claimed_by = ?, last_modified = ? WHERE id = ?').run(options.agentId ?? null, now, row.id);
|
|
102
|
+
return rowToMessage({ ...row, claimed: 1, claimed_by: options.agentId ?? null }, true);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
db.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ── release ───────────────────────────────────────────────────────────────────
|
|
110
|
+
function release(queue, id, options = {}) {
|
|
111
|
+
const db = (0, db_js_1.openDb)(queue);
|
|
112
|
+
try {
|
|
113
|
+
(0, db_js_1.inTransaction)(db, () => {
|
|
114
|
+
const row = db.prepare('SELECT * FROM messages WHERE id = ?').get(id);
|
|
115
|
+
if (!row)
|
|
116
|
+
throw new QQError(`message not found: ${id}`);
|
|
117
|
+
if (!row.claimed)
|
|
118
|
+
throw new QQError(`message not claimed: ${id}`);
|
|
119
|
+
const seq = nextSeq(db);
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
const newSubqueue = options.target ?? row.subqueue;
|
|
122
|
+
let newPayload;
|
|
123
|
+
if (options.payload !== undefined) {
|
|
124
|
+
newPayload = options.replace
|
|
125
|
+
? options.payload
|
|
126
|
+
: [row.payload, options.payload].filter(Boolean).join('\n');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
newPayload = row.payload ?? null;
|
|
130
|
+
}
|
|
131
|
+
db.prepare('UPDATE messages SET claimed = 0, claimed_by = NULL, seq = ?, subqueue = ?, payload = ?, last_modified = ? WHERE id = ?').run(seq, newSubqueue, newPayload, now, id);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
db.close();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ── list / batchRead ──────────────────────────────────────────────────────────
|
|
139
|
+
function queryMessages(path, filters, includePayload) {
|
|
140
|
+
const { queue, subqueue } = parsePath(path);
|
|
141
|
+
const db = (0, db_js_1.openDb)(queue);
|
|
142
|
+
try {
|
|
143
|
+
const conditions = [];
|
|
144
|
+
const params = [];
|
|
145
|
+
if (subqueue) {
|
|
146
|
+
conditions.push('subqueue LIKE ?');
|
|
147
|
+
params.push(toGlob(subqueue));
|
|
148
|
+
}
|
|
149
|
+
if (filters.claimed !== undefined) {
|
|
150
|
+
conditions.push('claimed = ?');
|
|
151
|
+
params.push(filters.claimed ? 1 : 0);
|
|
152
|
+
}
|
|
153
|
+
if (filters.claimed_by !== undefined) {
|
|
154
|
+
conditions.push('claimed_by = ?');
|
|
155
|
+
params.push(filters.claimed_by);
|
|
156
|
+
}
|
|
157
|
+
if (filters.ids?.length) {
|
|
158
|
+
conditions.push(`id IN (${filters.ids.map(() => '?').join(', ')})`);
|
|
159
|
+
params.push(...filters.ids);
|
|
160
|
+
}
|
|
161
|
+
if (filters.created_after !== undefined) {
|
|
162
|
+
conditions.push('created > ?');
|
|
163
|
+
params.push(filters.created_after);
|
|
164
|
+
}
|
|
165
|
+
if (filters.created_before !== undefined) {
|
|
166
|
+
conditions.push('created < ?');
|
|
167
|
+
params.push(filters.created_before);
|
|
168
|
+
}
|
|
169
|
+
if (filters.modified_after !== undefined) {
|
|
170
|
+
conditions.push('last_modified > ?');
|
|
171
|
+
params.push(filters.modified_after);
|
|
172
|
+
}
|
|
173
|
+
let sql = 'SELECT * FROM messages';
|
|
174
|
+
if (conditions.length)
|
|
175
|
+
sql += ' WHERE ' + conditions.join(' AND ');
|
|
176
|
+
sql += ' ORDER BY seq ASC';
|
|
177
|
+
if (filters.limit !== undefined) {
|
|
178
|
+
sql += ' LIMIT ?';
|
|
179
|
+
params.push(filters.limit);
|
|
180
|
+
}
|
|
181
|
+
else if (filters.offset !== undefined) {
|
|
182
|
+
sql += ' LIMIT -1';
|
|
183
|
+
}
|
|
184
|
+
if (filters.offset !== undefined) {
|
|
185
|
+
sql += ' OFFSET ?';
|
|
186
|
+
params.push(filters.offset);
|
|
187
|
+
}
|
|
188
|
+
const rows = db.prepare(sql).all(...params);
|
|
189
|
+
return rows.map(r => rowToMessage(r, includePayload));
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
db.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function list(path, filters = {}) {
|
|
196
|
+
return queryMessages(path, filters, false);
|
|
197
|
+
}
|
|
198
|
+
function batchRead(path, filters = {}) {
|
|
199
|
+
return queryMessages(path, filters, true);
|
|
200
|
+
}
|
|
201
|
+
// ── stats ─────────────────────────────────────────────────────────────────────
|
|
202
|
+
function stats(queue) {
|
|
203
|
+
const db = (0, db_js_1.openDb)(queue);
|
|
204
|
+
try {
|
|
205
|
+
const rows = db.prepare('SELECT subqueue, claimed, COUNT(*) as count FROM messages GROUP BY subqueue, claimed').all();
|
|
206
|
+
const result = {};
|
|
207
|
+
for (const row of rows) {
|
|
208
|
+
if (!result[row.subqueue])
|
|
209
|
+
result[row.subqueue] = { total: 0, claimed: 0 };
|
|
210
|
+
result[row.subqueue].total += row.count;
|
|
211
|
+
if (row.claimed)
|
|
212
|
+
result[row.subqueue].claimed += row.count;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
db.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// ── reap ──────────────────────────────────────────────────────────────────────
|
|
221
|
+
function reap(queue, agentId) {
|
|
222
|
+
const db = (0, db_js_1.openDb)(queue);
|
|
223
|
+
try {
|
|
224
|
+
return (0, db_js_1.inTransaction)(db, () => {
|
|
225
|
+
const now = Date.now();
|
|
226
|
+
const result = agentId
|
|
227
|
+
? db.prepare('UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1 AND claimed_by = ?').run(now, agentId)
|
|
228
|
+
: db.prepare('UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1').run(now);
|
|
229
|
+
return result.changes;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
db.close();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/db.d.ts
ADDED
package/dist/db.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAkBtC,wBAAgB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAQ3D;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtE"}
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.openDb = openDb;
|
|
7
|
+
exports.inTransaction = inTransaction;
|
|
8
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const DDL = `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
subqueue TEXT NOT NULL,
|
|
15
|
+
claimed INTEGER NOT NULL DEFAULT 0,
|
|
16
|
+
seq INTEGER NOT NULL,
|
|
17
|
+
payload TEXT,
|
|
18
|
+
claimed_by TEXT,
|
|
19
|
+
created INTEGER NOT NULL,
|
|
20
|
+
last_modified INTEGER NOT NULL
|
|
21
|
+
);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_claim ON messages (subqueue, claimed, seq);
|
|
23
|
+
`;
|
|
24
|
+
function openDb(queueName) {
|
|
25
|
+
const dir = (0, node_path_1.join)(process.cwd(), '.claude', 'qq');
|
|
26
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
27
|
+
const db = new better_sqlite3_1.default((0, node_path_1.join)(dir, `${queueName}.db`));
|
|
28
|
+
db.pragma('journal_mode = WAL');
|
|
29
|
+
db.pragma('busy_timeout = 5000');
|
|
30
|
+
db.exec(DDL);
|
|
31
|
+
return db;
|
|
32
|
+
}
|
|
33
|
+
function inTransaction(db, fn) {
|
|
34
|
+
return db.transaction(fn).immediate();
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":";;;;;AAkBA,wBAQC;AAED,sCAEC;AA9BD,oEAAsC;AACtC,qCAAoC;AACpC,yCAAiC;AAEjC,MAAM,GAAG,GAAG;;;;;;;;;;;;CAYX,CAAC;AAEF,SAAgB,MAAM,CAAC,SAAiB;IACtC,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACjD,IAAA,mBAAS,EAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,IAAI,wBAAQ,CAAC,IAAA,gBAAI,EAAC,GAAG,EAAE,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC;IACtD,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACjC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAgB,aAAa,CAAI,EAAqB,EAAE,EAAW;IACjE,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;AACxC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commander_1 = require("commander");
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const js_yaml_1 = require("js-yaml");
|
|
6
|
+
const commands_js_1 = require("./commands.js");
|
|
7
|
+
function out(data) {
|
|
8
|
+
process.stdout.write((0, js_yaml_1.dump)(data, { lineWidth: -1 }));
|
|
9
|
+
}
|
|
10
|
+
function fail(message) {
|
|
11
|
+
process.stderr.write(`error: ${message}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
function readStdin() {
|
|
15
|
+
if (process.stdin.isTTY)
|
|
16
|
+
return null;
|
|
17
|
+
const raw = (0, node_fs_1.readFileSync)(0, 'utf8').trim();
|
|
18
|
+
return raw || null;
|
|
19
|
+
}
|
|
20
|
+
function parseIntArg(value) {
|
|
21
|
+
const n = parseInt(value, 10);
|
|
22
|
+
if (isNaN(n))
|
|
23
|
+
throw new commander_1.InvalidArgumentError('expected an integer');
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
function collectFilters(opts) {
|
|
27
|
+
const f = {};
|
|
28
|
+
if (opts['claimed'] !== undefined)
|
|
29
|
+
f.claimed = opts['claimed'] === '1';
|
|
30
|
+
if (opts['claimedBy'])
|
|
31
|
+
f.claimed_by = opts['claimedBy'];
|
|
32
|
+
if (opts['ids'])
|
|
33
|
+
f.ids = opts['ids'].split(',');
|
|
34
|
+
if (opts['createdAfter'])
|
|
35
|
+
f.created_after = opts['createdAfter'];
|
|
36
|
+
if (opts['createdBefore'])
|
|
37
|
+
f.created_before = opts['createdBefore'];
|
|
38
|
+
if (opts['modifiedAfter'])
|
|
39
|
+
f.modified_after = opts['modifiedAfter'];
|
|
40
|
+
if (opts['limit'] !== undefined)
|
|
41
|
+
f.limit = opts['limit'];
|
|
42
|
+
if (opts['offset'] !== undefined)
|
|
43
|
+
f.offset = opts['offset'];
|
|
44
|
+
return f;
|
|
45
|
+
}
|
|
46
|
+
function addFilterOptions(cmd) {
|
|
47
|
+
return cmd
|
|
48
|
+
.option('--claimed <0|1>')
|
|
49
|
+
.option('--claimed-by <agent>')
|
|
50
|
+
.option('--ids <id1,id2,...>')
|
|
51
|
+
.option('--created-after <epoch>', '', parseIntArg)
|
|
52
|
+
.option('--created-before <epoch>', '', parseIntArg)
|
|
53
|
+
.option('--modified-after <epoch>', '', parseIntArg)
|
|
54
|
+
.option('--limit <n>', '', parseIntArg)
|
|
55
|
+
.option('--offset <n>', '', parseIntArg);
|
|
56
|
+
}
|
|
57
|
+
const program = new commander_1.Command();
|
|
58
|
+
program.name('qq').description('Persistent queue for Claude agent pipelines');
|
|
59
|
+
program
|
|
60
|
+
.command('make-queue')
|
|
61
|
+
.option('--name <suffix>')
|
|
62
|
+
.action((opts) => {
|
|
63
|
+
try {
|
|
64
|
+
out({ queue: (0, commands_js_1.makeQueue)(opts.name) });
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
program
|
|
71
|
+
.command('delete-queue <queue>')
|
|
72
|
+
.action((queue) => {
|
|
73
|
+
try {
|
|
74
|
+
(0, commands_js_1.deleteQueue)(queue);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
program
|
|
81
|
+
.command('push <path> <id>')
|
|
82
|
+
.action((path, id) => {
|
|
83
|
+
try {
|
|
84
|
+
(0, commands_js_1.push)(path, id, readStdin() ?? undefined);
|
|
85
|
+
out({ ok: true });
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
e instanceof commands_js_1.QQError ? fail(e.message) : fail(String(e));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
program
|
|
92
|
+
.command('claim <path>')
|
|
93
|
+
.option('--agent-id <id>')
|
|
94
|
+
.action((path, opts) => {
|
|
95
|
+
try {
|
|
96
|
+
out((0, commands_js_1.claim)(path, { agentId: opts.agentId }) ?? { empty: true });
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
program
|
|
103
|
+
.command('release <queue> <id>')
|
|
104
|
+
.option('--target <subqueue>')
|
|
105
|
+
.option('--payload', 'read payload from stdin')
|
|
106
|
+
.option('--replace')
|
|
107
|
+
.action((queue, id, opts) => {
|
|
108
|
+
try {
|
|
109
|
+
const releaseOpts = { target: opts.target, replace: opts.replace };
|
|
110
|
+
if (opts.payload)
|
|
111
|
+
releaseOpts.payload = readStdin() ?? undefined;
|
|
112
|
+
(0, commands_js_1.release)(queue, id, releaseOpts);
|
|
113
|
+
out({ ok: true });
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
e instanceof commands_js_1.QQError ? fail(e.message) : fail(String(e));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
addFilterOptions(program.command('list <path>'))
|
|
120
|
+
.action((path, opts) => {
|
|
121
|
+
try {
|
|
122
|
+
out((0, commands_js_1.list)(path, collectFilters(opts)));
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
addFilterOptions(program.command('batch-read <path>'))
|
|
129
|
+
.action((path, opts) => {
|
|
130
|
+
try {
|
|
131
|
+
out((0, commands_js_1.batchRead)(path, collectFilters(opts)));
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
program
|
|
138
|
+
.command('stats <queue>')
|
|
139
|
+
.action((queue) => {
|
|
140
|
+
try {
|
|
141
|
+
out((0, commands_js_1.stats)(queue));
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
program
|
|
148
|
+
.command('reap <queue> [agent-id]')
|
|
149
|
+
.action((queue, agentId) => {
|
|
150
|
+
try {
|
|
151
|
+
out({ reaped: (0, commands_js_1.reap)(queue, agentId) });
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
program.parse();
|
|
158
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quantod/qq",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent queue for multi-stage Claude agent pipelines",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/commands.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/commands.js"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"qq": "./bin/qq"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"dist",
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"better-sqlite3": "^12.0.0",
|
|
23
|
+
"commander": "^13.1.0",
|
|
24
|
+
"js-yaml": "^4.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
28
|
+
"@types/js-yaml": "^4.0.9",
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"typescript": "^5.8.3",
|
|
31
|
+
"vitest": "^3.2.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc --project tsconfig.build.json",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"lint": "tsc --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import type Database from 'better-sqlite3';
|
|
5
|
+
import { openDb, inTransaction } from './db.js';
|
|
6
|
+
|
|
7
|
+
// ── types ──────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export class QQError extends Error {
|
|
10
|
+
constructor(message: string) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'QQError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Message {
|
|
17
|
+
id: string;
|
|
18
|
+
subqueue: string;
|
|
19
|
+
claimed: boolean;
|
|
20
|
+
claimed_by: string | null;
|
|
21
|
+
seq: number;
|
|
22
|
+
created: number;
|
|
23
|
+
last_modified: number;
|
|
24
|
+
payload?: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FilterOptions {
|
|
28
|
+
claimed?: boolean;
|
|
29
|
+
claimed_by?: string;
|
|
30
|
+
ids?: string[];
|
|
31
|
+
created_after?: number;
|
|
32
|
+
created_before?: number;
|
|
33
|
+
modified_after?: number;
|
|
34
|
+
limit?: number;
|
|
35
|
+
offset?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ReleaseOptions {
|
|
39
|
+
target?: string;
|
|
40
|
+
payload?: string;
|
|
41
|
+
replace?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── internal helpers ───────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function parsePath(path: string): { queue: string; subqueue: string | undefined } {
|
|
47
|
+
const slash = path.indexOf('/');
|
|
48
|
+
if (slash === -1) return { queue: path, subqueue: undefined };
|
|
49
|
+
return { queue: path.slice(0, slash), subqueue: path.slice(slash + 1) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toGlob(pattern: string): string {
|
|
53
|
+
return pattern.replace(/\*/g, '%').replace(/\?/g, '_');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function nextSeq(db: Database.Database): number {
|
|
57
|
+
const row = db.prepare('SELECT MAX(seq) as m FROM messages').get() as { m: number | null };
|
|
58
|
+
return (row.m ?? 0) + 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function rowToMessage(row: Record<string, unknown>, includePayload: boolean): Message {
|
|
62
|
+
const msg: Message = {
|
|
63
|
+
id: row.id as string,
|
|
64
|
+
subqueue: row.subqueue as string,
|
|
65
|
+
claimed: Boolean(row.claimed),
|
|
66
|
+
claimed_by: (row.claimed_by as string | null) ?? null,
|
|
67
|
+
seq: row.seq as number,
|
|
68
|
+
created: row.created as number,
|
|
69
|
+
last_modified: row.last_modified as number,
|
|
70
|
+
};
|
|
71
|
+
if (includePayload) msg.payload = (row.payload as string | null) ?? null;
|
|
72
|
+
return msg;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── makeQueue ─────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export function makeQueue(nameSuffix?: string): string {
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
80
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
81
|
+
const suffix = nameSuffix ?? randomBytes(3).toString('hex');
|
|
82
|
+
const name = `${mm}${dd}_${suffix}`;
|
|
83
|
+
const db = openDb(name);
|
|
84
|
+
db.close();
|
|
85
|
+
return name;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── deleteQueue ───────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
export function deleteQueue(queue: string): void {
|
|
91
|
+
rmSync(join(process.cwd(), '.claude', 'qq', `${queue}.db`), { force: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── push ──────────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
export function push(path: string, id: string, payload?: string): void {
|
|
97
|
+
const { queue, subqueue } = parsePath(path);
|
|
98
|
+
if (!subqueue) throw new QQError('path must include a subqueue: queue/subqueue');
|
|
99
|
+
|
|
100
|
+
const db = openDb(queue);
|
|
101
|
+
try {
|
|
102
|
+
inTransaction(db, () => {
|
|
103
|
+
const existing = db.prepare('SELECT id FROM messages WHERE id = ?').get(id);
|
|
104
|
+
if (existing) throw new QQError(`duplicate id: ${id}`);
|
|
105
|
+
const seq = nextSeq(db);
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
db.prepare(
|
|
108
|
+
'INSERT INTO messages (id, subqueue, claimed, seq, payload, created, last_modified) VALUES (?, ?, 0, ?, ?, ?, ?)'
|
|
109
|
+
).run(id, subqueue, seq, payload ?? null, now, now);
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
db.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── claim ─────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
export function claim(path: string, options: { agentId?: string } = {}): Message | null {
|
|
119
|
+
const { queue, subqueue } = parsePath(path);
|
|
120
|
+
if (!subqueue) throw new QQError('path must include a subqueue: queue/subqueue');
|
|
121
|
+
|
|
122
|
+
const db = openDb(queue);
|
|
123
|
+
try {
|
|
124
|
+
return inTransaction(db, () => {
|
|
125
|
+
const pattern = toGlob(subqueue);
|
|
126
|
+
const row = db.prepare(
|
|
127
|
+
'SELECT * FROM messages WHERE subqueue LIKE ? AND claimed = 0 ORDER BY seq ASC LIMIT 1'
|
|
128
|
+
).get(pattern) as Record<string, unknown> | undefined;
|
|
129
|
+
|
|
130
|
+
if (!row) return null;
|
|
131
|
+
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
db.prepare(
|
|
134
|
+
'UPDATE messages SET claimed = 1, claimed_by = ?, last_modified = ? WHERE id = ?'
|
|
135
|
+
).run(options.agentId ?? null, now, row.id as string);
|
|
136
|
+
|
|
137
|
+
return rowToMessage({ ...row, claimed: 1, claimed_by: options.agentId ?? null }, true);
|
|
138
|
+
});
|
|
139
|
+
} finally {
|
|
140
|
+
db.close();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── release ───────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
export function release(queue: string, id: string, options: ReleaseOptions = {}): void {
|
|
147
|
+
const db = openDb(queue);
|
|
148
|
+
try {
|
|
149
|
+
inTransaction(db, () => {
|
|
150
|
+
const row = db.prepare('SELECT * FROM messages WHERE id = ?').get(id) as Record<string, unknown> | undefined;
|
|
151
|
+
if (!row) throw new QQError(`message not found: ${id}`);
|
|
152
|
+
if (!row.claimed) throw new QQError(`message not claimed: ${id}`);
|
|
153
|
+
|
|
154
|
+
const seq = nextSeq(db);
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
const newSubqueue = options.target ?? (row.subqueue as string);
|
|
157
|
+
|
|
158
|
+
let newPayload: string | null;
|
|
159
|
+
if (options.payload !== undefined) {
|
|
160
|
+
newPayload = options.replace
|
|
161
|
+
? options.payload
|
|
162
|
+
: [row.payload as string | null, options.payload].filter(Boolean).join('\n');
|
|
163
|
+
} else {
|
|
164
|
+
newPayload = (row.payload as string | null) ?? null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
db.prepare(
|
|
168
|
+
'UPDATE messages SET claimed = 0, claimed_by = NULL, seq = ?, subqueue = ?, payload = ?, last_modified = ? WHERE id = ?'
|
|
169
|
+
).run(seq, newSubqueue, newPayload, now, id);
|
|
170
|
+
});
|
|
171
|
+
} finally {
|
|
172
|
+
db.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── list / batchRead ──────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
function queryMessages(path: string, filters: FilterOptions, includePayload: boolean): Message[] {
|
|
179
|
+
const { queue, subqueue } = parsePath(path);
|
|
180
|
+
const db = openDb(queue);
|
|
181
|
+
try {
|
|
182
|
+
const conditions: string[] = [];
|
|
183
|
+
const params: (string | number | null)[] = [];
|
|
184
|
+
|
|
185
|
+
if (subqueue) {
|
|
186
|
+
conditions.push('subqueue LIKE ?');
|
|
187
|
+
params.push(toGlob(subqueue));
|
|
188
|
+
}
|
|
189
|
+
if (filters.claimed !== undefined) {
|
|
190
|
+
conditions.push('claimed = ?');
|
|
191
|
+
params.push(filters.claimed ? 1 : 0);
|
|
192
|
+
}
|
|
193
|
+
if (filters.claimed_by !== undefined) {
|
|
194
|
+
conditions.push('claimed_by = ?');
|
|
195
|
+
params.push(filters.claimed_by);
|
|
196
|
+
}
|
|
197
|
+
if (filters.ids?.length) {
|
|
198
|
+
conditions.push(`id IN (${filters.ids.map(() => '?').join(', ')})`);
|
|
199
|
+
params.push(...filters.ids);
|
|
200
|
+
}
|
|
201
|
+
if (filters.created_after !== undefined) {
|
|
202
|
+
conditions.push('created > ?');
|
|
203
|
+
params.push(filters.created_after);
|
|
204
|
+
}
|
|
205
|
+
if (filters.created_before !== undefined) {
|
|
206
|
+
conditions.push('created < ?');
|
|
207
|
+
params.push(filters.created_before);
|
|
208
|
+
}
|
|
209
|
+
if (filters.modified_after !== undefined) {
|
|
210
|
+
conditions.push('last_modified > ?');
|
|
211
|
+
params.push(filters.modified_after);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let sql = 'SELECT * FROM messages';
|
|
215
|
+
if (conditions.length) sql += ' WHERE ' + conditions.join(' AND ');
|
|
216
|
+
sql += ' ORDER BY seq ASC';
|
|
217
|
+
|
|
218
|
+
if (filters.limit !== undefined) {
|
|
219
|
+
sql += ' LIMIT ?';
|
|
220
|
+
params.push(filters.limit);
|
|
221
|
+
} else if (filters.offset !== undefined) {
|
|
222
|
+
sql += ' LIMIT -1';
|
|
223
|
+
}
|
|
224
|
+
if (filters.offset !== undefined) {
|
|
225
|
+
sql += ' OFFSET ?';
|
|
226
|
+
params.push(filters.offset);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const rows = db.prepare(sql).all(...params) as Record<string, unknown>[];
|
|
230
|
+
return rows.map(r => rowToMessage(r, includePayload));
|
|
231
|
+
} finally {
|
|
232
|
+
db.close();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function list(path: string, filters: FilterOptions = {}): Message[] {
|
|
237
|
+
return queryMessages(path, filters, false);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function batchRead(path: string, filters: FilterOptions = {}): Message[] {
|
|
241
|
+
return queryMessages(path, filters, true);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── stats ─────────────────────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
export function stats(queue: string): Record<string, { total: number; claimed: number }> {
|
|
247
|
+
const db = openDb(queue);
|
|
248
|
+
try {
|
|
249
|
+
const rows = db.prepare(
|
|
250
|
+
'SELECT subqueue, claimed, COUNT(*) as count FROM messages GROUP BY subqueue, claimed'
|
|
251
|
+
).all() as { subqueue: string; claimed: number; count: number }[];
|
|
252
|
+
|
|
253
|
+
const result: Record<string, { total: number; claimed: number }> = {};
|
|
254
|
+
for (const row of rows) {
|
|
255
|
+
if (!result[row.subqueue]) result[row.subqueue] = { total: 0, claimed: 0 };
|
|
256
|
+
result[row.subqueue].total += row.count;
|
|
257
|
+
if (row.claimed) result[row.subqueue].claimed += row.count;
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
} finally {
|
|
261
|
+
db.close();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── reap ──────────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
export function reap(queue: string, agentId?: string): number {
|
|
268
|
+
const db = openDb(queue);
|
|
269
|
+
try {
|
|
270
|
+
return inTransaction(db, () => {
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
const result = agentId
|
|
273
|
+
? db.prepare(
|
|
274
|
+
'UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1 AND claimed_by = ?'
|
|
275
|
+
).run(now, agentId)
|
|
276
|
+
: db.prepare(
|
|
277
|
+
'UPDATE messages SET claimed = 0, claimed_by = NULL, last_modified = ? WHERE claimed = 1'
|
|
278
|
+
).run(now);
|
|
279
|
+
return result.changes;
|
|
280
|
+
});
|
|
281
|
+
} finally {
|
|
282
|
+
db.close();
|
|
283
|
+
}
|
|
284
|
+
}
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const DDL = `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
subqueue TEXT NOT NULL,
|
|
9
|
+
claimed INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
seq INTEGER NOT NULL,
|
|
11
|
+
payload TEXT,
|
|
12
|
+
claimed_by TEXT,
|
|
13
|
+
created INTEGER NOT NULL,
|
|
14
|
+
last_modified INTEGER NOT NULL
|
|
15
|
+
);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_claim ON messages (subqueue, claimed, seq);
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export function openDb(queueName: string): Database.Database {
|
|
20
|
+
const dir = join(process.cwd(), '.claude', 'qq');
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
const db = new Database(join(dir, `${queueName}.db`));
|
|
23
|
+
db.pragma('journal_mode = WAL');
|
|
24
|
+
db.pragma('busy_timeout = 5000');
|
|
25
|
+
db.exec(DDL);
|
|
26
|
+
return db;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function inTransaction<T>(db: Database.Database, fn: () => T): T {
|
|
30
|
+
return db.transaction(fn).immediate();
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Command, InvalidArgumentError } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dump } from 'js-yaml';
|
|
4
|
+
import {
|
|
5
|
+
makeQueue, deleteQueue, push, claim, release,
|
|
6
|
+
list, batchRead, stats, reap, QQError,
|
|
7
|
+
type FilterOptions, type ReleaseOptions,
|
|
8
|
+
} from './commands.js';
|
|
9
|
+
|
|
10
|
+
function out(data: unknown): void {
|
|
11
|
+
process.stdout.write(dump(data, { lineWidth: -1 }));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fail(message: string): never {
|
|
15
|
+
process.stderr.write(`error: ${message}\n`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readStdin(): string | null {
|
|
20
|
+
if (process.stdin.isTTY) return null;
|
|
21
|
+
const raw = readFileSync(0, 'utf8').trim();
|
|
22
|
+
return raw || null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseIntArg(value: string): number {
|
|
26
|
+
const n = parseInt(value, 10);
|
|
27
|
+
if (isNaN(n)) throw new InvalidArgumentError('expected an integer');
|
|
28
|
+
return n;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function collectFilters(opts: Record<string, unknown>): FilterOptions {
|
|
32
|
+
const f: FilterOptions = {};
|
|
33
|
+
if (opts['claimed'] !== undefined) f.claimed = opts['claimed'] === '1';
|
|
34
|
+
if (opts['claimedBy']) f.claimed_by = opts['claimedBy'] as string;
|
|
35
|
+
if (opts['ids']) f.ids = (opts['ids'] as string).split(',');
|
|
36
|
+
if (opts['createdAfter']) f.created_after = opts['createdAfter'] as number;
|
|
37
|
+
if (opts['createdBefore']) f.created_before = opts['createdBefore'] as number;
|
|
38
|
+
if (opts['modifiedAfter']) f.modified_after = opts['modifiedAfter'] as number;
|
|
39
|
+
if (opts['limit'] !== undefined) f.limit = opts['limit'] as number;
|
|
40
|
+
if (opts['offset'] !== undefined) f.offset = opts['offset'] as number;
|
|
41
|
+
return f;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addFilterOptions(cmd: Command): Command {
|
|
45
|
+
return cmd
|
|
46
|
+
.option('--claimed <0|1>')
|
|
47
|
+
.option('--claimed-by <agent>')
|
|
48
|
+
.option('--ids <id1,id2,...>')
|
|
49
|
+
.option('--created-after <epoch>', '', parseIntArg)
|
|
50
|
+
.option('--created-before <epoch>', '', parseIntArg)
|
|
51
|
+
.option('--modified-after <epoch>', '', parseIntArg)
|
|
52
|
+
.option('--limit <n>', '', parseIntArg)
|
|
53
|
+
.option('--offset <n>', '', parseIntArg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const program = new Command();
|
|
57
|
+
program.name('qq').description('Persistent queue for Claude agent pipelines');
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command('make-queue')
|
|
61
|
+
.option('--name <suffix>')
|
|
62
|
+
.action((opts) => {
|
|
63
|
+
try { out({ queue: makeQueue(opts.name) }); }
|
|
64
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('delete-queue <queue>')
|
|
69
|
+
.action((queue) => {
|
|
70
|
+
try { deleteQueue(queue); }
|
|
71
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
program
|
|
75
|
+
.command('push <path> <id>')
|
|
76
|
+
.action((path, id) => {
|
|
77
|
+
try { push(path, id, readStdin() ?? undefined); out({ ok: true }); }
|
|
78
|
+
catch (e) { e instanceof QQError ? fail(e.message) : fail(String(e)); }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
program
|
|
82
|
+
.command('claim <path>')
|
|
83
|
+
.option('--agent-id <id>')
|
|
84
|
+
.action((path, opts) => {
|
|
85
|
+
try { out(claim(path, { agentId: opts.agentId }) ?? { empty: true }); }
|
|
86
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
program
|
|
90
|
+
.command('release <queue> <id>')
|
|
91
|
+
.option('--target <subqueue>')
|
|
92
|
+
.option('--payload', 'read payload from stdin')
|
|
93
|
+
.option('--replace')
|
|
94
|
+
.action((queue, id, opts) => {
|
|
95
|
+
try {
|
|
96
|
+
const releaseOpts: ReleaseOptions = { target: opts.target, replace: opts.replace };
|
|
97
|
+
if (opts.payload) releaseOpts.payload = readStdin() ?? undefined;
|
|
98
|
+
release(queue, id, releaseOpts);
|
|
99
|
+
out({ ok: true });
|
|
100
|
+
} catch (e) { e instanceof QQError ? fail(e.message) : fail(String(e)); }
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
addFilterOptions(program.command('list <path>'))
|
|
104
|
+
.action((path, opts) => {
|
|
105
|
+
try { out(list(path, collectFilters(opts))); }
|
|
106
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
addFilterOptions(program.command('batch-read <path>'))
|
|
110
|
+
.action((path, opts) => {
|
|
111
|
+
try { out(batchRead(path, collectFilters(opts))); }
|
|
112
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
program
|
|
116
|
+
.command('stats <queue>')
|
|
117
|
+
.action((queue) => {
|
|
118
|
+
try { out(stats(queue)); }
|
|
119
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
program
|
|
123
|
+
.command('reap <queue> [agent-id]')
|
|
124
|
+
.action((queue, agentId) => {
|
|
125
|
+
try { out({ reaped: reap(queue, agentId) }); }
|
|
126
|
+
catch (e) { fail(e instanceof Error ? e.message : String(e)); }
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
program.parse();
|