@hasna/mementos 0.4.39 → 0.4.41
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/cli/index.js +71 -14
- package/dist/db/agents.d.ts +1 -1
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts +52 -0
- package/dist/db/locks.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +208 -11
- package/dist/lib/memory-lock.d.ts +58 -0
- package/dist/lib/memory-lock.d.ts.map +1 -0
- package/dist/mcp/index.js +254 -13
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +181 -12
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2405,13 +2405,53 @@ var init_database = __esm(() => {
|
|
|
2405
2405
|
ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
2406
2406
|
CREATE INDEX IF NOT EXISTS idx_agents_active_project ON agents(active_project_id);
|
|
2407
2407
|
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
2408
|
+
`,
|
|
2409
|
+
`
|
|
2410
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
2411
|
+
CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);
|
|
2412
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (7);
|
|
2413
|
+
`,
|
|
2414
|
+
`
|
|
2415
|
+
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
2416
|
+
id TEXT PRIMARY KEY,
|
|
2417
|
+
resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector')),
|
|
2418
|
+
resource_id TEXT NOT NULL,
|
|
2419
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
2420
|
+
lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
|
|
2421
|
+
locked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2422
|
+
expires_at TEXT NOT NULL
|
|
2423
|
+
);
|
|
2424
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
2425
|
+
ON resource_locks(resource_type, resource_id)
|
|
2426
|
+
WHERE lock_type = 'exclusive';
|
|
2427
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
2428
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
2429
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (8);
|
|
2408
2430
|
`
|
|
2409
2431
|
];
|
|
2410
2432
|
});
|
|
2411
2433
|
|
|
2412
2434
|
// src/types/index.ts
|
|
2413
|
-
var EntityNotFoundError, MemoryNotFoundError, VersionConflictError;
|
|
2435
|
+
var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, VersionConflictError;
|
|
2414
2436
|
var init_types = __esm(() => {
|
|
2437
|
+
AgentConflictError = class AgentConflictError extends Error {
|
|
2438
|
+
conflict = true;
|
|
2439
|
+
existing_id;
|
|
2440
|
+
existing_name;
|
|
2441
|
+
last_seen_at;
|
|
2442
|
+
session_hint;
|
|
2443
|
+
working_dir;
|
|
2444
|
+
constructor(opts) {
|
|
2445
|
+
const msg = `Agent "${opts.existing_name}" is already active (session hint: ${opts.session_hint ?? "unknown"}, last seen ${opts.last_seen_at}). Wait 30 minutes or use a different name.`;
|
|
2446
|
+
super(msg);
|
|
2447
|
+
this.name = "AgentConflictError";
|
|
2448
|
+
this.existing_id = opts.existing_id;
|
|
2449
|
+
this.existing_name = opts.existing_name;
|
|
2450
|
+
this.last_seen_at = opts.last_seen_at;
|
|
2451
|
+
this.session_hint = opts.session_hint;
|
|
2452
|
+
this.working_dir = opts.working_dir ?? null;
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2415
2455
|
EntityNotFoundError = class EntityNotFoundError extends Error {
|
|
2416
2456
|
constructor(id) {
|
|
2417
2457
|
super(`Entity not found: ${id}`);
|
|
@@ -2471,6 +2511,7 @@ function parseAgentRow(row) {
|
|
|
2471
2511
|
return {
|
|
2472
2512
|
id: row["id"],
|
|
2473
2513
|
name: row["name"],
|
|
2514
|
+
session_id: row["session_id"] || null,
|
|
2474
2515
|
description: row["description"] || null,
|
|
2475
2516
|
role: row["role"] || null,
|
|
2476
2517
|
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
@@ -2479,33 +2520,46 @@ function parseAgentRow(row) {
|
|
|
2479
2520
|
last_seen_at: row["last_seen_at"]
|
|
2480
2521
|
};
|
|
2481
2522
|
}
|
|
2482
|
-
function registerAgent(name, description, role, db) {
|
|
2523
|
+
function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
2483
2524
|
const d = db || getDatabase();
|
|
2484
2525
|
const timestamp = now();
|
|
2485
2526
|
const normalizedName = name.trim().toLowerCase();
|
|
2486
2527
|
const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
2487
2528
|
if (existing) {
|
|
2488
2529
|
const existingId = existing["id"];
|
|
2489
|
-
|
|
2530
|
+
const existingSessionId = existing["session_id"] || null;
|
|
2531
|
+
const existingLastSeen = existing["last_seen_at"];
|
|
2532
|
+
if (sessionId && existingSessionId && existingSessionId !== sessionId) {
|
|
2533
|
+
const lastSeenMs = new Date(existingLastSeen).getTime();
|
|
2534
|
+
const nowMs = Date.now();
|
|
2535
|
+
if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
|
|
2536
|
+
throw new AgentConflictError({
|
|
2537
|
+
existing_id: existingId,
|
|
2538
|
+
existing_name: normalizedName,
|
|
2539
|
+
last_seen_at: existingLastSeen,
|
|
2540
|
+
session_hint: existingSessionId.slice(0, 8),
|
|
2541
|
+
working_dir: null
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
|
|
2490
2546
|
timestamp,
|
|
2547
|
+
sessionId ?? existingSessionId,
|
|
2491
2548
|
existingId
|
|
2492
2549
|
]);
|
|
2493
2550
|
if (description) {
|
|
2494
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
2495
|
-
description,
|
|
2496
|
-
existingId
|
|
2497
|
-
]);
|
|
2551
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
|
|
2498
2552
|
}
|
|
2499
2553
|
if (role) {
|
|
2500
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
]);
|
|
2554
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
|
|
2555
|
+
}
|
|
2556
|
+
if (projectId !== undefined) {
|
|
2557
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
2504
2558
|
}
|
|
2505
2559
|
return getAgent(existingId, d);
|
|
2506
2560
|
}
|
|
2507
2561
|
const id = shortUuid();
|
|
2508
|
-
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, normalizedName, description || null, role || "agent", timestamp, timestamp]);
|
|
2562
|
+
d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
|
|
2509
2563
|
return getAgent(id, d);
|
|
2510
2564
|
}
|
|
2511
2565
|
function getAgent(idOrName, db) {
|
|
@@ -2557,8 +2611,11 @@ function updateAgent(id, updates, db) {
|
|
|
2557
2611
|
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
|
|
2558
2612
|
return getAgent(agent.id, d);
|
|
2559
2613
|
}
|
|
2614
|
+
var CONFLICT_WINDOW_MS;
|
|
2560
2615
|
var init_agents = __esm(() => {
|
|
2616
|
+
init_types();
|
|
2561
2617
|
init_database();
|
|
2618
|
+
CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
2562
2619
|
});
|
|
2563
2620
|
|
|
2564
2621
|
// src/db/projects.ts
|
|
@@ -5347,10 +5404,10 @@ program2.command("clean").description("Remove expired memories and enforce quota
|
|
|
5347
5404
|
handleError(e);
|
|
5348
5405
|
}
|
|
5349
5406
|
});
|
|
5350
|
-
program2.command("init <name>").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").action((name, opts) => {
|
|
5407
|
+
program2.command("init <name>").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("-p, --project <id>", "Lock agent to a project (sets active_project_id)").action((name, opts) => {
|
|
5351
5408
|
try {
|
|
5352
5409
|
const globalOpts = program2.opts();
|
|
5353
|
-
const agent = registerAgent(name, opts.description, opts.role);
|
|
5410
|
+
const agent = registerAgent(name, undefined, opts.description, opts.role, opts.project);
|
|
5354
5411
|
if (globalOpts.json) {
|
|
5355
5412
|
outputJson(agent);
|
|
5356
5413
|
} else {
|
package/dist/db/agents.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import type { Agent } from "../types/index.js";
|
|
3
|
-
export declare function registerAgent(name: string, description?: string, role?: string, db?: Database): Agent;
|
|
3
|
+
export declare function registerAgent(name: string, sessionId?: string, description?: string, role?: string, projectId?: string, db?: Database): Agent;
|
|
4
4
|
export declare function getAgent(idOrName: string, db?: Database): Agent | null;
|
|
5
5
|
export declare function listAgents(db?: Database): Agent[];
|
|
6
6
|
export declare function touchAgent(idOrName: string, db?: Database): void;
|
package/dist/db/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAoB/C,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,EAClB,EAAE,CAAC,EAAE,QAAQ,GACZ,KAAK,CAuDP;AAED,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,MAAM,EAChB,EAAE,CAAC,EAAE,QAAQ,GACZ,KAAK,GAAG,IAAI,CAsBd;AAED,wBAAgB,UAAU,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAMjD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAKhE;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAM7E;AAED,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACtI,EAAE,CAAC,EAAE,QAAQ,GACZ,KAAK,GAAG,IAAI,CAyCd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmCtC,wBAAgB,SAAS,IAAI,MAAM,CAkBlC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmCtC,wBAAgB,SAAS,IAAI,MAAM,CAkBlC;AAmQD,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAerD;AA+BD,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAef"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
export type ResourceType = "project" | "memory" | "entity" | "agent" | "connector";
|
|
3
|
+
export type LockType = "advisory" | "exclusive";
|
|
4
|
+
export interface ResourceLock {
|
|
5
|
+
id: string;
|
|
6
|
+
resource_type: ResourceType;
|
|
7
|
+
resource_id: string;
|
|
8
|
+
agent_id: string;
|
|
9
|
+
lock_type: LockType;
|
|
10
|
+
locked_at: string;
|
|
11
|
+
expires_at: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Acquire a lock on a resource.
|
|
15
|
+
* - advisory: multiple agents can hold advisory locks simultaneously
|
|
16
|
+
* - exclusive: only one agent can hold an exclusive lock at a time
|
|
17
|
+
*
|
|
18
|
+
* Returns the lock if acquired, null if blocked by an existing exclusive lock.
|
|
19
|
+
* TTL is in seconds (default: 5 minutes).
|
|
20
|
+
*/
|
|
21
|
+
export declare function acquireLock(agentId: string, resourceType: ResourceType, resourceId: string, lockType?: LockType, ttlSeconds?: number, db?: Database): ResourceLock | null;
|
|
22
|
+
/**
|
|
23
|
+
* Release a specific lock by ID. Only the owning agent can release it.
|
|
24
|
+
* Returns true if released, false if not found or not owned.
|
|
25
|
+
*/
|
|
26
|
+
export declare function releaseLock(lockId: string, agentId: string, db?: Database): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Release all locks held by an agent on a specific resource.
|
|
29
|
+
*/
|
|
30
|
+
export declare function releaseResourceLocks(agentId: string, resourceType: ResourceType, resourceId: string, db?: Database): number;
|
|
31
|
+
/**
|
|
32
|
+
* Release all locks held by an agent (e.g., on session end).
|
|
33
|
+
*/
|
|
34
|
+
export declare function releaseAllAgentLocks(agentId: string, db?: Database): number;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a resource is currently locked.
|
|
37
|
+
* Returns the active lock(s) or empty array.
|
|
38
|
+
*/
|
|
39
|
+
export declare function checkLock(resourceType: ResourceType, resourceId: string, lockType?: LockType, db?: Database): ResourceLock[];
|
|
40
|
+
/**
|
|
41
|
+
* Check if a specific agent holds a lock on a resource.
|
|
42
|
+
*/
|
|
43
|
+
export declare function agentHoldsLock(agentId: string, resourceType: ResourceType, resourceId: string, lockType?: LockType, db?: Database): ResourceLock | null;
|
|
44
|
+
/**
|
|
45
|
+
* List all active locks for an agent.
|
|
46
|
+
*/
|
|
47
|
+
export declare function listAgentLocks(agentId: string, db?: Database): ResourceLock[];
|
|
48
|
+
/**
|
|
49
|
+
* Delete all expired locks. Called automatically by other lock functions.
|
|
50
|
+
*/
|
|
51
|
+
export declare function cleanExpiredLocks(db?: Database): number;
|
|
52
|
+
//# sourceMappingURL=locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../src/db/locks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;AACnF,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,WAAW,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,YAAY,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,QAAQ,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAcD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,QAAsB,EAChC,UAAU,SAAM,EAChB,EAAE,CAAC,EAAE,QAAQ,GACZ,YAAY,GAAG,IAAI,CAuDrB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAOnF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,MAAM,EAClB,EAAE,CAAC,EAAE,QAAQ,GACZ,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAI3E;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,QAAQ,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,YAAY,EAAE,CAgBhB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,QAAQ,EACnB,EAAE,CAAC,EAAE,QAAQ,GACZ,YAAY,GAAG,IAAI,CAcrB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,YAAY,EAAE,CAS7E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAIvD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export { MemoryNotFoundError, DuplicateMemoryError, MemoryExpiredError, InvalidS
|
|
|
3
3
|
export { getDatabase, closeDatabase, resetDatabase, getDbPath, resolvePartialId, now, uuid, shortUuid, } from "./db/database.js";
|
|
4
4
|
export { createMemory, getMemory, getMemoryByKey, getMemoriesByKey, listMemories, updateMemory, deleteMemory, bulkDeleteMemories, touchMemory, cleanExpiredMemories, getMemoryVersions, } from "./db/memories.js";
|
|
5
5
|
export { registerAgent, getAgent, listAgents, listAgentsByProject, updateAgent, touchAgent, } from "./db/agents.js";
|
|
6
|
+
export { acquireLock, releaseLock, releaseResourceLocks, releaseAllAgentLocks, checkLock, agentHoldsLock, listAgentLocks, cleanExpiredLocks, } from "./db/locks.js";
|
|
7
|
+
export type { ResourceLock, ResourceType, LockType } from "./db/locks.js";
|
|
8
|
+
export { acquireMemoryWriteLock, releaseMemoryWriteLock, checkMemoryWriteLock, withMemoryLock, MemoryLockConflictError, memoryLockId, } from "./lib/memory-lock.js";
|
|
6
9
|
export { registerProject, getProject, listProjects, } from "./db/projects.js";
|
|
7
10
|
export { searchMemories } from "./lib/search.js";
|
|
8
11
|
export { loadConfig, DEFAULT_CONFIG, getActiveProfile, setActiveProfile, listProfiles, deleteProfile, } from "./lib/config.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,MAAM,EACN,mBAAmB,EACnB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,OAAO,EACP,cAAc,EACd,aAAa,EACb,WAAW,EACX,UAAU,EACV,kBAAkB,EAElB,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,GAAG,EACH,IAAI,EACJ,SAAS,GACV,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,YAAY,EACZ,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,aAAa,EACb,QAAQ,EACR,UAAU,EACV,mBAAmB,EACnB,WAAW,EACX,UAAU,GACX,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EACL,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAG/G,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,EACL,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,MAAM,EACN,mBAAmB,EACnB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,OAAO,EACP,cAAc,EACd,aAAa,EACb,WAAW,EACX,UAAU,EACV,kBAAkB,EAElB,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,GAAG,EACH,IAAI,EACJ,SAAS,GACV,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,YAAY,EACZ,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,aAAa,EACb,QAAQ,EACR,UAAU,EACV,mBAAmB,EACnB,WAAW,EACX,UAAU,GACX,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,SAAS,EACT,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG1E,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,uBAAuB,EACvB,YAAY,GACb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EACL,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAG/G,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,EACL,YAAY,EACZ,SAAS,EACT,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/types/index.ts
|
|
3
|
+
class AgentConflictError extends Error {
|
|
4
|
+
conflict = true;
|
|
5
|
+
existing_id;
|
|
6
|
+
existing_name;
|
|
7
|
+
last_seen_at;
|
|
8
|
+
session_hint;
|
|
9
|
+
working_dir;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
const msg = `Agent "${opts.existing_name}" is already active (session hint: ${opts.session_hint ?? "unknown"}, last seen ${opts.last_seen_at}). Wait 30 minutes or use a different name.`;
|
|
12
|
+
super(msg);
|
|
13
|
+
this.name = "AgentConflictError";
|
|
14
|
+
this.existing_id = opts.existing_id;
|
|
15
|
+
this.existing_name = opts.existing_name;
|
|
16
|
+
this.last_seen_at = opts.last_seen_at;
|
|
17
|
+
this.session_hint = opts.session_hint;
|
|
18
|
+
this.working_dir = opts.working_dir ?? null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
3
21
|
class EntityNotFoundError extends Error {
|
|
4
22
|
constructor(id) {
|
|
5
23
|
super(`Entity not found: ${id}`);
|
|
@@ -301,6 +319,28 @@ var MIGRATIONS = [
|
|
|
301
319
|
ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
302
320
|
CREATE INDEX IF NOT EXISTS idx_agents_active_project ON agents(active_project_id);
|
|
303
321
|
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
322
|
+
`,
|
|
323
|
+
`
|
|
324
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);
|
|
326
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (7);
|
|
327
|
+
`,
|
|
328
|
+
`
|
|
329
|
+
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
330
|
+
id TEXT PRIMARY KEY,
|
|
331
|
+
resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector')),
|
|
332
|
+
resource_id TEXT NOT NULL,
|
|
333
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
334
|
+
lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
|
|
335
|
+
locked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
336
|
+
expires_at TEXT NOT NULL
|
|
337
|
+
);
|
|
338
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
339
|
+
ON resource_locks(resource_type, resource_id)
|
|
340
|
+
WHERE lock_type = 'exclusive';
|
|
341
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
342
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
343
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (8);
|
|
304
344
|
`
|
|
305
345
|
];
|
|
306
346
|
var _db = null;
|
|
@@ -399,10 +439,12 @@ function containsSecrets(text) {
|
|
|
399
439
|
}
|
|
400
440
|
|
|
401
441
|
// src/db/agents.ts
|
|
442
|
+
var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
402
443
|
function parseAgentRow(row) {
|
|
403
444
|
return {
|
|
404
445
|
id: row["id"],
|
|
405
446
|
name: row["name"],
|
|
447
|
+
session_id: row["session_id"] || null,
|
|
406
448
|
description: row["description"] || null,
|
|
407
449
|
role: row["role"] || null,
|
|
408
450
|
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
@@ -411,33 +453,46 @@ function parseAgentRow(row) {
|
|
|
411
453
|
last_seen_at: row["last_seen_at"]
|
|
412
454
|
};
|
|
413
455
|
}
|
|
414
|
-
function registerAgent(name, description, role, db) {
|
|
456
|
+
function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
415
457
|
const d = db || getDatabase();
|
|
416
458
|
const timestamp = now();
|
|
417
459
|
const normalizedName = name.trim().toLowerCase();
|
|
418
460
|
const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
419
461
|
if (existing) {
|
|
420
462
|
const existingId = existing["id"];
|
|
421
|
-
|
|
463
|
+
const existingSessionId = existing["session_id"] || null;
|
|
464
|
+
const existingLastSeen = existing["last_seen_at"];
|
|
465
|
+
if (sessionId && existingSessionId && existingSessionId !== sessionId) {
|
|
466
|
+
const lastSeenMs = new Date(existingLastSeen).getTime();
|
|
467
|
+
const nowMs = Date.now();
|
|
468
|
+
if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
|
|
469
|
+
throw new AgentConflictError({
|
|
470
|
+
existing_id: existingId,
|
|
471
|
+
existing_name: normalizedName,
|
|
472
|
+
last_seen_at: existingLastSeen,
|
|
473
|
+
session_hint: existingSessionId.slice(0, 8),
|
|
474
|
+
working_dir: null
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
|
|
422
479
|
timestamp,
|
|
480
|
+
sessionId ?? existingSessionId,
|
|
423
481
|
existingId
|
|
424
482
|
]);
|
|
425
483
|
if (description) {
|
|
426
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
427
|
-
description,
|
|
428
|
-
existingId
|
|
429
|
-
]);
|
|
484
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
|
|
430
485
|
}
|
|
431
486
|
if (role) {
|
|
432
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
]);
|
|
487
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
|
|
488
|
+
}
|
|
489
|
+
if (projectId !== undefined) {
|
|
490
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
436
491
|
}
|
|
437
492
|
return getAgent(existingId, d);
|
|
438
493
|
}
|
|
439
494
|
const id = shortUuid();
|
|
440
|
-
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, normalizedName, description || null, role || "agent", timestamp, timestamp]);
|
|
495
|
+
d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
|
|
441
496
|
return getAgent(id, d);
|
|
442
497
|
}
|
|
443
498
|
function getAgent(idOrName, db) {
|
|
@@ -1602,6 +1657,134 @@ function getMemoryVersions(memoryId, db) {
|
|
|
1602
1657
|
return [];
|
|
1603
1658
|
}
|
|
1604
1659
|
}
|
|
1660
|
+
// src/db/locks.ts
|
|
1661
|
+
function parseLockRow(row) {
|
|
1662
|
+
return {
|
|
1663
|
+
id: row["id"],
|
|
1664
|
+
resource_type: row["resource_type"],
|
|
1665
|
+
resource_id: row["resource_id"],
|
|
1666
|
+
agent_id: row["agent_id"],
|
|
1667
|
+
lock_type: row["lock_type"],
|
|
1668
|
+
locked_at: row["locked_at"],
|
|
1669
|
+
expires_at: row["expires_at"]
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive", ttlSeconds = 300, db) {
|
|
1673
|
+
const d = db || getDatabase();
|
|
1674
|
+
cleanExpiredLocks(d);
|
|
1675
|
+
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at > datetime('now')").get(resourceType, resourceId, agentId, lockType);
|
|
1676
|
+
if (ownLock) {
|
|
1677
|
+
const newExpiry = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1678
|
+
d.run("UPDATE resource_locks SET expires_at = ? WHERE id = ?", [
|
|
1679
|
+
newExpiry,
|
|
1680
|
+
ownLock["id"]
|
|
1681
|
+
]);
|
|
1682
|
+
return parseLockRow({ ...ownLock, expires_at: newExpiry });
|
|
1683
|
+
}
|
|
1684
|
+
if (lockType === "exclusive") {
|
|
1685
|
+
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at > datetime('now')").get(resourceType, resourceId, agentId);
|
|
1686
|
+
if (existing) {
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const id = shortUuid();
|
|
1691
|
+
const lockedAt = now();
|
|
1692
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1693
|
+
d.run("INSERT INTO resource_locks (id, resource_type, resource_id, agent_id, lock_type, locked_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, resourceType, resourceId, agentId, lockType, lockedAt, expiresAt]);
|
|
1694
|
+
return {
|
|
1695
|
+
id,
|
|
1696
|
+
resource_type: resourceType,
|
|
1697
|
+
resource_id: resourceId,
|
|
1698
|
+
agent_id: agentId,
|
|
1699
|
+
lock_type: lockType,
|
|
1700
|
+
locked_at: lockedAt,
|
|
1701
|
+
expires_at: expiresAt
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
function releaseLock(lockId, agentId, db) {
|
|
1705
|
+
const d = db || getDatabase();
|
|
1706
|
+
const result = d.run("DELETE FROM resource_locks WHERE id = ? AND agent_id = ?", [lockId, agentId]);
|
|
1707
|
+
return result.changes > 0;
|
|
1708
|
+
}
|
|
1709
|
+
function releaseResourceLocks(agentId, resourceType, resourceId, db) {
|
|
1710
|
+
const d = db || getDatabase();
|
|
1711
|
+
const result = d.run("DELETE FROM resource_locks WHERE agent_id = ? AND resource_type = ? AND resource_id = ?", [agentId, resourceType, resourceId]);
|
|
1712
|
+
return result.changes;
|
|
1713
|
+
}
|
|
1714
|
+
function releaseAllAgentLocks(agentId, db) {
|
|
1715
|
+
const d = db || getDatabase();
|
|
1716
|
+
const result = d.run("DELETE FROM resource_locks WHERE agent_id = ?", [agentId]);
|
|
1717
|
+
return result.changes;
|
|
1718
|
+
}
|
|
1719
|
+
function checkLock(resourceType, resourceId, lockType, db) {
|
|
1720
|
+
const d = db || getDatabase();
|
|
1721
|
+
cleanExpiredLocks(d);
|
|
1722
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > datetime('now')" : "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND expires_at > datetime('now')";
|
|
1723
|
+
const rows = lockType ? d.query(query).all(resourceType, resourceId, lockType) : d.query(query).all(resourceType, resourceId);
|
|
1724
|
+
return rows.map(parseLockRow);
|
|
1725
|
+
}
|
|
1726
|
+
function agentHoldsLock(agentId, resourceType, resourceId, lockType, db) {
|
|
1727
|
+
const d = db || getDatabase();
|
|
1728
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE agent_id = ? AND resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > datetime('now')" : "SELECT * FROM resource_locks WHERE agent_id = ? AND resource_type = ? AND resource_id = ? AND expires_at > datetime('now')";
|
|
1729
|
+
const row = lockType ? d.query(query).get(agentId, resourceType, resourceId, lockType) : d.query(query).get(agentId, resourceType, resourceId);
|
|
1730
|
+
return row ? parseLockRow(row) : null;
|
|
1731
|
+
}
|
|
1732
|
+
function listAgentLocks(agentId, db) {
|
|
1733
|
+
const d = db || getDatabase();
|
|
1734
|
+
cleanExpiredLocks(d);
|
|
1735
|
+
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at > datetime('now') ORDER BY locked_at DESC").all(agentId);
|
|
1736
|
+
return rows.map(parseLockRow);
|
|
1737
|
+
}
|
|
1738
|
+
function cleanExpiredLocks(db) {
|
|
1739
|
+
const d = db || getDatabase();
|
|
1740
|
+
const result = d.run("DELETE FROM resource_locks WHERE expires_at <= datetime('now')");
|
|
1741
|
+
return result.changes;
|
|
1742
|
+
}
|
|
1743
|
+
// src/lib/memory-lock.ts
|
|
1744
|
+
var MEMORY_WRITE_TTL = 30;
|
|
1745
|
+
function memoryLockId(key, scope, projectId) {
|
|
1746
|
+
return `${scope}:${key}:${projectId ?? ""}`;
|
|
1747
|
+
}
|
|
1748
|
+
function acquireMemoryWriteLock(agentId, key, scope, projectId, ttlSeconds = MEMORY_WRITE_TTL, db) {
|
|
1749
|
+
const d = db || getDatabase();
|
|
1750
|
+
return acquireLock(agentId, "memory", memoryLockId(key, scope, projectId), "exclusive", ttlSeconds, d);
|
|
1751
|
+
}
|
|
1752
|
+
function releaseMemoryWriteLock(lockId, agentId, db) {
|
|
1753
|
+
const d = db || getDatabase();
|
|
1754
|
+
return releaseLock(lockId, agentId, d);
|
|
1755
|
+
}
|
|
1756
|
+
function checkMemoryWriteLock(key, scope, projectId, db) {
|
|
1757
|
+
const d = db || getDatabase();
|
|
1758
|
+
const locks = checkLock("memory", memoryLockId(key, scope, projectId), "exclusive", d);
|
|
1759
|
+
return locks[0] ?? null;
|
|
1760
|
+
}
|
|
1761
|
+
function withMemoryLock(agentId, key, scope, projectId, fn, ttlSeconds = MEMORY_WRITE_TTL, db) {
|
|
1762
|
+
const d = db || getDatabase();
|
|
1763
|
+
const lock = acquireMemoryWriteLock(agentId, key, scope, projectId, ttlSeconds, d);
|
|
1764
|
+
if (!lock) {
|
|
1765
|
+
const existing = checkMemoryWriteLock(key, scope, projectId, d);
|
|
1766
|
+
throw new MemoryLockConflictError(key, scope, existing?.agent_id ?? "unknown");
|
|
1767
|
+
}
|
|
1768
|
+
try {
|
|
1769
|
+
return fn();
|
|
1770
|
+
} finally {
|
|
1771
|
+
releaseLock(lock.id, agentId, d);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
class MemoryLockConflictError extends Error {
|
|
1776
|
+
conflict = true;
|
|
1777
|
+
key;
|
|
1778
|
+
scope;
|
|
1779
|
+
blocking_agent_id;
|
|
1780
|
+
constructor(key, scope, blockingAgentId) {
|
|
1781
|
+
super(`Memory key "${key}" (scope: ${scope}) is currently write-locked by agent ${blockingAgentId}. ` + "Retry after a few seconds or use optimistic locking (version field).");
|
|
1782
|
+
this.name = "MemoryLockConflictError";
|
|
1783
|
+
this.key = key;
|
|
1784
|
+
this.scope = scope;
|
|
1785
|
+
this.blocking_agent_id = blockingAgentId;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1605
1788
|
// src/lib/search.ts
|
|
1606
1789
|
function parseMemoryRow2(row) {
|
|
1607
1790
|
return {
|
|
@@ -2481,6 +2664,7 @@ function syncMemories(agentName, direction = "both", options = {}) {
|
|
|
2481
2664
|
}
|
|
2482
2665
|
var defaultSyncAgents = ["claude", "codex", "gemini"];
|
|
2483
2666
|
export {
|
|
2667
|
+
withMemoryLock,
|
|
2484
2668
|
uuid,
|
|
2485
2669
|
updateMemory,
|
|
2486
2670
|
updateEntity,
|
|
@@ -2495,6 +2679,10 @@ export {
|
|
|
2495
2679
|
runCleanup,
|
|
2496
2680
|
resolvePartialId,
|
|
2497
2681
|
resetDatabase,
|
|
2682
|
+
releaseResourceLocks,
|
|
2683
|
+
releaseMemoryWriteLock,
|
|
2684
|
+
releaseLock,
|
|
2685
|
+
releaseAllAgentLocks,
|
|
2498
2686
|
registerProject,
|
|
2499
2687
|
registerAgent,
|
|
2500
2688
|
redactSecrets,
|
|
@@ -2502,6 +2690,7 @@ export {
|
|
|
2502
2690
|
parseEntityRow,
|
|
2503
2691
|
now,
|
|
2504
2692
|
mergeEntities,
|
|
2693
|
+
memoryLockId,
|
|
2505
2694
|
loadConfig,
|
|
2506
2695
|
listRelations,
|
|
2507
2696
|
listProjects,
|
|
@@ -2510,6 +2699,7 @@ export {
|
|
|
2510
2699
|
listEntities,
|
|
2511
2700
|
listAgentsByProject,
|
|
2512
2701
|
listAgents,
|
|
2702
|
+
listAgentLocks,
|
|
2513
2703
|
linkEntityToMemory,
|
|
2514
2704
|
getRelation,
|
|
2515
2705
|
getRelatedEntities,
|
|
@@ -2543,12 +2733,19 @@ export {
|
|
|
2543
2733
|
containsSecrets,
|
|
2544
2734
|
closeDatabase,
|
|
2545
2735
|
cleanExpiredMemories,
|
|
2736
|
+
cleanExpiredLocks,
|
|
2737
|
+
checkMemoryWriteLock,
|
|
2738
|
+
checkLock,
|
|
2546
2739
|
bulkLinkEntities,
|
|
2547
2740
|
bulkDeleteMemories,
|
|
2548
2741
|
archiveUnused,
|
|
2549
2742
|
archiveStale,
|
|
2743
|
+
agentHoldsLock,
|
|
2744
|
+
acquireMemoryWriteLock,
|
|
2745
|
+
acquireLock,
|
|
2550
2746
|
VersionConflictError,
|
|
2551
2747
|
MemoryNotFoundError,
|
|
2748
|
+
MemoryLockConflictError,
|
|
2552
2749
|
MemoryInjector,
|
|
2553
2750
|
MemoryExpiredError,
|
|
2554
2751
|
InvalidScopeError,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPE4-00111: Concurrent memory access coordination
|
|
3
|
+
*
|
|
4
|
+
* Uses the resource_locks table (Migration 8) to provide:
|
|
5
|
+
* - Advisory locks for read-heavy coordination
|
|
6
|
+
* - Exclusive locks to prevent simultaneous writes to the same key
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Reads: always parallel, no locking needed
|
|
10
|
+
* - Writes: agents SHOULD acquire an exclusive lock on the memory key before writing
|
|
11
|
+
* The lock resource_id uses format: "{scope}:{key}:{agent_id}:{project_id}"
|
|
12
|
+
* This prevents two agents overwriting the same shared memory simultaneously
|
|
13
|
+
* - Optimistic fallback: if lock can't be acquired, VersionConflictError from
|
|
14
|
+
* updateMemory() provides a second layer of protection
|
|
15
|
+
*/
|
|
16
|
+
import { Database } from "bun:sqlite";
|
|
17
|
+
import { type ResourceLock } from "../db/locks.js";
|
|
18
|
+
/**
|
|
19
|
+
* Compute the lock resource_id for a memory key.
|
|
20
|
+
* Scoped by scope + key + project so different projects don't block each other.
|
|
21
|
+
*/
|
|
22
|
+
export declare function memoryLockId(key: string, scope: string, projectId?: string | null): string;
|
|
23
|
+
/**
|
|
24
|
+
* Acquire an exclusive write lock on a memory key.
|
|
25
|
+
* Returns the lock if acquired, null if another agent is writing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function acquireMemoryWriteLock(agentId: string, key: string, scope: string, projectId?: string | null, ttlSeconds?: number, db?: Database): ResourceLock | null;
|
|
28
|
+
/**
|
|
29
|
+
* Release a memory write lock.
|
|
30
|
+
*/
|
|
31
|
+
export declare function releaseMemoryWriteLock(lockId: string, agentId: string, db?: Database): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a memory key is currently write-locked.
|
|
34
|
+
* Returns the active lock or null.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkMemoryWriteLock(key: string, scope: string, projectId?: string | null, db?: Database): ResourceLock | null;
|
|
37
|
+
/**
|
|
38
|
+
* Execute a callback with an exclusive memory write lock.
|
|
39
|
+
* Throws if the lock cannot be acquired (another agent is writing).
|
|
40
|
+
* Automatically releases the lock after the callback completes.
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* withMemoryLock(agentId, "my-key", "shared", projectId, () => {
|
|
44
|
+
* createMemory({ key: "my-key", ... });
|
|
45
|
+
* });
|
|
46
|
+
*/
|
|
47
|
+
export declare function withMemoryLock<T>(agentId: string, key: string, scope: string, projectId: string | null | undefined, fn: () => T, ttlSeconds?: number, db?: Database): T;
|
|
48
|
+
/**
|
|
49
|
+
* Error thrown when a memory write lock cannot be acquired.
|
|
50
|
+
*/
|
|
51
|
+
export declare class MemoryLockConflictError extends Error {
|
|
52
|
+
readonly conflict: true;
|
|
53
|
+
readonly key: string;
|
|
54
|
+
readonly scope: string;
|
|
55
|
+
readonly blocking_agent_id: string;
|
|
56
|
+
constructor(key: string, scope: string, blockingAgentId: string);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=memory-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-lock.d.ts","sourceRoot":"","sources":["../../src/lib/memory-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAuC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKxF;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,SAAmB,EAC7B,EAAE,CAAC,EAAE,QAAQ,GACZ,YAAY,GAAG,IAAI,CAGrB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EACzB,EAAE,CAAC,EAAE,QAAQ,GACZ,YAAY,GAAG,IAAI,CAIrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,EAAE,EAAE,MAAM,CAAC,EACX,UAAU,SAAmB,EAC7B,EAAE,CAAC,EAAE,QAAQ,GACZ,CAAC,CAcH;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,SAAgB,QAAQ,EAAG,IAAI,CAAU;IACzC,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,iBAAiB,EAAE,MAAM,CAAC;gBAE9B,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM;CAUhE"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -3989,6 +3989,24 @@ var coerce = {
|
|
|
3989
3989
|
};
|
|
3990
3990
|
var NEVER = INVALID;
|
|
3991
3991
|
// src/types/index.ts
|
|
3992
|
+
class AgentConflictError extends Error {
|
|
3993
|
+
conflict = true;
|
|
3994
|
+
existing_id;
|
|
3995
|
+
existing_name;
|
|
3996
|
+
last_seen_at;
|
|
3997
|
+
session_hint;
|
|
3998
|
+
working_dir;
|
|
3999
|
+
constructor(opts) {
|
|
4000
|
+
const msg = `Agent "${opts.existing_name}" is already active (session hint: ${opts.session_hint ?? "unknown"}, last seen ${opts.last_seen_at}). Wait 30 minutes or use a different name.`;
|
|
4001
|
+
super(msg);
|
|
4002
|
+
this.name = "AgentConflictError";
|
|
4003
|
+
this.existing_id = opts.existing_id;
|
|
4004
|
+
this.existing_name = opts.existing_name;
|
|
4005
|
+
this.last_seen_at = opts.last_seen_at;
|
|
4006
|
+
this.session_hint = opts.session_hint;
|
|
4007
|
+
this.working_dir = opts.working_dir ?? null;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
3992
4010
|
class EntityNotFoundError extends Error {
|
|
3993
4011
|
constructor(id) {
|
|
3994
4012
|
super(`Entity not found: ${id}`);
|
|
@@ -4283,6 +4301,28 @@ var MIGRATIONS = [
|
|
|
4283
4301
|
ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
4284
4302
|
CREATE INDEX IF NOT EXISTS idx_agents_active_project ON agents(active_project_id);
|
|
4285
4303
|
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
4304
|
+
`,
|
|
4305
|
+
`
|
|
4306
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
4307
|
+
CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);
|
|
4308
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (7);
|
|
4309
|
+
`,
|
|
4310
|
+
`
|
|
4311
|
+
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
4312
|
+
id TEXT PRIMARY KEY,
|
|
4313
|
+
resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector')),
|
|
4314
|
+
resource_id TEXT NOT NULL,
|
|
4315
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
4316
|
+
lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
|
|
4317
|
+
locked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
4318
|
+
expires_at TEXT NOT NULL
|
|
4319
|
+
);
|
|
4320
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
4321
|
+
ON resource_locks(resource_type, resource_id)
|
|
4322
|
+
WHERE lock_type = 'exclusive';
|
|
4323
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
4324
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
4325
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (8);
|
|
4286
4326
|
`
|
|
4287
4327
|
];
|
|
4288
4328
|
var _db = null;
|
|
@@ -4365,10 +4405,12 @@ function redactSecrets(text) {
|
|
|
4365
4405
|
}
|
|
4366
4406
|
|
|
4367
4407
|
// src/db/agents.ts
|
|
4408
|
+
var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
4368
4409
|
function parseAgentRow(row) {
|
|
4369
4410
|
return {
|
|
4370
4411
|
id: row["id"],
|
|
4371
4412
|
name: row["name"],
|
|
4413
|
+
session_id: row["session_id"] || null,
|
|
4372
4414
|
description: row["description"] || null,
|
|
4373
4415
|
role: row["role"] || null,
|
|
4374
4416
|
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
@@ -4377,33 +4419,46 @@ function parseAgentRow(row) {
|
|
|
4377
4419
|
last_seen_at: row["last_seen_at"]
|
|
4378
4420
|
};
|
|
4379
4421
|
}
|
|
4380
|
-
function registerAgent(name, description, role, db) {
|
|
4422
|
+
function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
4381
4423
|
const d = db || getDatabase();
|
|
4382
4424
|
const timestamp = now();
|
|
4383
4425
|
const normalizedName = name.trim().toLowerCase();
|
|
4384
4426
|
const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
4385
4427
|
if (existing) {
|
|
4386
4428
|
const existingId = existing["id"];
|
|
4387
|
-
|
|
4429
|
+
const existingSessionId = existing["session_id"] || null;
|
|
4430
|
+
const existingLastSeen = existing["last_seen_at"];
|
|
4431
|
+
if (sessionId && existingSessionId && existingSessionId !== sessionId) {
|
|
4432
|
+
const lastSeenMs = new Date(existingLastSeen).getTime();
|
|
4433
|
+
const nowMs = Date.now();
|
|
4434
|
+
if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
|
|
4435
|
+
throw new AgentConflictError({
|
|
4436
|
+
existing_id: existingId,
|
|
4437
|
+
existing_name: normalizedName,
|
|
4438
|
+
last_seen_at: existingLastSeen,
|
|
4439
|
+
session_hint: existingSessionId.slice(0, 8),
|
|
4440
|
+
working_dir: null
|
|
4441
|
+
});
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
|
|
4388
4445
|
timestamp,
|
|
4446
|
+
sessionId ?? existingSessionId,
|
|
4389
4447
|
existingId
|
|
4390
4448
|
]);
|
|
4391
4449
|
if (description) {
|
|
4392
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
4393
|
-
description,
|
|
4394
|
-
existingId
|
|
4395
|
-
]);
|
|
4450
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
|
|
4396
4451
|
}
|
|
4397
4452
|
if (role) {
|
|
4398
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
]);
|
|
4453
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
|
|
4454
|
+
}
|
|
4455
|
+
if (projectId !== undefined) {
|
|
4456
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
4402
4457
|
}
|
|
4403
4458
|
return getAgent(existingId, d);
|
|
4404
4459
|
}
|
|
4405
4460
|
const id = shortUuid();
|
|
4406
|
-
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, normalizedName, description || null, role || "agent", timestamp, timestamp]);
|
|
4461
|
+
d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
|
|
4407
4462
|
return getAgent(id, d);
|
|
4408
4463
|
}
|
|
4409
4464
|
function getAgent(idOrName, db) {
|
|
@@ -5433,6 +5488,93 @@ function getMemoryVersions(memoryId, db) {
|
|
|
5433
5488
|
}
|
|
5434
5489
|
}
|
|
5435
5490
|
|
|
5491
|
+
// src/db/locks.ts
|
|
5492
|
+
function parseLockRow(row) {
|
|
5493
|
+
return {
|
|
5494
|
+
id: row["id"],
|
|
5495
|
+
resource_type: row["resource_type"],
|
|
5496
|
+
resource_id: row["resource_id"],
|
|
5497
|
+
agent_id: row["agent_id"],
|
|
5498
|
+
lock_type: row["lock_type"],
|
|
5499
|
+
locked_at: row["locked_at"],
|
|
5500
|
+
expires_at: row["expires_at"]
|
|
5501
|
+
};
|
|
5502
|
+
}
|
|
5503
|
+
function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive", ttlSeconds = 300, db) {
|
|
5504
|
+
const d = db || getDatabase();
|
|
5505
|
+
cleanExpiredLocks(d);
|
|
5506
|
+
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at > datetime('now')").get(resourceType, resourceId, agentId, lockType);
|
|
5507
|
+
if (ownLock) {
|
|
5508
|
+
const newExpiry = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
5509
|
+
d.run("UPDATE resource_locks SET expires_at = ? WHERE id = ?", [
|
|
5510
|
+
newExpiry,
|
|
5511
|
+
ownLock["id"]
|
|
5512
|
+
]);
|
|
5513
|
+
return parseLockRow({ ...ownLock, expires_at: newExpiry });
|
|
5514
|
+
}
|
|
5515
|
+
if (lockType === "exclusive") {
|
|
5516
|
+
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at > datetime('now')").get(resourceType, resourceId, agentId);
|
|
5517
|
+
if (existing) {
|
|
5518
|
+
return null;
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
const id = shortUuid();
|
|
5522
|
+
const lockedAt = now();
|
|
5523
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
5524
|
+
d.run("INSERT INTO resource_locks (id, resource_type, resource_id, agent_id, lock_type, locked_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, resourceType, resourceId, agentId, lockType, lockedAt, expiresAt]);
|
|
5525
|
+
return {
|
|
5526
|
+
id,
|
|
5527
|
+
resource_type: resourceType,
|
|
5528
|
+
resource_id: resourceId,
|
|
5529
|
+
agent_id: agentId,
|
|
5530
|
+
lock_type: lockType,
|
|
5531
|
+
locked_at: lockedAt,
|
|
5532
|
+
expires_at: expiresAt
|
|
5533
|
+
};
|
|
5534
|
+
}
|
|
5535
|
+
function releaseLock(lockId, agentId, db) {
|
|
5536
|
+
const d = db || getDatabase();
|
|
5537
|
+
const result = d.run("DELETE FROM resource_locks WHERE id = ? AND agent_id = ?", [lockId, agentId]);
|
|
5538
|
+
return result.changes > 0;
|
|
5539
|
+
}
|
|
5540
|
+
function checkLock(resourceType, resourceId, lockType, db) {
|
|
5541
|
+
const d = db || getDatabase();
|
|
5542
|
+
cleanExpiredLocks(d);
|
|
5543
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > datetime('now')" : "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND expires_at > datetime('now')";
|
|
5544
|
+
const rows = lockType ? d.query(query).all(resourceType, resourceId, lockType) : d.query(query).all(resourceType, resourceId);
|
|
5545
|
+
return rows.map(parseLockRow);
|
|
5546
|
+
}
|
|
5547
|
+
function listAgentLocks(agentId, db) {
|
|
5548
|
+
const d = db || getDatabase();
|
|
5549
|
+
cleanExpiredLocks(d);
|
|
5550
|
+
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at > datetime('now') ORDER BY locked_at DESC").all(agentId);
|
|
5551
|
+
return rows.map(parseLockRow);
|
|
5552
|
+
}
|
|
5553
|
+
function cleanExpiredLocks(db) {
|
|
5554
|
+
const d = db || getDatabase();
|
|
5555
|
+
const result = d.run("DELETE FROM resource_locks WHERE expires_at <= datetime('now')");
|
|
5556
|
+
return result.changes;
|
|
5557
|
+
}
|
|
5558
|
+
|
|
5559
|
+
// src/lib/memory-lock.ts
|
|
5560
|
+
var MEMORY_WRITE_TTL = 30;
|
|
5561
|
+
function memoryLockId(key, scope, projectId) {
|
|
5562
|
+
return `${scope}:${key}:${projectId ?? ""}`;
|
|
5563
|
+
}
|
|
5564
|
+
function acquireMemoryWriteLock(agentId, key, scope, projectId, ttlSeconds = MEMORY_WRITE_TTL, db) {
|
|
5565
|
+
const d = db || getDatabase();
|
|
5566
|
+
return acquireLock(agentId, "memory", memoryLockId(key, scope, projectId), "exclusive", ttlSeconds, d);
|
|
5567
|
+
}
|
|
5568
|
+
function releaseMemoryWriteLock(lockId, agentId, db) {
|
|
5569
|
+
const d = db || getDatabase();
|
|
5570
|
+
return releaseLock(lockId, agentId, d);
|
|
5571
|
+
}
|
|
5572
|
+
function checkMemoryWriteLock(key, scope, projectId, db) {
|
|
5573
|
+
const d = db || getDatabase();
|
|
5574
|
+
const locks = checkLock("memory", memoryLockId(key, scope, projectId), "exclusive", d);
|
|
5575
|
+
return locks[0] ?? null;
|
|
5576
|
+
}
|
|
5577
|
+
|
|
5436
5578
|
// src/lib/search.ts
|
|
5437
5579
|
function parseMemoryRow2(row) {
|
|
5438
5580
|
return {
|
|
@@ -6709,11 +6851,13 @@ ${lines.join(`
|
|
|
6709
6851
|
});
|
|
6710
6852
|
server.tool("register_agent", "Register an agent. Idempotent \u2014 same name returns existing agent.", {
|
|
6711
6853
|
name: exports_external.string(),
|
|
6854
|
+
session_id: exports_external.string().optional(),
|
|
6712
6855
|
description: exports_external.string().optional(),
|
|
6713
|
-
role: exports_external.string().optional()
|
|
6856
|
+
role: exports_external.string().optional(),
|
|
6857
|
+
project_id: exports_external.string().optional()
|
|
6714
6858
|
}, async (args) => {
|
|
6715
6859
|
try {
|
|
6716
|
-
const agent = registerAgent(args.name, args.description, args.role);
|
|
6860
|
+
const agent = registerAgent(args.name, args.session_id, args.description, args.role, args.project_id);
|
|
6717
6861
|
return {
|
|
6718
6862
|
content: [{
|
|
6719
6863
|
type: "text",
|
|
@@ -7817,6 +7961,103 @@ server.resource("projects", "mementos://projects", { description: "All registere
|
|
|
7817
7961
|
const projects = listProjects();
|
|
7818
7962
|
return { contents: [{ uri: "mementos://projects", text: JSON.stringify(projects, null, 2), mimeType: "application/json" }] };
|
|
7819
7963
|
});
|
|
7964
|
+
server.tool("memory_lock", "Acquire an exclusive write lock on a memory key to prevent concurrent writes.", {
|
|
7965
|
+
agent_id: exports_external.string(),
|
|
7966
|
+
key: exports_external.string(),
|
|
7967
|
+
scope: exports_external.string().optional().default("shared"),
|
|
7968
|
+
project_id: exports_external.string().optional(),
|
|
7969
|
+
ttl_seconds: exports_external.number().optional().default(30)
|
|
7970
|
+
}, async (args) => {
|
|
7971
|
+
const lock = acquireMemoryWriteLock(args.agent_id, args.key, args.scope, args.project_id, args.ttl_seconds);
|
|
7972
|
+
if (!lock) {
|
|
7973
|
+
const existing = checkMemoryWriteLock(args.key, args.scope, args.project_id);
|
|
7974
|
+
return {
|
|
7975
|
+
content: [{
|
|
7976
|
+
type: "text",
|
|
7977
|
+
text: `Lock conflict: memory key "${args.key}" is write-locked by agent ${existing?.agent_id ?? "unknown"} (expires ${existing?.expires_at ?? "unknown"}). Retry after a few seconds.`
|
|
7978
|
+
}],
|
|
7979
|
+
isError: true
|
|
7980
|
+
};
|
|
7981
|
+
}
|
|
7982
|
+
return {
|
|
7983
|
+
content: [{
|
|
7984
|
+
type: "text",
|
|
7985
|
+
text: `Lock acquired: ${lock.id} on key "${args.key}" (expires ${lock.expires_at})`
|
|
7986
|
+
}]
|
|
7987
|
+
};
|
|
7988
|
+
});
|
|
7989
|
+
server.tool("memory_unlock", "Release a memory write lock.", {
|
|
7990
|
+
lock_id: exports_external.string(),
|
|
7991
|
+
agent_id: exports_external.string()
|
|
7992
|
+
}, async (args) => {
|
|
7993
|
+
const released = releaseMemoryWriteLock(args.lock_id, args.agent_id);
|
|
7994
|
+
return {
|
|
7995
|
+
content: [{
|
|
7996
|
+
type: "text",
|
|
7997
|
+
text: released ? `Lock ${args.lock_id} released.` : `Lock ${args.lock_id} not found or not owned by ${args.agent_id}.`
|
|
7998
|
+
}]
|
|
7999
|
+
};
|
|
8000
|
+
});
|
|
8001
|
+
server.tool("memory_check_lock", "Check if a memory key is currently write-locked.", {
|
|
8002
|
+
key: exports_external.string(),
|
|
8003
|
+
scope: exports_external.string().optional().default("shared"),
|
|
8004
|
+
project_id: exports_external.string().optional()
|
|
8005
|
+
}, async (args) => {
|
|
8006
|
+
const lock = checkMemoryWriteLock(args.key, args.scope, args.project_id);
|
|
8007
|
+
return {
|
|
8008
|
+
content: [{
|
|
8009
|
+
type: "text",
|
|
8010
|
+
text: lock ? `Locked: key "${args.key}" held by agent ${lock.agent_id} (expires ${lock.expires_at})` : `Unlocked: key "${args.key}" is free to write.`
|
|
8011
|
+
}]
|
|
8012
|
+
};
|
|
8013
|
+
});
|
|
8014
|
+
server.tool("resource_lock", "Acquire a lock on any resource (project, memory, entity, agent, connector).", {
|
|
8015
|
+
agent_id: exports_external.string(),
|
|
8016
|
+
resource_type: exports_external.enum(["project", "memory", "entity", "agent", "connector"]),
|
|
8017
|
+
resource_id: exports_external.string(),
|
|
8018
|
+
lock_type: exports_external.enum(["advisory", "exclusive"]).optional().default("exclusive"),
|
|
8019
|
+
ttl_seconds: exports_external.number().optional().default(300)
|
|
8020
|
+
}, async (args) => {
|
|
8021
|
+
const lock = acquireLock(args.agent_id, args.resource_type, args.resource_id, args.lock_type, args.ttl_seconds);
|
|
8022
|
+
if (!lock) {
|
|
8023
|
+
return {
|
|
8024
|
+
content: [{ type: "text", text: `Lock conflict on ${args.resource_type}:${args.resource_id}. Another agent holds an exclusive lock.` }],
|
|
8025
|
+
isError: true
|
|
8026
|
+
};
|
|
8027
|
+
}
|
|
8028
|
+
return {
|
|
8029
|
+
content: [{ type: "text", text: `Lock acquired: ${lock.id} (expires ${lock.expires_at})` }]
|
|
8030
|
+
};
|
|
8031
|
+
});
|
|
8032
|
+
server.tool("resource_unlock", "Release a resource lock.", {
|
|
8033
|
+
lock_id: exports_external.string(),
|
|
8034
|
+
agent_id: exports_external.string()
|
|
8035
|
+
}, async (args) => {
|
|
8036
|
+
const released = releaseLock(args.lock_id, args.agent_id);
|
|
8037
|
+
return {
|
|
8038
|
+
content: [{ type: "text", text: released ? `Released.` : `Not found or not owned.` }]
|
|
8039
|
+
};
|
|
8040
|
+
});
|
|
8041
|
+
server.tool("resource_check_lock", "Check active locks on a resource.", {
|
|
8042
|
+
resource_type: exports_external.enum(["project", "memory", "entity", "agent", "connector"]),
|
|
8043
|
+
resource_id: exports_external.string(),
|
|
8044
|
+
lock_type: exports_external.enum(["advisory", "exclusive"]).optional()
|
|
8045
|
+
}, async (args) => {
|
|
8046
|
+
const locks = checkLock(args.resource_type, args.resource_id, args.lock_type);
|
|
8047
|
+
return {
|
|
8048
|
+
content: [{ type: "text", text: locks.length === 0 ? "No active locks." : JSON.stringify(locks, null, 2) }]
|
|
8049
|
+
};
|
|
8050
|
+
});
|
|
8051
|
+
server.tool("list_agent_locks", "List all active resource locks held by an agent.", { agent_id: exports_external.string() }, async (args) => {
|
|
8052
|
+
const locks = listAgentLocks(args.agent_id);
|
|
8053
|
+
return {
|
|
8054
|
+
content: [{ type: "text", text: locks.length === 0 ? "No active locks." : JSON.stringify(locks, null, 2) }]
|
|
8055
|
+
};
|
|
8056
|
+
});
|
|
8057
|
+
server.tool("clean_expired_locks", "Delete all expired resource locks.", {}, async () => {
|
|
8058
|
+
const count = cleanExpiredLocks();
|
|
8059
|
+
return { content: [{ type: "text", text: `Cleaned ${count} expired lock(s).` }] };
|
|
8060
|
+
});
|
|
7820
8061
|
async function main() {
|
|
7821
8062
|
const transport = new StdioServerTransport;
|
|
7822
8063
|
await server.connect(transport);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAgxCH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA6H9C"}
|
package/dist/server/index.js
CHANGED
|
@@ -24,6 +24,24 @@ import { dirname as dirname3, extname, join as join3, resolve as resolve3, sep }
|
|
|
24
24
|
import { fileURLToPath } from "url";
|
|
25
25
|
|
|
26
26
|
// src/types/index.ts
|
|
27
|
+
class AgentConflictError extends Error {
|
|
28
|
+
conflict = true;
|
|
29
|
+
existing_id;
|
|
30
|
+
existing_name;
|
|
31
|
+
last_seen_at;
|
|
32
|
+
session_hint;
|
|
33
|
+
working_dir;
|
|
34
|
+
constructor(opts) {
|
|
35
|
+
const msg = `Agent "${opts.existing_name}" is already active (session hint: ${opts.session_hint ?? "unknown"}, last seen ${opts.last_seen_at}). Wait 30 minutes or use a different name.`;
|
|
36
|
+
super(msg);
|
|
37
|
+
this.name = "AgentConflictError";
|
|
38
|
+
this.existing_id = opts.existing_id;
|
|
39
|
+
this.existing_name = opts.existing_name;
|
|
40
|
+
this.last_seen_at = opts.last_seen_at;
|
|
41
|
+
this.session_hint = opts.session_hint;
|
|
42
|
+
this.working_dir = opts.working_dir ?? null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
27
45
|
class EntityNotFoundError extends Error {
|
|
28
46
|
constructor(id) {
|
|
29
47
|
super(`Entity not found: ${id}`);
|
|
@@ -311,6 +329,28 @@ var MIGRATIONS = [
|
|
|
311
329
|
ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
312
330
|
CREATE INDEX IF NOT EXISTS idx_agents_active_project ON agents(active_project_id);
|
|
313
331
|
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
332
|
+
`,
|
|
333
|
+
`
|
|
334
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
335
|
+
CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);
|
|
336
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (7);
|
|
337
|
+
`,
|
|
338
|
+
`
|
|
339
|
+
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
340
|
+
id TEXT PRIMARY KEY,
|
|
341
|
+
resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector')),
|
|
342
|
+
resource_id TEXT NOT NULL,
|
|
343
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
344
|
+
lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
|
|
345
|
+
locked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
346
|
+
expires_at TEXT NOT NULL
|
|
347
|
+
);
|
|
348
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
349
|
+
ON resource_locks(resource_type, resource_id)
|
|
350
|
+
WHERE lock_type = 'exclusive';
|
|
351
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
352
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
353
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (8);
|
|
314
354
|
`
|
|
315
355
|
];
|
|
316
356
|
var _db = null;
|
|
@@ -382,10 +422,12 @@ function redactSecrets(text) {
|
|
|
382
422
|
}
|
|
383
423
|
|
|
384
424
|
// src/db/agents.ts
|
|
425
|
+
var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
385
426
|
function parseAgentRow(row) {
|
|
386
427
|
return {
|
|
387
428
|
id: row["id"],
|
|
388
429
|
name: row["name"],
|
|
430
|
+
session_id: row["session_id"] || null,
|
|
389
431
|
description: row["description"] || null,
|
|
390
432
|
role: row["role"] || null,
|
|
391
433
|
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
@@ -394,33 +436,46 @@ function parseAgentRow(row) {
|
|
|
394
436
|
last_seen_at: row["last_seen_at"]
|
|
395
437
|
};
|
|
396
438
|
}
|
|
397
|
-
function registerAgent(name, description, role, db) {
|
|
439
|
+
function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
398
440
|
const d = db || getDatabase();
|
|
399
441
|
const timestamp = now();
|
|
400
442
|
const normalizedName = name.trim().toLowerCase();
|
|
401
443
|
const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
402
444
|
if (existing) {
|
|
403
445
|
const existingId = existing["id"];
|
|
404
|
-
|
|
446
|
+
const existingSessionId = existing["session_id"] || null;
|
|
447
|
+
const existingLastSeen = existing["last_seen_at"];
|
|
448
|
+
if (sessionId && existingSessionId && existingSessionId !== sessionId) {
|
|
449
|
+
const lastSeenMs = new Date(existingLastSeen).getTime();
|
|
450
|
+
const nowMs = Date.now();
|
|
451
|
+
if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
|
|
452
|
+
throw new AgentConflictError({
|
|
453
|
+
existing_id: existingId,
|
|
454
|
+
existing_name: normalizedName,
|
|
455
|
+
last_seen_at: existingLastSeen,
|
|
456
|
+
session_hint: existingSessionId.slice(0, 8),
|
|
457
|
+
working_dir: null
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
|
|
405
462
|
timestamp,
|
|
463
|
+
sessionId ?? existingSessionId,
|
|
406
464
|
existingId
|
|
407
465
|
]);
|
|
408
466
|
if (description) {
|
|
409
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
410
|
-
description,
|
|
411
|
-
existingId
|
|
412
|
-
]);
|
|
467
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
|
|
413
468
|
}
|
|
414
469
|
if (role) {
|
|
415
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
]);
|
|
470
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
|
|
471
|
+
}
|
|
472
|
+
if (projectId !== undefined) {
|
|
473
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
419
474
|
}
|
|
420
475
|
return getAgent(existingId, d);
|
|
421
476
|
}
|
|
422
477
|
const id = shortUuid();
|
|
423
|
-
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, normalizedName, description || null, role || "agent", timestamp, timestamp]);
|
|
478
|
+
d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
|
|
424
479
|
return getAgent(id, d);
|
|
425
480
|
}
|
|
426
481
|
function getAgent(idOrName, db) {
|
|
@@ -1496,6 +1551,79 @@ function getMemoryVersions(memoryId, db) {
|
|
|
1496
1551
|
}
|
|
1497
1552
|
}
|
|
1498
1553
|
|
|
1554
|
+
// src/db/locks.ts
|
|
1555
|
+
function parseLockRow(row) {
|
|
1556
|
+
return {
|
|
1557
|
+
id: row["id"],
|
|
1558
|
+
resource_type: row["resource_type"],
|
|
1559
|
+
resource_id: row["resource_id"],
|
|
1560
|
+
agent_id: row["agent_id"],
|
|
1561
|
+
lock_type: row["lock_type"],
|
|
1562
|
+
locked_at: row["locked_at"],
|
|
1563
|
+
expires_at: row["expires_at"]
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive", ttlSeconds = 300, db) {
|
|
1567
|
+
const d = db || getDatabase();
|
|
1568
|
+
cleanExpiredLocks(d);
|
|
1569
|
+
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at > datetime('now')").get(resourceType, resourceId, agentId, lockType);
|
|
1570
|
+
if (ownLock) {
|
|
1571
|
+
const newExpiry = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1572
|
+
d.run("UPDATE resource_locks SET expires_at = ? WHERE id = ?", [
|
|
1573
|
+
newExpiry,
|
|
1574
|
+
ownLock["id"]
|
|
1575
|
+
]);
|
|
1576
|
+
return parseLockRow({ ...ownLock, expires_at: newExpiry });
|
|
1577
|
+
}
|
|
1578
|
+
if (lockType === "exclusive") {
|
|
1579
|
+
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at > datetime('now')").get(resourceType, resourceId, agentId);
|
|
1580
|
+
if (existing) {
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
const id = shortUuid();
|
|
1585
|
+
const lockedAt = now();
|
|
1586
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
1587
|
+
d.run("INSERT INTO resource_locks (id, resource_type, resource_id, agent_id, lock_type, locked_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, resourceType, resourceId, agentId, lockType, lockedAt, expiresAt]);
|
|
1588
|
+
return {
|
|
1589
|
+
id,
|
|
1590
|
+
resource_type: resourceType,
|
|
1591
|
+
resource_id: resourceId,
|
|
1592
|
+
agent_id: agentId,
|
|
1593
|
+
lock_type: lockType,
|
|
1594
|
+
locked_at: lockedAt,
|
|
1595
|
+
expires_at: expiresAt
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function releaseLock(lockId, agentId, db) {
|
|
1599
|
+
const d = db || getDatabase();
|
|
1600
|
+
const result = d.run("DELETE FROM resource_locks WHERE id = ? AND agent_id = ?", [lockId, agentId]);
|
|
1601
|
+
return result.changes > 0;
|
|
1602
|
+
}
|
|
1603
|
+
function releaseAllAgentLocks(agentId, db) {
|
|
1604
|
+
const d = db || getDatabase();
|
|
1605
|
+
const result = d.run("DELETE FROM resource_locks WHERE agent_id = ?", [agentId]);
|
|
1606
|
+
return result.changes;
|
|
1607
|
+
}
|
|
1608
|
+
function checkLock(resourceType, resourceId, lockType, db) {
|
|
1609
|
+
const d = db || getDatabase();
|
|
1610
|
+
cleanExpiredLocks(d);
|
|
1611
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > datetime('now')" : "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND expires_at > datetime('now')";
|
|
1612
|
+
const rows = lockType ? d.query(query).all(resourceType, resourceId, lockType) : d.query(query).all(resourceType, resourceId);
|
|
1613
|
+
return rows.map(parseLockRow);
|
|
1614
|
+
}
|
|
1615
|
+
function listAgentLocks(agentId, db) {
|
|
1616
|
+
const d = db || getDatabase();
|
|
1617
|
+
cleanExpiredLocks(d);
|
|
1618
|
+
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at > datetime('now') ORDER BY locked_at DESC").all(agentId);
|
|
1619
|
+
return rows.map(parseLockRow);
|
|
1620
|
+
}
|
|
1621
|
+
function cleanExpiredLocks(db) {
|
|
1622
|
+
const d = db || getDatabase();
|
|
1623
|
+
const result = d.run("DELETE FROM resource_locks WHERE expires_at <= datetime('now')");
|
|
1624
|
+
return result.changes;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1499
1627
|
// src/lib/search.ts
|
|
1500
1628
|
function parseMemoryRow2(row) {
|
|
1501
1629
|
return {
|
|
@@ -2604,7 +2732,7 @@ addRoute("POST", "/api/agents", async (req) => {
|
|
|
2604
2732
|
if (!body || !body["name"]) {
|
|
2605
2733
|
return errorResponse("Missing required field: name", 400);
|
|
2606
2734
|
}
|
|
2607
|
-
const agent = registerAgent(body["name"], body["description"], body["role"]);
|
|
2735
|
+
const agent = registerAgent(body["name"], body["session_id"], body["description"], body["role"], body["project_id"]);
|
|
2608
2736
|
return json(agent, 201);
|
|
2609
2737
|
});
|
|
2610
2738
|
addRoute("GET", "/api/agents/:id", (_req, _url, params) => {
|
|
@@ -2639,6 +2767,47 @@ addRoute("PATCH", "/api/agents/:id", async (req, _url, params) => {
|
|
|
2639
2767
|
return errorResponse(e instanceof Error ? e.message : "Update failed", 400);
|
|
2640
2768
|
}
|
|
2641
2769
|
});
|
|
2770
|
+
addRoute("POST", "/api/locks", async (req) => {
|
|
2771
|
+
const body = await readJson(req);
|
|
2772
|
+
if (!body?.agent_id || !body?.resource_type || !body?.resource_id) {
|
|
2773
|
+
return errorResponse("Missing required fields: agent_id, resource_type, resource_id", 400);
|
|
2774
|
+
}
|
|
2775
|
+
const lock = acquireLock(body["agent_id"], body["resource_type"], body["resource_id"], body["lock_type"] || "exclusive", body["ttl_seconds"] || 300);
|
|
2776
|
+
if (!lock) {
|
|
2777
|
+
const existing = checkLock(body["resource_type"], body["resource_id"], "exclusive");
|
|
2778
|
+
return errorResponse(`Lock conflict: resource ${body["resource_type"]}:${body["resource_id"]} is held by agent ${existing[0]?.agent_id ?? "unknown"}`, 409);
|
|
2779
|
+
}
|
|
2780
|
+
return json(lock, 201);
|
|
2781
|
+
});
|
|
2782
|
+
addRoute("GET", "/api/locks", (_req, url) => {
|
|
2783
|
+
const resourceType = url.searchParams.get("resource_type");
|
|
2784
|
+
const resourceId = url.searchParams.get("resource_id");
|
|
2785
|
+
const lockType = url.searchParams.get("lock_type");
|
|
2786
|
+
if (!resourceType || !resourceId) {
|
|
2787
|
+
return errorResponse("Missing required query params: resource_type, resource_id", 400);
|
|
2788
|
+
}
|
|
2789
|
+
return json(checkLock(resourceType, resourceId, lockType));
|
|
2790
|
+
});
|
|
2791
|
+
addRoute("DELETE", "/api/locks/:id", async (req, _url, params) => {
|
|
2792
|
+
const body = await readJson(req);
|
|
2793
|
+
if (!body?.agent_id)
|
|
2794
|
+
return errorResponse("Missing required field: agent_id", 400);
|
|
2795
|
+
const released = releaseLock(params["id"], body["agent_id"]);
|
|
2796
|
+
if (!released)
|
|
2797
|
+
return errorResponse("Lock not found or not owned by this agent", 404);
|
|
2798
|
+
return json({ released: true });
|
|
2799
|
+
});
|
|
2800
|
+
addRoute("GET", "/api/agents/:id/locks", (_req, _url, params) => {
|
|
2801
|
+
return json(listAgentLocks(params["id"]));
|
|
2802
|
+
});
|
|
2803
|
+
addRoute("DELETE", "/api/agents/:id/locks", (_req, _url, params) => {
|
|
2804
|
+
const count = releaseAllAgentLocks(params["id"]);
|
|
2805
|
+
return json({ released: count });
|
|
2806
|
+
});
|
|
2807
|
+
addRoute("POST", "/api/locks/clean", () => {
|
|
2808
|
+
const count = cleanExpiredLocks();
|
|
2809
|
+
return json({ cleaned: count });
|
|
2810
|
+
});
|
|
2642
2811
|
addRoute("GET", "/api/projects", (_req, url) => {
|
|
2643
2812
|
const q = getSearchParams(url);
|
|
2644
2813
|
const projects = listProjects();
|
package/dist/types/index.d.ts
CHANGED
|
@@ -85,6 +85,7 @@ export interface MemorySearchResult {
|
|
|
85
85
|
export interface Agent {
|
|
86
86
|
id: string;
|
|
87
87
|
name: string;
|
|
88
|
+
session_id: string | null;
|
|
88
89
|
description: string | null;
|
|
89
90
|
role: string | null;
|
|
90
91
|
metadata: Record<string, unknown>;
|
|
@@ -92,6 +93,22 @@ export interface Agent {
|
|
|
92
93
|
created_at: string;
|
|
93
94
|
last_seen_at: string;
|
|
94
95
|
}
|
|
96
|
+
export declare class AgentConflictError extends Error {
|
|
97
|
+
readonly conflict: true;
|
|
98
|
+
readonly existing_id: string;
|
|
99
|
+
readonly existing_name: string;
|
|
100
|
+
readonly last_seen_at: string;
|
|
101
|
+
readonly session_hint: string | null;
|
|
102
|
+
readonly working_dir: string | null;
|
|
103
|
+
constructor(opts: {
|
|
104
|
+
existing_id: string;
|
|
105
|
+
existing_name: string;
|
|
106
|
+
last_seen_at: string;
|
|
107
|
+
session_hint: string | null;
|
|
108
|
+
working_dir?: string | null;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export declare function isAgentConflict(result: unknown): result is AgentConflictError;
|
|
95
112
|
export interface Project {
|
|
96
113
|
id: string;
|
|
97
114
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAM1D,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;AAM7E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAM7E,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAM7D,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAMD,MAAM,WAAW,mBAAoB,SAAQ,MAAM;IACjD,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAMD,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IACvC,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;IACtC,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnD;AAMD,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAgB,QAAQ,EAAG,IAAI,CAAU;IACzC,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,aAAa,EAAE,MAAM,CAAC;IACtC,SAAgB,YAAY,EAAE,MAAM,CAAC;IACrC,SAAgB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,SAAgB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;gBAE/B,IAAI,EAAE;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B;CAUF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,kBAAkB,CAE7E;AAMD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAMD,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,WAAW,CAAC;IAC3B,gBAAgB,EAAE,cAAc,CAAC;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACnD,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,EAAE,cAAc,EAAE,CAAC;QAC7B,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,UAAU,EAAE;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,uBAAuB,EAAE,MAAM,CAAC;KACjC,CAAC;CACH;AAMD,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAM5C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,cAAc,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,aAAa,CAAC;IACzB,mBAAmB,CAAC,EAAE,kBAAkB,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAMD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,cAAc,CAAC;AACjH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;AACpI,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1D,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,YAAY,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAoB,SAAQ,MAAM;IACjD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,YAAY,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,EAAE,EAAE,MAAM;CAIvB;AAUD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,EAAE,EAAE,MAAM;CAIvB;AAED,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW;CAI5C;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,EAAE,EAAE,MAAM;CAIvB;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;gBAEV,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAQzD"}
|