pi-harness-runtime 1.1.323 → 1.1.324
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/test/policy-retention.test.ts +2 -2
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/dist/worker.d.ts +19 -5
- package/packages/durable-review/dist/worker.d.ts.map +1 -1
- package/packages/durable-review/dist/worker.js +179 -35
- package/packages/durable-review/dist/worker.js.map +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/durable-review/src/worker.ts +201 -48
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
package/package.json
CHANGED
|
@@ -308,7 +308,7 @@ describe("Policy Retention Tests", () => {
|
|
|
308
308
|
const manifest = await loader.load(TEST_DIR);
|
|
309
309
|
|
|
310
310
|
// Full text should include actual content, not just contract pointers
|
|
311
|
-
const { renderFullPolicyText } = await import("../dist/
|
|
311
|
+
const { renderFullPolicyText } = await import("../dist/index.js");
|
|
312
312
|
const fullText = renderFullPolicyText(manifest);
|
|
313
313
|
|
|
314
314
|
assert.ok(fullText.includes("Full Agents"), "Should include source title");
|
|
@@ -323,7 +323,7 @@ describe("Policy Retention Tests", () => {
|
|
|
323
323
|
});
|
|
324
324
|
|
|
325
325
|
const manifest = await loader.load(TEST_DIR);
|
|
326
|
-
const { checkPolicyTextSize } = await import("../dist/
|
|
326
|
+
const { checkPolicyTextSize } = await import("../dist/index.js");
|
|
327
327
|
|
|
328
328
|
// With normal-sized content, should fit in 100KB limit
|
|
329
329
|
const result = checkPolicyTextSize(manifest, 100 * 1024);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.324",
|
|
4
4
|
"description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Durable Review Worker
|
|
3
3
|
*
|
|
4
|
-
* Node worker entry point with exclusive ownership and Harness integration.
|
|
4
|
+
* Node worker entry point with proper exclusive ownership and Harness integration.
|
|
5
|
+
* Uses atomic lock acquisition (mkdir) and process liveness verification.
|
|
5
6
|
*/
|
|
6
7
|
import type { ReviewRequest, ReviewStatusResponse } from "./types.js";
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Uses a simple file-based lock with stale detection
|
|
9
|
+
* Lock acquisition result
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
interface LockResult {
|
|
12
12
|
acquired: boolean;
|
|
13
13
|
existingOwner?: string;
|
|
14
|
-
|
|
14
|
+
staleLock?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Try to acquire exclusive ownership using file creation
|
|
18
|
+
*
|
|
19
|
+
* Uses exclusive file creation for atomic lock acquisition.
|
|
20
|
+
* The process must be alive (verified by PID check) before taking over a stale lock.
|
|
21
|
+
*/
|
|
22
|
+
export declare function tryAcquireOwnership(workspaceKey: string, workerId: string): Promise<LockResult>;
|
|
15
23
|
/**
|
|
16
24
|
* Release ownership of the workspace database
|
|
17
25
|
*/
|
|
18
26
|
export declare function releaseOwnership(workspaceKey: string, workerId: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Verify we still hold the lock (for worker health checks)
|
|
29
|
+
*/
|
|
30
|
+
export declare function verifyLockOwnership(workspaceKey: string, workerId: string): boolean;
|
|
19
31
|
/**
|
|
20
32
|
* Start the worker for a workspace
|
|
21
33
|
*/
|
|
@@ -23,6 +35,7 @@ export declare function startWorker(workspacePath: string): Promise<{
|
|
|
23
35
|
success: boolean;
|
|
24
36
|
workerId?: string;
|
|
25
37
|
workspaceKey?: string;
|
|
38
|
+
harness?: unknown;
|
|
26
39
|
error?: string;
|
|
27
40
|
}>;
|
|
28
41
|
/**
|
|
@@ -53,4 +66,5 @@ export declare function getJobStatus(_jobId: string): Promise<ReviewStatusRespon
|
|
|
53
66
|
* Cancel a review job
|
|
54
67
|
*/
|
|
55
68
|
export declare function cancelJob(_jobId: string): Promise<boolean>;
|
|
69
|
+
export {};
|
|
56
70
|
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAiCtE;;GAEG;AACH,UAAU,UAAU;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAqCD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAuFrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa5F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CASnF;AAMD;;GAEG;AACH,wBAAsB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAmDD;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAchD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAChD,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAoBD;AAMD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,CA2B1D;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAGvF;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhE"}
|
|
@@ -1,60 +1,143 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Durable Review Worker
|
|
3
3
|
*
|
|
4
|
-
* Node worker entry point with exclusive ownership and Harness integration.
|
|
4
|
+
* Node worker entry point with proper exclusive ownership and Harness integration.
|
|
5
|
+
* Uses atomic lock acquisition (mkdir) and process liveness verification.
|
|
5
6
|
*/
|
|
6
7
|
import { randomUUID } from "node:crypto";
|
|
7
8
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { join } from "node:path";
|
|
9
|
-
import { getWorkspaceDir, hashWorkspaceKey, validateAndHashRequest, toStatusResponse, createJobRecord
|
|
10
|
+
import { getDatabasePath, getWorkspaceDir, hashWorkspaceKey, validateAndHashRequest, toStatusResponse, createJobRecord } from "./service.js";
|
|
10
11
|
/**
|
|
11
12
|
* Current worker state
|
|
12
13
|
*/
|
|
13
14
|
let currentWorker = null;
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Atomic Lock Operations
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
14
18
|
/**
|
|
15
|
-
* Get lock file path for
|
|
19
|
+
* Get lock file path for workspace
|
|
20
|
+
* Single lock file per workspace - only one worker can hold it at a time
|
|
16
21
|
*/
|
|
17
22
|
function getLockPath(workspaceKey) {
|
|
18
23
|
return join(getWorkspaceDir(workspaceKey), ".worker.lock");
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
|
-
*
|
|
22
|
-
|
|
26
|
+
* Check if a process is alive (Unix)
|
|
27
|
+
*/
|
|
28
|
+
function isProcessAlive(pid) {
|
|
29
|
+
try {
|
|
30
|
+
// Unix: sending signal 0 checks if process exists
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify lock owner is still alive
|
|
40
|
+
*/
|
|
41
|
+
function verifyOwnerAlive(lock) {
|
|
42
|
+
if (lock.pid === process.pid) {
|
|
43
|
+
return true; // Same process
|
|
44
|
+
}
|
|
45
|
+
return isProcessAlive(lock.pid);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Try to acquire exclusive ownership using file creation
|
|
49
|
+
*
|
|
50
|
+
* Uses exclusive file creation for atomic lock acquisition.
|
|
51
|
+
* The process must be alive (verified by PID check) before taking over a stale lock.
|
|
23
52
|
*/
|
|
24
53
|
export async function tryAcquireOwnership(workspaceKey, workerId) {
|
|
54
|
+
const lockDir = join(getWorkspaceDir(workspaceKey), "locks");
|
|
25
55
|
const lockPath = getLockPath(workspaceKey);
|
|
26
|
-
const
|
|
27
|
-
|
|
56
|
+
const lockContent = {
|
|
57
|
+
workerId,
|
|
58
|
+
pid: process.pid,
|
|
59
|
+
startedAt: Date.now(),
|
|
60
|
+
lockId: randomUUID(), // Unique ID for this lock instance
|
|
61
|
+
};
|
|
62
|
+
// Ensure lock directory exists
|
|
28
63
|
if (!existsSync(lockDir)) {
|
|
29
64
|
mkdirSync(lockDir, { recursive: true });
|
|
30
65
|
}
|
|
31
66
|
try {
|
|
32
|
-
// Check if lock exists
|
|
67
|
+
// Check if our lock file already exists
|
|
33
68
|
if (existsSync(lockPath)) {
|
|
34
|
-
const existing = JSON.parse(readFileSync(lockPath, "
|
|
35
|
-
//
|
|
36
|
-
if (existing.workerId === workerId) {
|
|
69
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
70
|
+
// Same worker - we already have the lock
|
|
71
|
+
if (existing.workerId === workerId && existing.pid === process.pid) {
|
|
72
|
+
console.log("[lock] Same worker, returning acquired");
|
|
37
73
|
return { acquired: true };
|
|
38
74
|
}
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
75
|
+
// Different worker - check if owner is still alive
|
|
76
|
+
if (verifyOwnerAlive(existing)) {
|
|
77
|
+
// Owner is alive, we can't take the lock
|
|
78
|
+
return { acquired: false, existingOwner: existing.workerId };
|
|
79
|
+
}
|
|
80
|
+
// Owner is dead - we can take over
|
|
81
|
+
// Write new lock content atomically via rename
|
|
82
|
+
const tmpPath = lockPath + ".tmp." + process.pid;
|
|
83
|
+
writeFileSync(tmpPath, JSON.stringify(lockContent), "utf8");
|
|
84
|
+
try {
|
|
85
|
+
// Rename is atomic on POSIX when destination is on same filesystem
|
|
86
|
+
// This takes over the lock
|
|
87
|
+
const { renameSync } = await import("node:fs");
|
|
88
|
+
renameSync(tmpPath, lockPath);
|
|
89
|
+
return { acquired: true, staleLock: true };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Takeover failed (another process beat us)
|
|
93
|
+
try {
|
|
94
|
+
unlinkSync(tmpPath);
|
|
95
|
+
}
|
|
96
|
+
catch { /* ignore */ }
|
|
43
97
|
return { acquired: false, existingOwner: existing.workerId };
|
|
44
98
|
}
|
|
45
|
-
// Lock is stale, we can take over
|
|
46
99
|
}
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
startedAt: Date.now(),
|
|
52
|
-
};
|
|
53
|
-
writeFileSync(lockPath, JSON.stringify(lockContent), "utf-8");
|
|
100
|
+
// Lock doesn't exist - create it atomically
|
|
101
|
+
// Use writeFileSync with flag 'wx' for exclusive create
|
|
102
|
+
const { writeFileSync: wfSync } = await import("node:fs");
|
|
103
|
+
wfSync(lockPath, JSON.stringify(lockContent), { flag: "wx" });
|
|
54
104
|
return { acquired: true };
|
|
55
105
|
}
|
|
56
|
-
catch (
|
|
57
|
-
|
|
106
|
+
catch (err) {
|
|
107
|
+
const error = err;
|
|
108
|
+
if (error.code === "EEXIST") {
|
|
109
|
+
// Race: another process created it first
|
|
110
|
+
try {
|
|
111
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
112
|
+
if (verifyOwnerAlive(existing)) {
|
|
113
|
+
return { acquired: false, existingOwner: existing.workerId };
|
|
114
|
+
}
|
|
115
|
+
// Owner is dead, try takeover
|
|
116
|
+
const takeoverContent = {
|
|
117
|
+
...lockContent,
|
|
118
|
+
lockId: randomUUID(),
|
|
119
|
+
};
|
|
120
|
+
const tmpPath = lockPath + ".tmp." + process.pid;
|
|
121
|
+
writeFileSync(tmpPath, JSON.stringify(takeoverContent), "utf8");
|
|
122
|
+
try {
|
|
123
|
+
const { renameSync } = await import("node:fs");
|
|
124
|
+
renameSync(tmpPath, lockPath);
|
|
125
|
+
return { acquired: true, staleLock: true };
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
try {
|
|
129
|
+
unlinkSync(tmpPath);
|
|
130
|
+
}
|
|
131
|
+
catch { /* ignore */ }
|
|
132
|
+
return { acquired: false, existingOwner: existing.workerId };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { acquired: false };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Other error
|
|
140
|
+
console.error("[durable-review] Lock acquisition error:", error);
|
|
58
141
|
return { acquired: false };
|
|
59
142
|
}
|
|
60
143
|
}
|
|
@@ -65,7 +148,7 @@ export async function releaseOwnership(workspaceKey, workerId) {
|
|
|
65
148
|
const lockPath = getLockPath(workspaceKey);
|
|
66
149
|
try {
|
|
67
150
|
if (existsSync(lockPath)) {
|
|
68
|
-
const existing = JSON.parse(readFileSync(lockPath, "
|
|
151
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
69
152
|
if (existing.workerId === workerId) {
|
|
70
153
|
unlinkSync(lockPath);
|
|
71
154
|
}
|
|
@@ -75,14 +158,32 @@ export async function releaseOwnership(workspaceKey, workerId) {
|
|
|
75
158
|
// Ignore errors on release
|
|
76
159
|
}
|
|
77
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Verify we still hold the lock (for worker health checks)
|
|
163
|
+
*/
|
|
164
|
+
export function verifyLockOwnership(workspaceKey, workerId) {
|
|
165
|
+
const lockPath = getLockPath(workspaceKey);
|
|
166
|
+
try {
|
|
167
|
+
if (!existsSync(lockPath))
|
|
168
|
+
return false;
|
|
169
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
170
|
+
return existing.workerId === workerId && existing.pid === process.pid;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Worker Lifecycle
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
78
179
|
/**
|
|
79
180
|
* Start the worker for a workspace
|
|
80
181
|
*/
|
|
81
182
|
export async function startWorker(workspacePath) {
|
|
82
|
-
const canonicalPath = workspacePath;
|
|
183
|
+
const canonicalPath = workspacePath;
|
|
83
184
|
const workspaceKey = hashWorkspaceKey(canonicalPath);
|
|
84
185
|
const workerId = `worker-${randomUUID().slice(0, 8)}`;
|
|
85
|
-
// Try to acquire ownership
|
|
186
|
+
// Try to acquire exclusive ownership
|
|
86
187
|
const lockResult = await tryAcquireOwnership(workspaceKey, workerId);
|
|
87
188
|
if (!lockResult.acquired) {
|
|
88
189
|
return {
|
|
@@ -90,19 +191,54 @@ export async function startWorker(workspacePath) {
|
|
|
90
191
|
error: `Workspace is owned by ${lockResult.existingOwner || "another worker"}. Stop it first or try again later.`,
|
|
91
192
|
};
|
|
92
193
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
194
|
+
if (lockResult.staleLock) {
|
|
195
|
+
console.log(`[durable-review] Took over stale lock from ${lockResult.existingOwner}`);
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
// Open SQLite storage using pi-durable's Node adapter
|
|
199
|
+
const { openNodeSqliteStorage } = await import("@earendil-works/pi-durable/storage/sqlite/node");
|
|
200
|
+
const dbPath = getDatabasePath(workspaceKey);
|
|
201
|
+
const storage = await openNodeSqliteStorage(dbPath);
|
|
202
|
+
// Create registry with reviewer extension
|
|
203
|
+
const { createRegistry } = await import("@earendil-works/pi-durable");
|
|
204
|
+
const registry = createRegistry();
|
|
205
|
+
// Open Harness with storage
|
|
206
|
+
const { Harness: HarnessClass } = await import("@earendil-works/pi-durable");
|
|
207
|
+
const { BACKGROUND_CONTEXT } = await import("@earendil-works/chord/context");
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
+
const harness = await HarnessClass.open(storage, { registry }, BACKGROUND_CONTEXT);
|
|
210
|
+
currentWorker = {
|
|
211
|
+
workerId,
|
|
212
|
+
workspaceKey,
|
|
213
|
+
harness,
|
|
214
|
+
storage,
|
|
215
|
+
ready: true,
|
|
216
|
+
};
|
|
217
|
+
return { success: true, workerId, workspaceKey, harness };
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
// Release lock on failure
|
|
221
|
+
await releaseOwnership(workspaceKey, workerId);
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
error: err instanceof Error ? err.message : String(err),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
100
227
|
}
|
|
101
228
|
/**
|
|
102
229
|
* Stop the worker
|
|
103
230
|
*/
|
|
104
231
|
export async function stopWorker() {
|
|
105
232
|
if (currentWorker) {
|
|
233
|
+
try {
|
|
234
|
+
const { BACKGROUND_CONTEXT } = await import("@earendil-works/chord/context");
|
|
235
|
+
if (currentWorker.harness) {
|
|
236
|
+
await currentWorker.harness.close(BACKGROUND_CONTEXT);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Ignore close errors
|
|
241
|
+
}
|
|
106
242
|
await releaseOwnership(currentWorker.workspaceKey, currentWorker.workerId);
|
|
107
243
|
currentWorker = null;
|
|
108
244
|
}
|
|
@@ -117,12 +253,20 @@ export async function checkWorkerReady() {
|
|
|
117
253
|
if (!currentWorker.ready) {
|
|
118
254
|
return { ready: false, workerId: currentWorker.workerId, error: "Worker not ready" };
|
|
119
255
|
}
|
|
256
|
+
// Verify we still hold the lock
|
|
257
|
+
if (!verifyLockOwnership(currentWorker.workspaceKey, currentWorker.workerId)) {
|
|
258
|
+
currentWorker = null;
|
|
259
|
+
return { ready: false, error: "Lock lost" };
|
|
260
|
+
}
|
|
120
261
|
return {
|
|
121
262
|
ready: true,
|
|
122
263
|
workerId: currentWorker.workerId,
|
|
123
264
|
workspaceKey: currentWorker.workspaceKey,
|
|
124
265
|
};
|
|
125
266
|
}
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Review Operations
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
126
270
|
/**
|
|
127
271
|
* Handle incoming review request
|
|
128
272
|
*/
|