@ai-devkit/agent-manager 0.26.1 → 0.26.2
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/dist/__tests__/AgentManager.test.js +37 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/utils/AgentRegistry.test.js +68 -46
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
- package/dist/database/connection.d.ts +22 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +58 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/index.d.ts +4 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +4 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/migrations/001_initial.sql +12 -0
- package/dist/database/schema.d.ts +4 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +47 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/utils/AgentRegistry.d.ts +7 -3
- package/dist/utils/AgentRegistry.d.ts.map +1 -1
- package/dist/utils/AgentRegistry.js +108 -57
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/AgentManager.test.ts +37 -0
- package/src/__tests__/utils/AgentRegistry.test.ts +48 -40
- package/src/database/connection.ts +74 -0
- package/src/database/index.ts +7 -0
- package/src/database/migrations/001_initial.sql +12 -0
- package/src/database/schema.ts +62 -0
- package/src/utils/AgentRegistry.ts +109 -49
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/database/connection.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { mkdirSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { homedir } from 'os';\nimport { initializeSchema } from './schema.js';\n\nexport const DEFAULT_AGENT_REGISTRY_DB_PATH = join(homedir(), '.ai-devkit', 'agents.db');\n\nexport interface DatabaseOptions {\n dbPath?: string;\n verbose?: boolean;\n readonly?: boolean;\n}\n\nexport function resolveAgentRegistryDbPath(filePath?: string): string {\n if (!filePath) return DEFAULT_AGENT_REGISTRY_DB_PATH;\n return filePath.endsWith('.json') ? filePath.replace(/\\.json$/, '.db') : filePath;\n}\n\nexport class DatabaseConnection {\n private db: Database.Database;\n private readonly dbPath: string;\n\n constructor(options: DatabaseOptions = {}) {\n this.dbPath = options.dbPath ?? DEFAULT_AGENT_REGISTRY_DB_PATH;\n mkdirSync(dirname(this.dbPath), { recursive: true });\n\n this.db = new Database(this.dbPath, {\n readonly: options.readonly ?? false,\n verbose: options.verbose ? console.log : undefined,\n });\n\n this.configure();\n initializeSchema(this);\n }\n\n private configure(): void {\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('synchronous = NORMAL');\n this.db.pragma('busy_timeout = 5000');\n this.db.pragma('mmap_size = 268435456');\n }\n\n get instance(): Database.Database {\n return this.db;\n }\n\n get path(): string {\n return this.dbPath;\n }\n\n query<T>(sql: string, params: unknown[] = []): T[] {\n return this.db.prepare(sql).all(...params) as T[];\n }\n\n queryOne<T>(sql: string, params: unknown[] = []): T | undefined {\n return this.db.prepare(sql).get(...params) as T | undefined;\n }\n\n execute(sql: string, params: unknown[] = []): Database.RunResult {\n return this.db.prepare(sql).run(...params);\n }\n\n transaction<T>(fn: () => T): T {\n return this.db.transaction(fn)();\n }\n\n close(): void {\n if (this.db.open) {\n this.db.close();\n }\n }\n}\n"],"names":["Database","mkdirSync","dirname","join","homedir","initializeSchema","DEFAULT_AGENT_REGISTRY_DB_PATH","resolveAgentRegistryDbPath","filePath","endsWith","replace","DatabaseConnection","db","dbPath","options","recursive","readonly","verbose","console","log","undefined","configure","pragma","instance","path","query","sql","params","prepare","all","queryOne","get","execute","run","transaction","fn","close","open"],"mappings":"AAAA,OAAOA,cAAc,iBAAiB;AACtC,SAASC,SAAS,QAAQ,KAAK;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,OAAO;AACrC,SAASC,OAAO,QAAQ,KAAK;AAC7B,SAASC,gBAAgB,QAAQ,cAAc;AAE/C,OAAO,MAAMC,iCAAiCH,KAAKC,WAAW,cAAc,aAAa;AAQzF,OAAO,SAASG,2BAA2BC,QAAiB;IACxD,IAAI,CAACA,UAAU,OAAOF;IACtB,OAAOE,SAASC,QAAQ,CAAC,WAAWD,SAASE,OAAO,CAAC,WAAW,SAASF;AAC7E;AAEA,OAAO,MAAMG;IACDC,GAAsB;IACbC,OAAe;IAEhC,YAAYC,UAA2B,CAAC,CAAC,CAAE;QACvC,IAAI,CAACD,MAAM,GAAGC,QAAQD,MAAM,IAAIP;QAChCL,UAAUC,QAAQ,IAAI,CAACW,MAAM,GAAG;YAAEE,WAAW;QAAK;QAElD,IAAI,CAACH,EAAE,GAAG,IAAIZ,SAAS,IAAI,CAACa,MAAM,EAAE;YAChCG,UAAUF,QAAQE,QAAQ,IAAI;YAC9BC,SAASH,QAAQG,OAAO,GAAGC,QAAQC,GAAG,GAAGC;QAC7C;QAEA,IAAI,CAACC,SAAS;QACdhB,iBAAiB,IAAI;IACzB;IAEQgB,YAAkB;QACtB,IAAI,CAACT,EAAE,CAACU,MAAM,CAAC;QACf,IAAI,CAACV,EAAE,CAACU,MAAM,CAAC;QACf,IAAI,CAACV,EAAE,CAACU,MAAM,CAAC;QACf,IAAI,CAACV,EAAE,CAACU,MAAM,CAAC;QACf,IAAI,CAACV,EAAE,CAACU,MAAM,CAAC;IACnB;IAEA,IAAIC,WAA8B;QAC9B,OAAO,IAAI,CAACX,EAAE;IAClB;IAEA,IAAIY,OAAe;QACf,OAAO,IAAI,CAACX,MAAM;IACtB;IAEAY,MAASC,GAAW,EAAEC,SAAoB,EAAE,EAAO;QAC/C,OAAO,IAAI,CAACf,EAAE,CAACgB,OAAO,CAACF,KAAKG,GAAG,IAAIF;IACvC;IAEAG,SAAYJ,GAAW,EAAEC,SAAoB,EAAE,EAAiB;QAC5D,OAAO,IAAI,CAACf,EAAE,CAACgB,OAAO,CAACF,KAAKK,GAAG,IAAIJ;IACvC;IAEAK,QAAQN,GAAW,EAAEC,SAAoB,EAAE,EAAsB;QAC7D,OAAO,IAAI,CAACf,EAAE,CAACgB,OAAO,CAACF,KAAKO,GAAG,IAAIN;IACvC;IAEAO,YAAeC,EAAW,EAAK;QAC3B,OAAO,IAAI,CAACvB,EAAE,CAACsB,WAAW,CAACC;IAC/B;IAEAC,QAAc;QACV,IAAI,IAAI,CAACxB,EAAE,CAACyB,IAAI,EAAE;YACd,IAAI,CAACzB,EAAE,CAACwB,KAAK;QACjB;IACJ;AACJ"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DatabaseConnection, DEFAULT_AGENT_REGISTRY_DB_PATH, resolveAgentRegistryDbPath, } from './connection.js';
|
|
2
|
+
export type { DatabaseOptions } from './connection.js';
|
|
3
|
+
export { getSchemaVersion, initializeSchema } from './schema.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,kBAAkB,EAClB,8BAA8B,EAC9B,0BAA0B,GAC7B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/database/index.ts"],"sourcesContent":["export {\n DatabaseConnection,\n DEFAULT_AGENT_REGISTRY_DB_PATH,\n resolveAgentRegistryDbPath,\n} from './connection.js';\nexport type { DatabaseOptions } from './connection.js';\nexport { getSchemaVersion, initializeSchema } from './schema.js';\n"],"names":["DatabaseConnection","DEFAULT_AGENT_REGISTRY_DB_PATH","resolveAgentRegistryDbPath","getSchemaVersion","initializeSchema"],"mappings":"AAAA,SACIA,kBAAkB,EAClBC,8BAA8B,EAC9BC,0BAA0B,QACvB,kBAAkB;AAEzB,SAASC,gBAAgB,EAAEC,gBAAgB,QAAQ,cAAc"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
2
|
+
type TEXT NOT NULL,
|
|
3
|
+
pid INTEGER NOT NULL,
|
|
4
|
+
name TEXT NOT NULL UNIQUE,
|
|
5
|
+
tmux_session TEXT NOT NULL DEFAULT '',
|
|
6
|
+
cwd TEXT NOT NULL DEFAULT '',
|
|
7
|
+
started_at TEXT NOT NULL,
|
|
8
|
+
session_id TEXT NOT NULL DEFAULT '',
|
|
9
|
+
session_file_path TEXT NOT NULL DEFAULT '',
|
|
10
|
+
updated_at TEXT NOT NULL,
|
|
11
|
+
PRIMARY KEY (type, pid)
|
|
12
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/database/schema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAI1D,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,kBAAkB,GAAG,MAAM,CAG/D;AAuCD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,kBAAkB,GAAG,IAAI,CAY7D"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export function getSchemaVersion(db) {
|
|
6
|
+
const result = db.instance.pragma('user_version');
|
|
7
|
+
return result[0]?.user_version ?? 0;
|
|
8
|
+
}
|
|
9
|
+
function setSchemaVersion(db, version) {
|
|
10
|
+
db.instance.pragma(`user_version = ${version}`);
|
|
11
|
+
}
|
|
12
|
+
function getMigrationsDir() {
|
|
13
|
+
return join(__dirname, 'migrations');
|
|
14
|
+
}
|
|
15
|
+
function getMigrationFiles() {
|
|
16
|
+
const migrationsDir = getMigrationsDir();
|
|
17
|
+
let files;
|
|
18
|
+
try {
|
|
19
|
+
files = readdirSync(migrationsDir).filter((f)=>f.endsWith('.sql')).sort();
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return files.map((file)=>{
|
|
24
|
+
const match = file.match(/^(\d+)_(.+)\.sql$/);
|
|
25
|
+
if (!match || !match[1] || !match[2]) {
|
|
26
|
+
throw new Error(`Invalid migration filename: ${file}. Expected format: 001_name.sql`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
version: parseInt(match[1], 10),
|
|
30
|
+
name: match[2],
|
|
31
|
+
path: join(migrationsDir, file)
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function initializeSchema(db) {
|
|
36
|
+
const currentVersion = getSchemaVersion(db);
|
|
37
|
+
const pendingMigrations = getMigrationFiles().filter((m)=>m.version > currentVersion);
|
|
38
|
+
for (const migration of pendingMigrations){
|
|
39
|
+
const sql = readFileSync(migration.path, 'utf-8');
|
|
40
|
+
db.transaction(()=>{
|
|
41
|
+
db.instance.exec(sql);
|
|
42
|
+
setSchemaVersion(db, migration.version);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/database/schema.ts"],"sourcesContent":["import { readFileSync, readdirSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport type { DatabaseConnection } from './connection.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport function getSchemaVersion(db: DatabaseConnection): number {\n const result = db.instance.pragma('user_version') as { user_version: number }[];\n return result[0]?.user_version ?? 0;\n}\n\nfunction setSchemaVersion(db: DatabaseConnection, version: number): void {\n db.instance.pragma(`user_version = ${version}`);\n}\n\nfunction getMigrationsDir(): string {\n return join(__dirname, 'migrations');\n}\n\ninterface Migration {\n version: number;\n path: string;\n name: string;\n}\n\nfunction getMigrationFiles(): Migration[] {\n const migrationsDir = getMigrationsDir();\n\n let files: string[];\n try {\n files = readdirSync(migrationsDir).filter((f) => f.endsWith('.sql')).sort();\n } catch {\n return [];\n }\n\n return files.map((file) => {\n const match = file.match(/^(\\d+)_(.+)\\.sql$/);\n if (!match || !match[1] || !match[2]) {\n throw new Error(`Invalid migration filename: ${file}. Expected format: 001_name.sql`);\n }\n return {\n version: parseInt(match[1], 10),\n name: match[2],\n path: join(migrationsDir, file),\n };\n });\n}\n\nexport function initializeSchema(db: DatabaseConnection): void {\n const currentVersion = getSchemaVersion(db);\n const pendingMigrations = getMigrationFiles().filter((m) => m.version > currentVersion);\n\n for (const migration of pendingMigrations) {\n const sql = readFileSync(migration.path, 'utf-8');\n\n db.transaction(() => {\n db.instance.exec(sql);\n setSchemaVersion(db, migration.version);\n });\n }\n}\n"],"names":["readFileSync","readdirSync","dirname","join","fileURLToPath","__dirname","url","getSchemaVersion","db","result","instance","pragma","user_version","setSchemaVersion","version","getMigrationsDir","getMigrationFiles","migrationsDir","files","filter","f","endsWith","sort","map","file","match","Error","parseInt","name","path","initializeSchema","currentVersion","pendingMigrations","m","migration","sql","transaction","exec"],"mappings":"AAAA,SAASA,YAAY,EAAEC,WAAW,QAAQ,KAAK;AAC/C,SAASC,OAAO,EAAEC,IAAI,QAAQ,OAAO;AACrC,SAASC,aAAa,QAAQ,MAAM;AAGpC,MAAMC,YAAYH,QAAQE,cAAc,YAAYE,GAAG;AAEvD,OAAO,SAASC,iBAAiBC,EAAsB;IACnD,MAAMC,SAASD,GAAGE,QAAQ,CAACC,MAAM,CAAC;IAClC,OAAOF,MAAM,CAAC,EAAE,EAAEG,gBAAgB;AACtC;AAEA,SAASC,iBAAiBL,EAAsB,EAAEM,OAAe;IAC7DN,GAAGE,QAAQ,CAACC,MAAM,CAAC,CAAC,eAAe,EAAEG,SAAS;AAClD;AAEA,SAASC;IACL,OAAOZ,KAAKE,WAAW;AAC3B;AAQA,SAASW;IACL,MAAMC,gBAAgBF;IAEtB,IAAIG;IACJ,IAAI;QACAA,QAAQjB,YAAYgB,eAAeE,MAAM,CAAC,CAACC,IAAMA,EAAEC,QAAQ,CAAC,SAASC,IAAI;IAC7E,EAAE,OAAM;QACJ,OAAO,EAAE;IACb;IAEA,OAAOJ,MAAMK,GAAG,CAAC,CAACC;QACd,MAAMC,QAAQD,KAAKC,KAAK,CAAC;QACzB,IAAI,CAACA,SAAS,CAACA,KAAK,CAAC,EAAE,IAAI,CAACA,KAAK,CAAC,EAAE,EAAE;YAClC,MAAM,IAAIC,MAAM,CAAC,4BAA4B,EAAEF,KAAK,+BAA+B,CAAC;QACxF;QACA,OAAO;YACHV,SAASa,SAASF,KAAK,CAAC,EAAE,EAAE;YAC5BG,MAAMH,KAAK,CAAC,EAAE;YACdI,MAAM1B,KAAKc,eAAeO;QAC9B;IACJ;AACJ;AAEA,OAAO,SAASM,iBAAiBtB,EAAsB;IACnD,MAAMuB,iBAAiBxB,iBAAiBC;IACxC,MAAMwB,oBAAoBhB,oBAAoBG,MAAM,CAAC,CAACc,IAAMA,EAAEnB,OAAO,GAAGiB;IAExE,KAAK,MAAMG,aAAaF,kBAAmB;QACvC,MAAMG,MAAMnC,aAAakC,UAAUL,IAAI,EAAE;QAEzCrB,GAAG4B,WAAW,CAAC;YACX5B,GAAGE,QAAQ,CAAC2B,IAAI,CAACF;YACjBtB,iBAAiBL,IAAI0B,UAAUpB,OAAO;QAC1C;IACJ;AACJ"}
|
|
@@ -18,12 +18,16 @@ export interface RegistryEntry {
|
|
|
18
18
|
sessionFilePath: string;
|
|
19
19
|
}
|
|
20
20
|
export declare class AgentRegistry {
|
|
21
|
-
private
|
|
21
|
+
private db;
|
|
22
22
|
constructor(filePath?: string);
|
|
23
23
|
static default(): AgentRegistry;
|
|
24
|
-
private
|
|
25
|
-
private writeFile;
|
|
24
|
+
private rowToEntry;
|
|
26
25
|
private mergeEntry;
|
|
26
|
+
private findByIdentity;
|
|
27
|
+
private findByName;
|
|
28
|
+
private deleteNameConflict;
|
|
29
|
+
private insertOrUpdate;
|
|
30
|
+
private save;
|
|
27
31
|
isAlive(entry: RegistryEntry): boolean;
|
|
28
32
|
prune(): void;
|
|
29
33
|
register(entry: RegistryEntry): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentRegistry.d.ts","sourceRoot":"","sources":["../../src/utils/AgentRegistry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AgentRegistry.d.ts","sourceRoot":"","sources":["../../src/utils/AgentRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAM7D,qBAAa,mBAAoB,SAAQ,KAAK;IACvB,SAAS,EAAE,MAAM;gBAAjB,SAAS,EAAE,MAAM;CAIvC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IACvB,SAAS,EAAE,MAAM;gBAAjB,SAAS,EAAE,MAAM;CAIvC;AAED,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAkBD,qBAAa,aAAa;IACtB,OAAO,CAAC,EAAE,CAAqB;gBAEnB,QAAQ,GAAE,MAA8B;IAIpD,MAAM,CAAC,OAAO,IAAI,aAAa;IAO/B,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO;IAStC,KAAK,IAAI,IAAI;IAUb,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAIpC,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,IAAI;IAU7C,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBlD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAI1C,IAAI,IAAI,aAAa,EAAE;CAI1B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
3
|
+
import { DatabaseConnection, resolveAgentRegistryDbPath } from '../database/index.js';
|
|
4
4
|
export class RenameNotFoundError extends Error {
|
|
5
5
|
agentName;
|
|
6
6
|
constructor(agentName){
|
|
@@ -18,9 +18,11 @@ export class RenameConflictError extends Error {
|
|
|
18
18
|
const DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.ai-devkit', 'agents.json');
|
|
19
19
|
let defaultInstance = null;
|
|
20
20
|
export class AgentRegistry {
|
|
21
|
-
|
|
21
|
+
db;
|
|
22
22
|
constructor(filePath = DEFAULT_REGISTRY_PATH){
|
|
23
|
-
this.
|
|
23
|
+
this.db = new DatabaseConnection({
|
|
24
|
+
dbPath: resolveAgentRegistryDbPath(filePath)
|
|
25
|
+
});
|
|
24
26
|
}
|
|
25
27
|
static default() {
|
|
26
28
|
if (!defaultInstance) {
|
|
@@ -28,35 +30,80 @@ export class AgentRegistry {
|
|
|
28
30
|
}
|
|
29
31
|
return defaultInstance;
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
writeFile(data) {
|
|
45
|
-
const dir = path.dirname(this.filePath);
|
|
46
|
-
fs.mkdirSync(dir, {
|
|
47
|
-
recursive: true
|
|
48
|
-
});
|
|
49
|
-
const tmp = `${this.filePath}.tmp`;
|
|
50
|
-
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
|
|
51
|
-
fs.renameSync(tmp, this.filePath);
|
|
33
|
+
rowToEntry(row) {
|
|
34
|
+
return {
|
|
35
|
+
name: row.name,
|
|
36
|
+
type: row.type,
|
|
37
|
+
pid: row.pid,
|
|
38
|
+
tmuxSession: row.tmux_session,
|
|
39
|
+
cwd: row.cwd,
|
|
40
|
+
startedAt: row.started_at,
|
|
41
|
+
sessionId: row.session_id,
|
|
42
|
+
sessionFilePath: row.session_file_path
|
|
43
|
+
};
|
|
52
44
|
}
|
|
53
45
|
mergeEntry(incoming, existing) {
|
|
54
46
|
if (!existing) return incoming;
|
|
47
|
+
const incomingIsManaged = Boolean(incoming.tmuxSession);
|
|
55
48
|
return {
|
|
56
|
-
...
|
|
57
|
-
|
|
49
|
+
...existing,
|
|
50
|
+
name: incomingIsManaged ? incoming.name : existing.name,
|
|
51
|
+
tmuxSession: incoming.tmuxSession || existing.tmuxSession,
|
|
52
|
+
cwd: incoming.cwd || existing.cwd,
|
|
53
|
+
startedAt: existing.startedAt || incoming.startedAt,
|
|
54
|
+
sessionId: incoming.sessionId || existing.sessionId,
|
|
55
|
+
sessionFilePath: incoming.sessionFilePath || existing.sessionFilePath
|
|
58
56
|
};
|
|
59
57
|
}
|
|
58
|
+
findByIdentity(type, pid) {
|
|
59
|
+
const row = this.db.queryOne('SELECT * FROM agents WHERE type = ? AND pid = ?', [
|
|
60
|
+
type,
|
|
61
|
+
pid
|
|
62
|
+
]);
|
|
63
|
+
return row ? this.rowToEntry(row) : undefined;
|
|
64
|
+
}
|
|
65
|
+
findByName(name) {
|
|
66
|
+
const row = this.db.queryOne('SELECT * FROM agents WHERE name = ?', [
|
|
67
|
+
name
|
|
68
|
+
]);
|
|
69
|
+
return row ? this.rowToEntry(row) : undefined;
|
|
70
|
+
}
|
|
71
|
+
deleteNameConflict(name, type, pid) {
|
|
72
|
+
const conflict = this.findByName(name);
|
|
73
|
+
if (!conflict) return;
|
|
74
|
+
if (conflict.type === type && conflict.pid === pid) return;
|
|
75
|
+
if (!this.isAlive(conflict)) {
|
|
76
|
+
this.db.execute('DELETE FROM agents WHERE type = ? AND pid = ?', [
|
|
77
|
+
conflict.type,
|
|
78
|
+
conflict.pid
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
insertOrUpdate(entry) {
|
|
83
|
+
this.db.instance.prepare(`
|
|
84
|
+
INSERT INTO agents (
|
|
85
|
+
type, pid, name, tmux_session, cwd, started_at, session_id, session_file_path, updated_at
|
|
86
|
+
)
|
|
87
|
+
VALUES (
|
|
88
|
+
@type, @pid, @name, @tmuxSession, @cwd, @startedAt, @sessionId, @sessionFilePath, @updatedAt
|
|
89
|
+
)
|
|
90
|
+
ON CONFLICT(type, pid) DO UPDATE SET
|
|
91
|
+
name = excluded.name,
|
|
92
|
+
tmux_session = excluded.tmux_session,
|
|
93
|
+
cwd = excluded.cwd,
|
|
94
|
+
started_at = agents.started_at,
|
|
95
|
+
session_id = excluded.session_id,
|
|
96
|
+
session_file_path = excluded.session_file_path,
|
|
97
|
+
updated_at = excluded.updated_at
|
|
98
|
+
`).run({
|
|
99
|
+
...entry,
|
|
100
|
+
updatedAt: new Date().toISOString()
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
save(entry) {
|
|
104
|
+
this.deleteNameConflict(entry.name, entry.type, entry.pid);
|
|
105
|
+
this.insertOrUpdate(entry);
|
|
106
|
+
}
|
|
60
107
|
isAlive(entry) {
|
|
61
108
|
try {
|
|
62
109
|
process.kill(entry.pid, 0);
|
|
@@ -66,13 +113,16 @@ export class AgentRegistry {
|
|
|
66
113
|
}
|
|
67
114
|
}
|
|
68
115
|
prune() {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
116
|
+
const entries = this.list();
|
|
117
|
+
const stale = entries.filter((e)=>!this.isAlive(e));
|
|
118
|
+
this.db.transaction(()=>{
|
|
119
|
+
for (const entry of stale){
|
|
120
|
+
this.db.execute('DELETE FROM agents WHERE type = ? AND pid = ?', [
|
|
121
|
+
entry.type,
|
|
122
|
+
entry.pid
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
76
126
|
}
|
|
77
127
|
register(entry) {
|
|
78
128
|
this.registerBatch([
|
|
@@ -81,42 +131,43 @@ export class AgentRegistry {
|
|
|
81
131
|
}
|
|
82
132
|
registerBatch(entries) {
|
|
83
133
|
if (entries.length === 0) return;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
data.entries[idx] = this.mergeEntry(incoming, data.entries[idx]);
|
|
89
|
-
} else {
|
|
90
|
-
data.entries.push(incoming);
|
|
134
|
+
this.db.transaction(()=>{
|
|
135
|
+
for (const incoming of entries){
|
|
136
|
+
const existing = this.findByIdentity(incoming.type, incoming.pid);
|
|
137
|
+
this.save(this.mergeEntry(incoming, existing));
|
|
91
138
|
}
|
|
92
|
-
}
|
|
93
|
-
this.writeFile(data);
|
|
139
|
+
});
|
|
94
140
|
}
|
|
95
141
|
rename(currentName, newName) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
if (idx < 0) {
|
|
142
|
+
const existing = this.findByName(currentName);
|
|
143
|
+
if (!existing) {
|
|
99
144
|
throw new RenameNotFoundError(currentName);
|
|
100
145
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
if (conflict) {
|
|
146
|
+
const conflict = this.findByName(newName);
|
|
147
|
+
if (conflict && this.isAlive(conflict)) {
|
|
104
148
|
throw new RenameConflictError(newName);
|
|
105
149
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
150
|
+
this.db.transaction(()=>{
|
|
151
|
+
if (conflict) {
|
|
152
|
+
this.db.execute('DELETE FROM agents WHERE type = ? AND pid = ?', [
|
|
153
|
+
conflict.type,
|
|
154
|
+
conflict.pid
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
this.db.execute('UPDATE agents SET name = ?, updated_at = ? WHERE type = ? AND pid = ?', [
|
|
158
|
+
newName,
|
|
159
|
+
new Date().toISOString(),
|
|
160
|
+
existing.type,
|
|
161
|
+
existing.pid
|
|
162
|
+
]);
|
|
112
163
|
});
|
|
113
164
|
}
|
|
114
165
|
lookup(name) {
|
|
115
|
-
|
|
116
|
-
return data.entries.find((e)=>e.name === name) ?? null;
|
|
166
|
+
return this.findByName(name) ?? null;
|
|
117
167
|
}
|
|
118
168
|
list() {
|
|
119
|
-
|
|
169
|
+
const rows = this.db.query('SELECT * FROM agents ORDER BY started_at ASC, name ASC');
|
|
170
|
+
return rows.map((row)=>this.rowToEntry(row));
|
|
120
171
|
}
|
|
121
172
|
}
|
|
122
173
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/AgentRegistry.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport type { AgentType } from '../adapters/AgentAdapter.js';\n\nexport class RenameNotFoundError extends Error {\n constructor(public agentName: string) {\n super(`Agent \"${agentName}\" not found in registry.`);\n this.name = 'RenameNotFoundError';\n }\n}\n\nexport class RenameConflictError extends Error {\n constructor(public agentName: string) {\n super(`Agent \"${agentName}\" is already in use.`);\n this.name = 'RenameConflictError';\n }\n}\n\nexport interface RegistryEntry {\n name: string;\n type: AgentType;\n pid: number;\n tmuxSession: string;\n cwd: string;\n startedAt: string; // ISO 8601\n sessionId: string;\n sessionFilePath: string;\n}\n\ninterface RegistryFile {\n entries: RegistryEntry[];\n}\n\nconst DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.ai-devkit', 'agents.json');\n\nlet defaultInstance: AgentRegistry | null = null;\n\nexport class AgentRegistry {\n private filePath: string;\n\n constructor(filePath: string = DEFAULT_REGISTRY_PATH) {\n this.filePath = filePath;\n }\n\n static default(): AgentRegistry {\n if (!defaultInstance) {\n defaultInstance = new AgentRegistry();\n }\n return defaultInstance;\n }\n\n private readFile(): RegistryFile {\n try {\n const raw = fs.readFileSync(this.filePath, 'utf8');\n const parsed = JSON.parse(raw) as RegistryFile;\n return { entries: Array.isArray(parsed.entries) ? parsed.entries : [] };\n } catch {\n return { entries: [] };\n }\n }\n\n private writeFile(data: RegistryFile): void {\n const dir = path.dirname(this.filePath);\n fs.mkdirSync(dir, { recursive: true });\n const tmp = `${this.filePath}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');\n fs.renameSync(tmp, this.filePath);\n }\n\n private mergeEntry(incoming: RegistryEntry, existing: RegistryEntry | undefined): RegistryEntry {\n if (!existing) return incoming;\n return {\n ...incoming,\n tmuxSession: incoming.tmuxSession || existing.tmuxSession,\n };\n }\n\n isAlive(entry: RegistryEntry): boolean {\n try {\n process.kill(entry.pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n prune(): void {\n const data = this.readFile();\n const live = data.entries.filter((e) => this.isAlive(e));\n if (live.length !== data.entries.length) {\n this.writeFile({ entries: live });\n }\n }\n\n register(entry: RegistryEntry): void {\n this.registerBatch([entry]);\n }\n\n registerBatch(entries: RegistryEntry[]): void {\n if (entries.length === 0) return;\n const data = this.readFile();\n for (const incoming of entries) {\n const idx = data.entries.findIndex((e) => e.name === incoming.name);\n if (idx >= 0) {\n data.entries[idx] = this.mergeEntry(incoming, data.entries[idx]);\n } else {\n data.entries.push(incoming);\n }\n }\n this.writeFile(data);\n }\n\n rename(currentName: string, newName: string): void {\n const data = this.readFile();\n const idx = data.entries.findIndex((e) => e.name === currentName);\n if (idx < 0) {\n throw new RenameNotFoundError(currentName);\n }\n const liveEntries = data.entries.filter((e) => this.isAlive(e));\n const conflict = liveEntries.find((e) => e.name === newName);\n if (conflict) {\n throw new RenameConflictError(newName);\n }\n const pruned = liveEntries.map((e) =>\n e.name === currentName ? { ...e, name: newName } : e,\n );\n this.writeFile({ entries: pruned });\n }\n\n lookup(name: string): RegistryEntry | null {\n const data = this.readFile();\n return data.entries.find((e) => e.name === name) ?? null;\n }\n\n list(): RegistryEntry[] {\n return this.readFile().entries;\n }\n}\n"],"names":["fs","os","path","RenameNotFoundError","Error","agentName","name","RenameConflictError","DEFAULT_REGISTRY_PATH","join","homedir","defaultInstance","AgentRegistry","filePath","default","readFile","raw","readFileSync","parsed","JSON","parse","entries","Array","isArray","writeFile","data","dir","dirname","mkdirSync","recursive","tmp","writeFileSync","stringify","renameSync","mergeEntry","incoming","existing","tmuxSession","isAlive","entry","process","kill","pid","prune","live","filter","e","length","register","registerBatch","idx","findIndex","push","rename","currentName","newName","liveEntries","conflict","find","pruned","map","lookup","list"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/AgentRegistry.ts"],"sourcesContent":["import os from 'os';\nimport path from 'path';\nimport type { AgentType } from '../adapters/AgentAdapter.js';\nimport {\n DatabaseConnection,\n resolveAgentRegistryDbPath,\n} from '../database/index.js';\n\nexport class RenameNotFoundError extends Error {\n constructor(public agentName: string) {\n super(`Agent \"${agentName}\" not found in registry.`);\n this.name = 'RenameNotFoundError';\n }\n}\n\nexport class RenameConflictError extends Error {\n constructor(public agentName: string) {\n super(`Agent \"${agentName}\" is already in use.`);\n this.name = 'RenameConflictError';\n }\n}\n\nexport interface RegistryEntry {\n name: string;\n type: AgentType;\n pid: number;\n tmuxSession: string;\n cwd: string;\n startedAt: string; // ISO 8601\n sessionId: string;\n sessionFilePath: string;\n}\n\ninterface RegistryRow {\n name: string;\n type: AgentType;\n pid: number;\n tmux_session: string;\n cwd: string;\n started_at: string;\n session_id: string;\n session_file_path: string;\n updated_at: string;\n}\n\nconst DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.ai-devkit', 'agents.json');\n\nlet defaultInstance: AgentRegistry | null = null;\n\nexport class AgentRegistry {\n private db: DatabaseConnection;\n\n constructor(filePath: string = DEFAULT_REGISTRY_PATH) {\n this.db = new DatabaseConnection({ dbPath: resolveAgentRegistryDbPath(filePath) });\n }\n\n static default(): AgentRegistry {\n if (!defaultInstance) {\n defaultInstance = new AgentRegistry();\n }\n return defaultInstance;\n }\n\n private rowToEntry(row: RegistryRow): RegistryEntry {\n return {\n name: row.name,\n type: row.type,\n pid: row.pid,\n tmuxSession: row.tmux_session,\n cwd: row.cwd,\n startedAt: row.started_at,\n sessionId: row.session_id,\n sessionFilePath: row.session_file_path,\n };\n }\n\n private mergeEntry(incoming: RegistryEntry, existing: RegistryEntry | undefined): RegistryEntry {\n if (!existing) return incoming;\n const incomingIsManaged = Boolean(incoming.tmuxSession);\n return {\n ...existing,\n name: incomingIsManaged ? incoming.name : existing.name,\n tmuxSession: incoming.tmuxSession || existing.tmuxSession,\n cwd: incoming.cwd || existing.cwd,\n startedAt: existing.startedAt || incoming.startedAt,\n sessionId: incoming.sessionId || existing.sessionId,\n sessionFilePath: incoming.sessionFilePath || existing.sessionFilePath,\n };\n }\n\n private findByIdentity(type: AgentType, pid: number): RegistryEntry | undefined {\n const row = this.db.queryOne<RegistryRow>(\n 'SELECT * FROM agents WHERE type = ? AND pid = ?',\n [type, pid],\n );\n return row ? this.rowToEntry(row) : undefined;\n }\n\n private findByName(name: string): RegistryEntry | undefined {\n const row = this.db.queryOne<RegistryRow>('SELECT * FROM agents WHERE name = ?', [name]);\n return row ? this.rowToEntry(row) : undefined;\n }\n\n private deleteNameConflict(name: string, type: AgentType, pid: number): void {\n const conflict = this.findByName(name);\n if (!conflict) return;\n if (conflict.type === type && conflict.pid === pid) return;\n if (!this.isAlive(conflict)) {\n this.db.execute('DELETE FROM agents WHERE type = ? AND pid = ?', [conflict.type, conflict.pid]);\n }\n }\n\n private insertOrUpdate(entry: RegistryEntry): void {\n this.db.instance.prepare(`\n INSERT INTO agents (\n type, pid, name, tmux_session, cwd, started_at, session_id, session_file_path, updated_at\n )\n VALUES (\n @type, @pid, @name, @tmuxSession, @cwd, @startedAt, @sessionId, @sessionFilePath, @updatedAt\n )\n ON CONFLICT(type, pid) DO UPDATE SET\n name = excluded.name,\n tmux_session = excluded.tmux_session,\n cwd = excluded.cwd,\n started_at = agents.started_at,\n session_id = excluded.session_id,\n session_file_path = excluded.session_file_path,\n updated_at = excluded.updated_at\n `).run({ ...entry, updatedAt: new Date().toISOString() });\n }\n\n private save(entry: RegistryEntry): void {\n this.deleteNameConflict(entry.name, entry.type, entry.pid);\n this.insertOrUpdate(entry);\n }\n\n isAlive(entry: RegistryEntry): boolean {\n try {\n process.kill(entry.pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n prune(): void {\n const entries = this.list();\n const stale = entries.filter((e) => !this.isAlive(e));\n this.db.transaction(() => {\n for (const entry of stale) {\n this.db.execute('DELETE FROM agents WHERE type = ? AND pid = ?', [entry.type, entry.pid]);\n }\n });\n }\n\n register(entry: RegistryEntry): void {\n this.registerBatch([entry]);\n }\n\n registerBatch(entries: RegistryEntry[]): void {\n if (entries.length === 0) return;\n this.db.transaction(() => {\n for (const incoming of entries) {\n const existing = this.findByIdentity(incoming.type, incoming.pid);\n this.save(this.mergeEntry(incoming, existing));\n }\n });\n }\n\n rename(currentName: string, newName: string): void {\n const existing = this.findByName(currentName);\n if (!existing) {\n throw new RenameNotFoundError(currentName);\n }\n const conflict = this.findByName(newName);\n if (conflict && this.isAlive(conflict)) {\n throw new RenameConflictError(newName);\n }\n\n this.db.transaction(() => {\n if (conflict) {\n this.db.execute('DELETE FROM agents WHERE type = ? AND pid = ?', [conflict.type, conflict.pid]);\n }\n this.db.execute(\n 'UPDATE agents SET name = ?, updated_at = ? WHERE type = ? AND pid = ?',\n [newName, new Date().toISOString(), existing.type, existing.pid],\n );\n });\n }\n\n lookup(name: string): RegistryEntry | null {\n return this.findByName(name) ?? null;\n }\n\n list(): RegistryEntry[] {\n const rows = this.db.query<RegistryRow>('SELECT * FROM agents ORDER BY started_at ASC, name ASC');\n return rows.map((row) => this.rowToEntry(row));\n }\n}\n"],"names":["os","path","DatabaseConnection","resolveAgentRegistryDbPath","RenameNotFoundError","Error","agentName","name","RenameConflictError","DEFAULT_REGISTRY_PATH","join","homedir","defaultInstance","AgentRegistry","db","filePath","dbPath","default","rowToEntry","row","type","pid","tmuxSession","tmux_session","cwd","startedAt","started_at","sessionId","session_id","sessionFilePath","session_file_path","mergeEntry","incoming","existing","incomingIsManaged","Boolean","findByIdentity","queryOne","undefined","findByName","deleteNameConflict","conflict","isAlive","execute","insertOrUpdate","entry","instance","prepare","run","updatedAt","Date","toISOString","save","process","kill","prune","entries","list","stale","filter","e","transaction","register","registerBatch","length","rename","currentName","newName","lookup","rows","query","map"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-devkit/agent-manager",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Standalone agent detection and management utilities for AI DevKit",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
|
|
15
|
+
"build": "swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly && cp -r src/database/migrations dist/database/",
|
|
16
16
|
"dev": "swc src -d dist --strip-leading-paths --watch",
|
|
17
17
|
"test": "vitest run",
|
|
18
18
|
"test:watch": "vitest",
|
|
@@ -340,6 +340,43 @@ describe('AgentManager', () => {
|
|
|
340
340
|
expect(registry.list()[0].startedAt).toBe('2026-05-30T00:00:00.000Z');
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
+
it('preserves a user-managed name when a fallback row was written later for the same pid', async () => {
|
|
344
|
+
registry.register({
|
|
345
|
+
name: 'agent-list-debug',
|
|
346
|
+
type: 'codex',
|
|
347
|
+
pid: process.pid,
|
|
348
|
+
tmuxSession: 'agent-list-debug',
|
|
349
|
+
cwd: '/cwd/debug',
|
|
350
|
+
startedAt: '2026-05-30T00:00:00.000Z',
|
|
351
|
+
sessionId: 'pid-debug',
|
|
352
|
+
sessionFilePath: '',
|
|
353
|
+
});
|
|
354
|
+
registry.register({
|
|
355
|
+
name: `ai-devkit-${process.pid}`,
|
|
356
|
+
type: 'codex',
|
|
357
|
+
pid: process.pid,
|
|
358
|
+
tmuxSession: '',
|
|
359
|
+
cwd: '/cwd/debug',
|
|
360
|
+
startedAt: '2026-05-31T00:00:00.000Z',
|
|
361
|
+
sessionId: 'pid-debug',
|
|
362
|
+
sessionFilePath: '',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
scopedManager.registerAdapter(new MockAdapter('codex', [
|
|
366
|
+
createMockAgent({ name: `ai-devkit-${process.pid}`, type: 'codex', pid: process.pid }),
|
|
367
|
+
]));
|
|
368
|
+
|
|
369
|
+
const agents = await scopedManager.listAgents();
|
|
370
|
+
|
|
371
|
+
expect(agents[0].name).toBe('agent-list-debug');
|
|
372
|
+
expect(registry.list()).toHaveLength(1);
|
|
373
|
+
expect(registry.list()[0]).toMatchObject({
|
|
374
|
+
name: 'agent-list-debug',
|
|
375
|
+
pid: process.pid,
|
|
376
|
+
tmuxSession: 'agent-list-debug',
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
343
380
|
it('writes a fresh startedAt for new entries', async () => {
|
|
344
381
|
const before = new Date().toISOString();
|
|
345
382
|
scopedManager.registerAdapter(new MockAdapter('claude', [
|