@morty-build/cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MBS25BBY.js → chunk-OCDHOJ7L.js} +59 -72
- package/dist/{chunk-MBS25BBY.js.map → chunk-OCDHOJ7L.js.map} +4 -4
- package/dist/{chunk-ZSUNNLEK.js → chunk-XU65UWFZ.js} +2 -2
- package/dist/{chunk-ZSUNNLEK.js.map → chunk-XU65UWFZ.js.map} +1 -1
- package/dist/cli/handler/daemon/install.js +3 -3
- package/dist/cli/handler/daemon/install.js.map +1 -1
- package/dist/cli/handler/daemon/start.js +1 -1
- package/dist/cli/handler/daemon/start.js.map +1 -1
- package/dist/cli/handler/daemon/upgrade.js +1 -1
- package/dist/cli/handler/daemon/upgrade.js.map +1 -1
- package/dist/cli/handler/pair/pair.js +1 -1
- package/dist/cli/handler/pair/pair.js.map +1 -1
- package/dist/{cli-4I5WYVG2.js → cli-AWIX35OG.js} +83 -180
- package/dist/{cli-4I5WYVG2.js.map → cli-AWIX35OG.js.map} +3 -3
- package/dist/cli.js +2 -2
- package/dist/daemon/control/local-client.d.ts +2 -2
- package/dist/daemon/control/local-client.d.ts.map +1 -1
- package/dist/daemon/control/local-client.js +4 -7
- package/dist/daemon/control/local-client.js.map +1 -1
- package/dist/daemon/control/local-server.d.ts +1 -1
- package/dist/daemon/control/local-server.d.ts.map +1 -1
- package/dist/daemon/control/local-server.js +2 -2
- package/dist/daemon/control/local-server.js.map +1 -1
- package/dist/daemon/control/protocol.d.ts +7 -7
- package/dist/daemon/control/protocol.d.ts.map +1 -1
- package/dist/daemon/control/protocol.js +6 -13
- package/dist/daemon/control/protocol.js.map +1 -1
- package/dist/daemon/lifecycle/daemon-client.d.ts +1 -1
- package/dist/daemon/lifecycle/daemon-client.d.ts.map +1 -1
- package/dist/daemon/lifecycle/daemon-client.js +13 -13
- package/dist/daemon/lifecycle/daemon-client.js.map +1 -1
- package/dist/daemon/lifecycle/daemon.js +11 -24
- package/dist/daemon/lifecycle/daemon.js.map +1 -1
- package/dist/engine/runtime/command/native-session-requests.d.ts.map +1 -1
- package/dist/engine/runtime/command/native-session-requests.js +2 -4
- package/dist/engine/runtime/command/native-session-requests.js.map +1 -1
- package/dist/engine/runtime/command/registry/history.d.ts.map +1 -1
- package/dist/engine/runtime/command/registry/history.js +1 -0
- package/dist/engine/runtime/command/registry/history.js.map +1 -1
- package/dist/engine/runtime/command/registry/interactions.js +3 -3
- package/dist/engine/runtime/command/registry/interactions.js.map +1 -1
- package/dist/engine/runtime/connection/runtime-installation-store.d.ts.map +1 -1
- package/dist/engine/runtime/connection/runtime-installation-store.js +5 -13
- package/dist/engine/runtime/connection/runtime-installation-store.js.map +1 -1
- package/dist/engine/runtime/pairing.d.ts.map +1 -1
- package/dist/engine/runtime/pairing.js +6 -11
- package/dist/engine/runtime/pairing.js.map +1 -1
- package/dist/engine/session/prompt/prompt-lifecycle.d.ts +1 -1
- package/dist/engine/session/prompt/prompt-lifecycle.d.ts.map +1 -1
- package/dist/engine/session/prompt/prompt-lifecycle.js +4 -1
- package/dist/engine/session/prompt/prompt-lifecycle.js.map +1 -1
- package/dist/engine/session/turn/active-turn.d.ts.map +1 -1
- package/dist/engine/session/turn/active-turn.js +4 -3
- package/dist/engine/session/turn/active-turn.js.map +1 -1
- package/dist/engine/session/turn/execution-receipts.d.ts +3 -1
- package/dist/engine/session/turn/execution-receipts.d.ts.map +1 -1
- package/dist/engine/session/turn/execution-receipts.js +2 -12
- package/dist/engine/session/turn/execution-receipts.js.map +1 -1
- package/dist/engine/session/turn/session-turns.d.ts +3 -3
- package/dist/engine/storage/session/command-cancellation-table.d.ts +1 -1
- package/dist/engine/storage/session/command-cancellation-table.d.ts.map +1 -1
- package/dist/engine/storage/session/command-cancellation-table.js +1 -1
- package/dist/engine/storage/session/command-cancellation-table.js.map +1 -1
- package/dist/engine.js +2 -2
- package/dist/storage/daemon/daemon-lock.d.ts +6 -4
- package/dist/storage/daemon/daemon-lock.d.ts.map +1 -1
- package/dist/storage/daemon/daemon-lock.js +30 -140
- package/dist/storage/daemon/daemon-lock.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type InstallationDirectory } from "../../engine/storage/installation/installation-files.ts";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
type LockName = "manager.lock" | "installation.lock";
|
|
3
|
+
/** SQLite owns exclusion. The PID file is diagnostic output and cannot authorize or prevent acquisition. */
|
|
4
|
+
export declare function acquireDaemonLock(stateDirectory: InstallationDirectory, filename?: LockName): Promise<(() => Promise<void>) | null>;
|
|
5
|
+
/** Probe the same lock that acquisition holds; a PID can be stale or reused by an unrelated process. */
|
|
6
|
+
export declare function daemonLockExists(stateDirectory: InstallationDirectory, filename?: LockName): Promise<boolean>;
|
|
7
|
+
export {};
|
|
6
8
|
//# sourceMappingURL=daemon-lock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-lock.d.ts","sourceRoot":"","sources":["../../../src/storage/daemon/daemon-lock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"daemon-lock.d.ts","sourceRoot":"","sources":["../../../src/storage/daemon/daemon-lock.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,qBAAqB,EAE7B,MAAM,yDAAyD,CAAC;AAIjE,KAAK,QAAQ,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAiBrD,4GAA4G;AAC5G,wBAAsB,iBAAiB,CACnC,cAAc,EAAE,qBAAqB,EACrC,QAAQ,GAAE,QAAyB,GACpC,OAAO,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAsBvC;AAED,wGAAwG;AACxG,wBAAsB,gBAAgB,CAClC,cAAc,EAAE,qBAAqB,EACrC,QAAQ,GAAE,QAAyB,GACpC,OAAO,CAAC,OAAO,CAAC,CAgBlB"}
|
|
@@ -1,42 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { access, rename, unlink } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
|
-
import {
|
|
6
|
-
import { createInstallationFile, } from "../../engine/storage/installation/installation-files.js";
|
|
5
|
+
import { writeInstallationFile, } from "../../engine/storage/installation/installation-files.js";
|
|
7
6
|
import { isNodeError } from "../../engine/util/node-error.js";
|
|
8
7
|
const SQLITE_BUSY = 5;
|
|
9
|
-
|
|
10
|
-
const OWNERSHIP_CONTENTION_MILLISECONDS = 2_000;
|
|
11
|
-
/**
|
|
12
|
-
* Frees the lock name in one step. On Windows a file that is being unlinked stays
|
|
13
|
-
* in place until the deleting handle closes, and opening or recreating it fails
|
|
14
|
-
* with EPERM meanwhile; renaming it away first means no reader or acquirer of the
|
|
15
|
-
* lock name ever sees that state. A failure leaves the file in place, where its
|
|
16
|
-
* dead owner's PID marks it as stale.
|
|
17
|
-
*/
|
|
18
|
-
async function removeLockFile(path) {
|
|
19
|
-
const released = `${path}.${randomUUID()}.released`;
|
|
20
|
-
try {
|
|
21
|
-
await rename(path, released);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
await unlink(released).catch(() => undefined);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* The lock that makes one process the daemon among releases that take it: SQLite's exclusive lock on `<lock>.sqlite`,
|
|
30
|
-
* which the operating system holds for the process and frees when it exits, however it exits. Its holder's PID is
|
|
31
|
-
* recorded, so the next holder knows which lock file a crashed holder left, even after that PID was reused.
|
|
32
|
-
*/
|
|
8
|
+
/** The operating system releases the SQLite write transaction when its owning process exits. */
|
|
33
9
|
function holdDaemonOwnership(path) {
|
|
34
10
|
const database = new DatabaseSync(path);
|
|
35
11
|
try {
|
|
36
|
-
database.exec("BEGIN
|
|
37
|
-
|
|
38
|
-
// until the connection closes. Set before winning, it would also keep a loser's read lock and block the winner.
|
|
39
|
-
database.exec("PRAGMA locking_mode = EXCLUSIVE");
|
|
12
|
+
database.exec("BEGIN IMMEDIATE");
|
|
13
|
+
return () => database.close();
|
|
40
14
|
}
|
|
41
15
|
catch (cause) {
|
|
42
16
|
database.close();
|
|
@@ -45,134 +19,50 @@ function holdDaemonOwnership(path) {
|
|
|
45
19
|
}
|
|
46
20
|
throw cause;
|
|
47
21
|
}
|
|
48
|
-
try {
|
|
49
|
-
database.exec("CREATE TABLE IF NOT EXISTS daemon_owner (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), pid INTEGER NOT NULL)");
|
|
50
|
-
const previous = database.prepare("SELECT pid FROM daemon_owner").get();
|
|
51
|
-
database.prepare("INSERT OR REPLACE INTO daemon_owner (singleton, pid) VALUES (1, ?)").run(process.pid);
|
|
52
|
-
database.exec("COMMIT");
|
|
53
|
-
return {
|
|
54
|
-
previousOwner: typeof previous?.["pid"] === "number" ? previous["pid"] : null,
|
|
55
|
-
release: () => database.close(),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
catch (cause) {
|
|
59
|
-
database.close();
|
|
60
|
-
throw cause;
|
|
61
|
-
}
|
|
62
22
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Takes the lock file that earlier releases take and read. Only the ownership holder calls this, so contenders never
|
|
65
|
-
* take each other's lock file: one a crashed holder left is replaced, and one a live process of an earlier release holds
|
|
66
|
-
* is kept. Returns the release function, or null when that process holds it.
|
|
67
|
-
*/
|
|
68
|
-
async function takeLockFile(path, previousOwner) {
|
|
69
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
70
|
-
try {
|
|
71
|
-
// A contender never reads an empty lock and mistakes its live owner for a dead one.
|
|
72
|
-
await createInstallationFile(path, `${process.pid.toString()}\n`);
|
|
73
|
-
return async () => await removeLockFile(path);
|
|
74
|
-
}
|
|
75
|
-
catch (cause) {
|
|
76
|
-
if (!isNodeError(cause, "EEXIST")) {
|
|
77
|
-
throw cause;
|
|
78
|
-
}
|
|
79
|
-
const encoded = await readFile(path, "utf8").catch(() => "");
|
|
80
|
-
const pid = Number(encoded.trim());
|
|
81
|
-
// The previous ownership holder no longer holds the lock, so its lock file is stale whatever its PID names
|
|
82
|
-
// now. Any other PID was written by an earlier release, whose daemon holds the lock while it is alive.
|
|
83
|
-
if (Number.isSafeInteger(pid) && pid > 1 && pid !== previousOwner) {
|
|
84
|
-
try {
|
|
85
|
-
process.kill(pid, 0);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
catch (probeCause) {
|
|
89
|
-
if (!isNodeError(probeCause, "ESRCH")) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
await removeLockFile(path);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Takes the ownership lock, or gives up once another process is the daemon. Contenders that start together briefly
|
|
101
|
-
* hold the lock file's database for each other, so while no daemon holds the lock file yet they retry for a moment
|
|
102
|
-
* and one of them wins. A contender that finds a daemon gives up at once, so it never waits long enough to take over
|
|
103
|
-
* when that daemon stops: a stop then leaves no daemon behind, rather than one the stopping client did not start.
|
|
104
|
-
*/
|
|
105
|
-
async function contendForDaemonOwnership(stateDirectory, filename) {
|
|
106
|
-
const path = join(stateDirectory, filename);
|
|
107
|
-
const deadline = Date.now() + OWNERSHIP_CONTENTION_MILLISECONDS;
|
|
108
|
-
for (;;) {
|
|
109
|
-
const ownership = holdDaemonOwnership(`${path}.sqlite`);
|
|
110
|
-
if (ownership !== null) {
|
|
111
|
-
return ownership;
|
|
112
|
-
}
|
|
113
|
-
if ((await daemonLockExists(stateDirectory, filename)) || Date.now() >= deadline) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
// Jitter keeps contenders that collided from colliding again in step.
|
|
117
|
-
await delay(20 + Math.random() * 30);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
/** Makes this process the only daemon. Returns the release function, or null when another daemon holds the lock. */
|
|
23
|
+
/** SQLite owns exclusion. The PID file is diagnostic output and cannot authorize or prevent acquisition. */
|
|
121
24
|
export async function acquireDaemonLock(stateDirectory, filename = "manager.lock") {
|
|
122
25
|
const path = join(stateDirectory, filename);
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
26
|
+
const release = holdDaemonOwnership(`${path}.sqlite`);
|
|
27
|
+
if (release === null) {
|
|
125
28
|
return null;
|
|
126
29
|
}
|
|
127
|
-
let releaseLockFile = null;
|
|
128
30
|
try {
|
|
129
|
-
|
|
31
|
+
await writeInstallationFile(path, `${process.pid.toString()}\n`, AbortSignal.timeout(10_000));
|
|
130
32
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (releaseLockFile === null) {
|
|
137
|
-
return null;
|
|
33
|
+
catch (cause) {
|
|
34
|
+
release();
|
|
35
|
+
throw cause;
|
|
138
36
|
}
|
|
139
|
-
const release = releaseLockFile;
|
|
140
37
|
return async () => {
|
|
141
|
-
|
|
142
|
-
|
|
38
|
+
try {
|
|
39
|
+
// Detach the diagnostic filename before deleting it so concurrent Windows readers cannot delay reuse.
|
|
40
|
+
const detached = `${path}.${randomUUID()}.released`;
|
|
41
|
+
await rename(path, detached);
|
|
42
|
+
await unlink(detached);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
release();
|
|
46
|
+
}
|
|
143
47
|
};
|
|
144
48
|
}
|
|
145
|
-
/**
|
|
49
|
+
/** Probe the same lock that acquisition holds; a PID can be stale or reused by an unrelated process. */
|
|
146
50
|
export async function daemonLockExists(stateDirectory, filename = "manager.lock") {
|
|
51
|
+
const path = join(stateDirectory, `${filename}.sqlite`);
|
|
147
52
|
try {
|
|
148
|
-
|
|
149
|
-
// An earlier release can expose the exclusive file before it finishes writing the PID.
|
|
150
|
-
if (!Number.isSafeInteger(pid) || pid <= 1) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
try {
|
|
154
|
-
process.kill(pid, 0);
|
|
155
|
-
}
|
|
156
|
-
catch (cause) {
|
|
157
|
-
if (isNodeError(cause, "ESRCH")) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
if (!isNodeError(cause, "EPERM")) {
|
|
161
|
-
throw cause;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return true;
|
|
53
|
+
await access(path);
|
|
165
54
|
}
|
|
166
55
|
catch (cause) {
|
|
167
56
|
if (isNodeError(cause, "ENOENT")) {
|
|
168
57
|
return false;
|
|
169
58
|
}
|
|
170
|
-
// Released versions unlink their lock in place. On Windows the file cannot
|
|
171
|
-
// be opened until that deletion completes, so it still exists.
|
|
172
|
-
if (isNodeError(cause, "EPERM")) {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
59
|
throw cause;
|
|
176
60
|
}
|
|
61
|
+
const release = holdDaemonOwnership(path);
|
|
62
|
+
if (release === null) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
release();
|
|
66
|
+
return false;
|
|
177
67
|
}
|
|
178
68
|
//# sourceMappingURL=daemon-lock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-lock.js","sourceRoot":"","sources":["../../../src/storage/daemon/daemon-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"daemon-lock.js","sourceRoot":"","sources":["../../../src/storage/daemon/daemon-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAEH,qBAAqB,GACxB,MAAM,yDAAyD,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE9D,MAAM,WAAW,GAAG,CAAC,CAAC;AAGtB,gGAAgG;AAChG,SAAS,mBAAmB,CAAC,IAAY;IACrC,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACjC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,KAAK,YAAY,KAAK,IAAI,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YAChF,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,cAAqC,EACrC,QAAQ,GAAa,cAAc;IAEnC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC;IACtD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACD,MAAM,qBAAqB,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAClG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,EAAE,CAAC;QACV,MAAM,KAAK,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,IAAI,EAAE;QACd,IAAI,CAAC;YACD,sGAAsG;YACtG,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,WAAW,CAAC;YACpD,MAAM,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC7B,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;gBAAS,CAAC;YACP,OAAO,EAAE,CAAC;QACd,CAAC;IACL,CAAC,CAAC;AACN,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,cAAqC,EACrC,QAAQ,GAAa,cAAc;IAEnC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,QAAQ,SAAS,CAAC,CAAC;IACxD,IAAI,CAAC;QACD,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,KAAK,CAAC;IAChB,CAAC;IACD,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,EAAE,CAAC;IACV,OAAO,KAAK,CAAC;AACjB,CAAC"}
|