claude-yes 1.33.0 → 1.35.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/ts/pidStore.ts CHANGED
@@ -1,12 +1,12 @@
1
- import Datastore from "@seald-io/nedb";
2
1
  import { mkdir } from "fs/promises";
3
2
  import path from "path";
4
3
  import { logger } from "./logger.ts";
5
4
 
6
5
  export interface PidRecord {
6
+ id?: number;
7
7
  pid: number;
8
8
  cli: string;
9
- args: string[];
9
+ args: string;
10
10
  prompt?: string;
11
11
  logFile: string;
12
12
  fifoFile: string;
@@ -17,23 +17,95 @@ export interface PidRecord {
17
17
  updatedAt: number;
18
18
  }
19
19
 
20
+ // Direct SQLite implementation to avoid Kysely compatibility issues
21
+ class SqliteAdapter {
22
+ private db: any;
23
+
24
+ async init(dbPath: string) {
25
+ if (typeof globalThis.Bun !== "undefined") {
26
+ try {
27
+ const { Database } = await import("bun:sqlite");
28
+ this.db = new Database(dbPath);
29
+ } catch (error) {
30
+ logger.warn("[pidStore] bun:sqlite not available, falling back to better-sqlite3");
31
+ const Database = (await import("better-sqlite3")).default;
32
+ this.db = new Database(dbPath);
33
+ }
34
+ } else {
35
+ const Database = (await import("better-sqlite3")).default;
36
+ this.db = new Database(dbPath);
37
+ }
38
+ }
39
+
40
+ query(sql: string, params: any[] = []): any[] {
41
+ if (typeof this.db.prepare === "function") {
42
+ // better-sqlite3 style
43
+ return this.db.prepare(sql).all(params);
44
+ } else {
45
+ // bun:sqlite style
46
+ return this.db.query(sql).all(params);
47
+ }
48
+ }
49
+
50
+ run(sql: string, params: any[] = []): { lastInsertRowid?: number; changes?: number } {
51
+ if (typeof this.db.prepare === "function") {
52
+ // better-sqlite3 style
53
+ return this.db.prepare(sql).run(params);
54
+ } else {
55
+ // bun:sqlite style
56
+ this.db.run(sql, params);
57
+ return {}; // Bun doesn't return metadata in the same way
58
+ }
59
+ }
60
+
61
+ close() {
62
+ if (this.db.close) {
63
+ this.db.close();
64
+ }
65
+ }
66
+ }
67
+
20
68
  export class PidStore {
21
- protected db!: Datastore<PidRecord>;
69
+ protected db!: SqliteAdapter;
22
70
  private baseDir: string;
71
+ private dbPath: string;
23
72
 
24
73
  constructor(workingDir: string) {
25
74
  this.baseDir = path.resolve(workingDir, ".agent-yes");
75
+ this.dbPath = path.join(this.baseDir, "pid.sqlite");
26
76
  }
27
77
 
28
78
  async init(): Promise<void> {
29
79
  await mkdir(path.join(this.baseDir, "logs"), { recursive: true });
30
80
  await mkdir(path.join(this.baseDir, "fifo"), { recursive: true });
31
81
 
32
- this.db = new Datastore<PidRecord>({
33
- filename: path.join(this.baseDir, "pid.jsonl"),
34
- autoload: true,
35
- });
36
- await this.db.loadDatabaseAsync();
82
+ this.db = new SqliteAdapter();
83
+ await this.db.init(this.dbPath);
84
+
85
+ // Enable WAL mode for better concurrency and performance
86
+ this.db.run("PRAGMA journal_mode=WAL");
87
+ this.db.run("PRAGMA synchronous=NORMAL");
88
+ this.db.run("PRAGMA cache_size=1000");
89
+ this.db.run("PRAGMA temp_store=memory");
90
+
91
+ // Create table if it doesn't exist
92
+ this.db.run(`
93
+ CREATE TABLE IF NOT EXISTS pid_records (
94
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
95
+ pid INTEGER NOT NULL UNIQUE,
96
+ cli TEXT NOT NULL,
97
+ args TEXT NOT NULL,
98
+ prompt TEXT,
99
+ logFile TEXT NOT NULL,
100
+ fifoFile TEXT NOT NULL,
101
+ status TEXT NOT NULL DEFAULT 'active',
102
+ exitReason TEXT NOT NULL DEFAULT '',
103
+ exitCode INTEGER,
104
+ startedAt INTEGER NOT NULL,
105
+ updatedAt INTEGER NOT NULL
106
+ )
107
+ `);
108
+
37
109
  await this.cleanStaleRecords();
38
110
  }
39
111
 
@@ -49,21 +121,40 @@ export class PidStore {
49
121
  prompt?: string;
50
122
  }): Promise<PidRecord> {
51
123
  const now = Date.now();
52
- const record: PidRecord = {
53
- pid,
54
- cli,
55
- args,
56
- prompt,
57
- logFile: this.getLogPath(pid),
58
- fifoFile: this.getFifoPath(pid),
59
- status: "active",
60
- exitReason: "",
61
- startedAt: now,
62
- updatedAt: now,
63
- };
64
- await this.db.insertAsync(record);
124
+ const argsJson = JSON.stringify(args);
125
+ const logFile = this.getLogPath(pid);
126
+ const fifoFile = this.getFifoPath(pid);
127
+
128
+ try {
129
+ this.db.run(`
130
+ INSERT INTO pid_records (pid, cli, args, prompt, logFile, fifoFile, status, exitReason, startedAt, updatedAt)
131
+ VALUES (?, ?, ?, ?, ?, ?, 'active', '', ?, ?)
132
+ `, [pid, cli, argsJson, prompt, logFile, fifoFile, now, now]);
133
+ } catch (error: any) {
134
+ // Handle unique constraint violation by updating existing record
135
+ if (error.code === "SQLITE_CONSTRAINT_UNIQUE") {
136
+ this.db.run(`
137
+ UPDATE pid_records
138
+ SET cli = ?, args = ?, prompt = ?, logFile = ?, fifoFile = ?, status = 'active', exitReason = '', startedAt = ?, updatedAt = ?
139
+ WHERE pid = ?
140
+ `, [cli, argsJson, prompt, logFile, fifoFile, now, now, pid]);
141
+ } else {
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ // Fetch the record
147
+ const result = this.db.query("SELECT * FROM pid_records WHERE pid = ?", [pid])[0];
148
+
149
+ if (!result) {
150
+ // Log all records for debugging
151
+ const allRecords = this.db.query("SELECT * FROM pid_records");
152
+ logger.error(`[pidStore] Failed to find record for PID ${pid}. All records:`, allRecords);
153
+ throw new Error(`Failed to register process ${pid}`);
154
+ }
155
+
65
156
  logger.debug(`[pidStore] Registered process ${pid}`);
66
- return record;
157
+ return result;
67
158
  }
68
159
 
69
160
  async updateStatus(
@@ -71,12 +162,22 @@ export class PidStore {
71
162
  status: PidRecord["status"],
72
163
  extra?: { exitReason?: string; exitCode?: number },
73
164
  ): Promise<void> {
74
- const update: Partial<PidRecord> = {
75
- status,
76
- updatedAt: Date.now(),
77
- ...extra,
78
- };
79
- await this.db.updateAsync({ pid }, { $set: update }, {});
165
+ const updatedAt = Date.now();
166
+ const exitReason = extra?.exitReason || "";
167
+ const exitCode = extra?.exitCode;
168
+
169
+ if (exitCode !== undefined) {
170
+ this.db.run(
171
+ "UPDATE pid_records SET status = ?, exitReason = ?, exitCode = ?, updatedAt = ? WHERE pid = ?",
172
+ [status, exitReason, exitCode, updatedAt, pid]
173
+ );
174
+ } else {
175
+ this.db.run(
176
+ "UPDATE pid_records SET status = ?, exitReason = ?, updatedAt = ? WHERE pid = ?",
177
+ [status, exitReason, updatedAt, pid]
178
+ );
179
+ }
180
+
80
181
  logger.debug(`[pidStore] Updated process ${pid} status=${status}`);
81
182
  }
82
183
 
@@ -89,30 +190,31 @@ export class PidStore {
89
190
  }
90
191
 
91
192
  async cleanStaleRecords(): Promise<void> {
92
- const activeRecords = await this.db.findAsync({
93
- status: { $ne: "exited" } as any,
94
- });
193
+ const activeRecords = this.db.query("SELECT * FROM pid_records WHERE status != 'exited'");
194
+
95
195
  for (const record of activeRecords) {
96
196
  if (!this.isProcessAlive(record.pid)) {
97
- await this.db.updateAsync(
98
- { pid: record.pid },
99
- {
100
- $set: {
101
- status: "exited" as const,
102
- exitReason: "stale-cleanup",
103
- updatedAt: Date.now(),
104
- },
105
- },
106
- {},
197
+ this.db.run(
198
+ "UPDATE pid_records SET status = 'exited', exitReason = 'stale-cleanup', updatedAt = ? WHERE pid = ?",
199
+ [Date.now(), record.pid]
107
200
  );
201
+
108
202
  logger.debug(`[pidStore] Cleaned stale record for PID ${record.pid}`);
109
203
  }
110
204
  }
111
205
  }
112
206
 
113
207
  async close(): Promise<void> {
114
- await this.db.compactDatafileAsync();
115
- logger.debug("[pidStore] Database compacted and closed");
208
+ // Optimize the database (equivalent to compacting in nedb)
209
+ try {
210
+ this.db.run("PRAGMA optimize");
211
+ this.db.run("VACUUM");
212
+ } catch (error) {
213
+ logger.warn("[pidStore] Failed to optimize database:", error);
214
+ }
215
+
216
+ this.db.close();
217
+ logger.debug("[pidStore] Database optimized and closed");
116
218
  }
117
219
 
118
220
  private isProcessAlive(pid: number): boolean {
@@ -127,9 +229,12 @@ export class PidStore {
127
229
  static async findActiveFifo(workingDir: string): Promise<string | null> {
128
230
  const store = new PidStore(workingDir);
129
231
  await store.init();
130
- const records = await store.db.findAsync({ status: { $ne: "exited" } as any });
232
+
233
+ const records = store.db.query(
234
+ "SELECT * FROM pid_records WHERE status != 'exited' ORDER BY startedAt DESC LIMIT 1"
235
+ );
236
+
131
237
  await store.close();
132
- const sorted = records.sort((a, b) => b.startedAt - a.startedAt);
133
- return sorted[0]?.fifoFile ?? null;
238
+ return records[0]?.fifoFile ?? null;
134
239
  }
135
- }
240
+ }