@dmsdc-ai/aigentry-telepty 0.1.96 → 0.1.98
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/cli.js +16 -8
- package/daemon.js +68 -25
- package/package.json +4 -4
- package/session-state.js +154 -51
- package/skills/telepty/SKILL.md +39 -118
- package/skills/telepty-allow/SKILL.md +78 -0
- package/skills/telepty-attach/SKILL.md +52 -0
- package/skills/telepty-broadcast/SKILL.md +63 -0
- package/skills/telepty-daemon/SKILL.md +94 -0
- package/skills/telepty-inject/SKILL.md +93 -0
- package/skills/telepty-list/SKILL.md +81 -0
- package/skills/telepty-listen/SKILL.md +86 -0
- package/skills/telepty-rename/SKILL.md +63 -0
- package/skills/telepty-session/SKILL.md +83 -0
- package/specs/codex-inject-spec.md +201 -0
- package/src/mailbox/config.js +4 -0
- package/src/mailbox/delivery.js +93 -32
- package/src/mailbox/index.js +11 -0
- package/src/mailbox/storage.js +84 -5
package/src/mailbox/storage.js
CHANGED
|
@@ -7,6 +7,7 @@ const path = require('path');
|
|
|
7
7
|
|
|
8
8
|
const LOCK_POLL_MS = 10;
|
|
9
9
|
const LOCK_TIMEOUT_MS = 500;
|
|
10
|
+
const DEFAULT_STALE_LOCK_AGE_MS = 60000; // 60s — lock hold time is ~5ms, so 60s is definitionally stale
|
|
10
11
|
|
|
11
12
|
function isProcessAlive(pid) {
|
|
12
13
|
try {
|
|
@@ -20,10 +21,15 @@ function isProcessAlive(pid) {
|
|
|
20
21
|
/**
|
|
21
22
|
* Acquire an advisory lock for a session directory.
|
|
22
23
|
* Returns a release function. Throws on timeout.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} sessionDir
|
|
26
|
+
* @param {Object} [options]
|
|
27
|
+
* @param {number} [options.staleLockAgeMs] — break locks older than this (default 60s)
|
|
23
28
|
*/
|
|
24
|
-
function acquireLock(sessionDir) {
|
|
29
|
+
function acquireLock(sessionDir, options = {}) {
|
|
25
30
|
const lockPath = path.join(sessionDir, '.lock');
|
|
26
31
|
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
32
|
+
const staleLockAgeMs = options.staleLockAgeMs || DEFAULT_STALE_LOCK_AGE_MS;
|
|
27
33
|
|
|
28
34
|
while (Date.now() < deadline) {
|
|
29
35
|
try {
|
|
@@ -36,12 +42,28 @@ function acquireLock(sessionDir) {
|
|
|
36
42
|
} catch (err) {
|
|
37
43
|
if (err.code !== 'EEXIST') throw err;
|
|
38
44
|
|
|
39
|
-
// Lock file exists — check
|
|
45
|
+
// Lock file exists — check age first, then PID
|
|
46
|
+
|
|
47
|
+
// Fix 2: Lock age threshold — if lock is older than staleLockAgeMs,
|
|
48
|
+
// break regardless of PID (handles PID recycling)
|
|
49
|
+
try {
|
|
50
|
+
const stat = fs.statSync(lockPath);
|
|
51
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
52
|
+
if (ageMs > staleLockAgeMs) {
|
|
53
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// stat failed — file may have been removed between EEXIST and stat
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fix 1: Invalid PID handling — treat NaN, 0, negative, empty as stale
|
|
40
62
|
try {
|
|
41
63
|
const content = fs.readFileSync(lockPath, 'utf8').trim();
|
|
42
64
|
const pid = Number(content);
|
|
43
|
-
if (pid
|
|
44
|
-
//
|
|
65
|
+
if (!Number.isFinite(pid) || pid <= 0 || !isProcessAlive(pid)) {
|
|
66
|
+
// Invalid PID (empty, NaN, 0, negative) OR dead PID → stale lock
|
|
45
67
|
try { fs.unlinkSync(lockPath); } catch {}
|
|
46
68
|
continue;
|
|
47
69
|
}
|
|
@@ -51,7 +73,7 @@ function acquireLock(sessionDir) {
|
|
|
51
73
|
continue;
|
|
52
74
|
}
|
|
53
75
|
|
|
54
|
-
// Lock is held by a live process — wait
|
|
76
|
+
// Lock is held by a live process with a recent lock — wait
|
|
55
77
|
const buffer = new SharedArrayBuffer(4);
|
|
56
78
|
const view = new Int32Array(buffer);
|
|
57
79
|
Atomics.wait(view, 0, 0, LOCK_POLL_MS);
|
|
@@ -61,6 +83,61 @@ function acquireLock(sessionDir) {
|
|
|
61
83
|
throw new Error(`Mailbox lock timeout for ${sessionDir}`);
|
|
62
84
|
}
|
|
63
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Break stale lock files across all session directories (startup sweep).
|
|
88
|
+
* Returns count of broken locks.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} root — mailbox root directory
|
|
91
|
+
* @param {Object} [options]
|
|
92
|
+
* @param {number} [options.staleLockAgeMs] — age threshold (default 60s)
|
|
93
|
+
*/
|
|
94
|
+
function breakStaleLocks(root, options = {}) {
|
|
95
|
+
const staleLockAgeMs = options.staleLockAgeMs || DEFAULT_STALE_LOCK_AGE_MS;
|
|
96
|
+
const dirs = listSessionDirs(root);
|
|
97
|
+
let broken = 0;
|
|
98
|
+
|
|
99
|
+
for (const { sessionId, dir } of dirs) {
|
|
100
|
+
const lockPath = path.join(dir, '.lock');
|
|
101
|
+
if (!fs.existsSync(lockPath)) continue;
|
|
102
|
+
|
|
103
|
+
let shouldBreak = false;
|
|
104
|
+
let reason = '';
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const stat = fs.statSync(lockPath);
|
|
108
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
109
|
+
|
|
110
|
+
if (ageMs > staleLockAgeMs) {
|
|
111
|
+
shouldBreak = true;
|
|
112
|
+
reason = `age ${Math.round(ageMs / 1000)}s > ${Math.round(staleLockAgeMs / 1000)}s threshold`;
|
|
113
|
+
} else {
|
|
114
|
+
// Check PID validity
|
|
115
|
+
const content = fs.readFileSync(lockPath, 'utf8').trim();
|
|
116
|
+
const pid = Number(content);
|
|
117
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
118
|
+
shouldBreak = true;
|
|
119
|
+
reason = `invalid PID: ${JSON.stringify(content)}`;
|
|
120
|
+
} else if (!isProcessAlive(pid)) {
|
|
121
|
+
shouldBreak = true;
|
|
122
|
+
reason = `dead PID ${pid}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Can't read/stat lock — treat as stale
|
|
127
|
+
shouldBreak = true;
|
|
128
|
+
reason = 'unreadable lock file';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (shouldBreak) {
|
|
132
|
+
try { fs.unlinkSync(lockPath); } catch {}
|
|
133
|
+
console.log(`[MAILBOX] Broke stale lock for ${sessionId}: ${reason}`);
|
|
134
|
+
broken++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return broken;
|
|
139
|
+
}
|
|
140
|
+
|
|
64
141
|
// --- JSONL read/write ---
|
|
65
142
|
|
|
66
143
|
function readJsonl(filePath) {
|
|
@@ -172,6 +249,7 @@ function compact(sessionDir, threshold) {
|
|
|
172
249
|
|
|
173
250
|
module.exports = {
|
|
174
251
|
acquireLock,
|
|
252
|
+
breakStaleLocks,
|
|
175
253
|
readJsonl,
|
|
176
254
|
appendJsonl,
|
|
177
255
|
writeJsonl,
|
|
@@ -182,4 +260,5 @@ module.exports = {
|
|
|
182
260
|
loadMessages,
|
|
183
261
|
countPending,
|
|
184
262
|
compact,
|
|
263
|
+
isProcessAlive,
|
|
185
264
|
};
|