@aiwayds/dsh-tui-pi 2.12.0 → 2.13.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/README.md +4 -4
- package/README.zh-CN.md +1 -1
- package/lib/history.js +2 -2
- package/lib/history.js.map +1 -1
- package/lib/index.js +17 -8
- package/lib/index.js.map +1 -1
- package/lib/live-widgets.d.ts +25 -9
- package/lib/live-widgets.js +81 -29
- package/lib/live-widgets.js.map +1 -1
- package/lib/log-repair.d.ts +10 -13
- package/lib/log-repair.js +11 -26
- package/lib/log-repair.js.map +1 -1
- package/lib/messages.d.ts +11 -1
- package/lib/messages.js +18 -4
- package/lib/messages.js.map +1 -1
- package/lib/retention.d.ts +10 -4
- package/lib/retention.js +15 -4
- package/lib/retention.js.map +1 -1
- package/lib/session-dir.d.ts +26 -0
- package/lib/session-dir.js +53 -0
- package/lib/session-dir.js.map +1 -0
- package/lib/session.d.ts +37 -44
- package/lib/session.js +167 -232
- package/lib/session.js.map +1 -1
- package/lib/sessions.d.ts +51 -4
- package/lib/sessions.js +45 -3
- package/lib/sessions.js.map +1 -1
- package/package.json +5 -5
- package/lib/writer-lock.d.ts +0 -70
- package/lib/writer-lock.js +0 -206
- package/lib/writer-lock.js.map +0 -1
package/lib/writer-lock.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single-writer guard — a vendored, zero-dependency per-session pid-file
|
|
3
|
-
* lock, byte-for-byte the same contract the feishu plugin's guard uses so
|
|
4
|
-
* both surfaces compete on one file.
|
|
5
|
-
*
|
|
6
|
-
* Why it exists: two dsh processes pointed at the same profile each keep
|
|
7
|
-
* their own agent registry; `agents.get` cannot see the other process's
|
|
8
|
-
* live agent. A cold resume there therefore forks the log: two agents
|
|
9
|
-
* append to one jsonl and the interleaved seq numbers make the loader
|
|
10
|
-
* reject the session ("corrupt session log"). The lock sits NEXT to the
|
|
11
|
-
* log (`<sessionDir>/writer.lock`), derived with the exact path encoding
|
|
12
|
-
* upstream's jsonl backend uses, so every process about to create or
|
|
13
|
-
* cold-resume competes on the same file.
|
|
14
|
-
*
|
|
15
|
-
* Claiming protocol (concurrency-critical):
|
|
16
|
-
* - The payload is written FULLY to `<lock>.tmp-<pid>-<uniq>` first, then
|
|
17
|
-
* claimed with `link(2)` — atomic at the kernel level, never writable
|
|
18
|
-
* half-way, so no observer can read an empty/partial lock file.
|
|
19
|
-
* - Two processes that both judge the same residue dead cannot both win:
|
|
20
|
-
* they race ONE link(); the loser sees EEXIST and re-evaluates against
|
|
21
|
-
* the WINNER's fresh live pid, then refuses. Stealing (renaming the dead
|
|
22
|
-
* residue aside) is merely garbage collection of readable history — the
|
|
23
|
-
* exclusive right is granted by link(), not by who cleared what.
|
|
24
|
-
* - PID reuse can misreport a dead lock as alive; removing such a residual
|
|
25
|
-
* stays an explicit manual act (delete the file), never automatic.
|
|
26
|
-
*/
|
|
27
|
-
import { link, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
28
|
-
import { randomBytes } from 'node:crypto';
|
|
29
|
-
import { join, resolve } from 'node:path';
|
|
30
|
-
/** Thrown when a cold arm refused to open a second writer for a session. */
|
|
31
|
-
export class WriterLockedError extends Error {
|
|
32
|
-
holder;
|
|
33
|
-
constructor(holder) {
|
|
34
|
-
super(`session is locked by a live process (pid ${holder.pid}`
|
|
35
|
-
+ `${holder.createdAt !== '' ? `, since ${holder.createdAt}` : ''})`
|
|
36
|
-
+ ' — refusing to fork the log');
|
|
37
|
-
this.name = 'WriterLockedError';
|
|
38
|
-
this.holder = holder;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const LOCK_HOLDER = 'tui';
|
|
42
|
-
/** Contention budget across residue-clearing rounds before giving up loudly. */
|
|
43
|
-
const MAX_CLAIM_ROUNDS = 4;
|
|
44
|
-
/**
|
|
45
|
-
* Dirs where THIS process established (created or stole) the lock. Only an
|
|
46
|
-
* establishing acquisition may release the file: a cooperative re-acquire
|
|
47
|
-
* (same pid already recorded) inherits it without removal rights.
|
|
48
|
-
*/
|
|
49
|
-
const established = new Set();
|
|
50
|
-
export function writerLockPath(dir) {
|
|
51
|
-
return join(dir, 'writer.lock');
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Project-directory name under which the jsonl backend groups a cwd's
|
|
55
|
-
* sessions — byte-identical port of upstream `projectKey()` in
|
|
56
|
-
* @deepseek-ai/dsh-session-persistence-jsonl (also mirrored by the feishu
|
|
57
|
-
* plugin's guard). The derived session dir additionally relies on upstream's
|
|
58
|
-
* `encodeSegment(sessionId)` being the IDENTITY for dsh ids — true while ids
|
|
59
|
-
* are UUIDs (safe charset ⊇ UUID charset). If upstream ever introduces
|
|
60
|
-
* non-UUID ids, port encodeSegment too or locks land beside decoy paths;
|
|
61
|
-
* update only in lockstep with upstream either way.
|
|
62
|
-
*/
|
|
63
|
-
export function projectKeyFor(cwd) {
|
|
64
|
-
if (cwd.length === 0)
|
|
65
|
-
throw new Error('cannot encode an empty project path');
|
|
66
|
-
let readable = '';
|
|
67
|
-
let separatorRun = false;
|
|
68
|
-
for (let i = 0; i < cwd.length; i++) {
|
|
69
|
-
const code = cwd.charCodeAt(i);
|
|
70
|
-
const ch = String.fromCharCode(code);
|
|
71
|
-
if (ch === '/' || ch === '\\' || ch === ':') {
|
|
72
|
-
if (!separatorRun)
|
|
73
|
-
readable += '-';
|
|
74
|
-
separatorRun = true;
|
|
75
|
-
}
|
|
76
|
-
else if (ch !== '~' && /^[A-Za-z0-9._-]$/.test(ch)) {
|
|
77
|
-
readable += ch;
|
|
78
|
-
separatorRun = false;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
readable += `~${code.toString(16).toUpperCase().padStart(4, '0')}`;
|
|
82
|
-
separatorRun = false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return `--${(readable.replace(/^-+/, '') || 'root').slice(0, 251)}--`;
|
|
86
|
-
}
|
|
87
|
-
function processAlive(pid) {
|
|
88
|
-
try {
|
|
89
|
-
process.kill(pid, 0);
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
// EPERM means the process exists but belongs to another user — alive.
|
|
94
|
-
return error.code === 'EPERM';
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async function readHolder(path) {
|
|
98
|
-
let raw;
|
|
99
|
-
try {
|
|
100
|
-
raw = await readFile(path, 'utf8');
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
const parsed = JSON.parse(raw);
|
|
107
|
-
if (!Number.isInteger(parsed.pid) || parsed.pid <= 0)
|
|
108
|
-
return undefined;
|
|
109
|
-
return {
|
|
110
|
-
pid: parsed.pid,
|
|
111
|
-
createdAt: typeof parsed.createdAt === 'string' ? parsed.createdAt : '',
|
|
112
|
-
holder: typeof parsed.holder === 'string' ? parsed.holder : LOCK_HOLDER,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Fully write the payload to a private temp file, then CLAIM the lock name
|
|
121
|
-
* with link() — atomic: exactly one contender wins; everyone else gets
|
|
122
|
-
* EEXIST and re-reads whoever actually holds the name. Returns true iff WE
|
|
123
|
-
* claimed it (and thus own its release).
|
|
124
|
-
*/
|
|
125
|
-
async function claimFresh(path) {
|
|
126
|
-
const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`;
|
|
127
|
-
await writeFile(tmp, JSON.stringify({
|
|
128
|
-
pid: process.pid,
|
|
129
|
-
createdAt: new Date().toISOString(),
|
|
130
|
-
holder: LOCK_HOLDER,
|
|
131
|
-
}));
|
|
132
|
-
try {
|
|
133
|
-
try {
|
|
134
|
-
await link(tmp, path);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
if (error.code === 'EEXIST')
|
|
139
|
-
return false;
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
await unlink(tmp).catch(() => { });
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function clearResidue(path) {
|
|
148
|
-
try {
|
|
149
|
-
await rename(path, `${path}.stale-${Date.now()}-${randomBytes(3).toString('hex')}`);
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
// Another contender cleared (or replaced) it already — harmless; the
|
|
153
|
-
// caller simply loops and re-reads the CURRENT state of the name.
|
|
154
|
-
if (error.code !== 'ENOENT')
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Acquire the single-writer lock for `dir`. Fresh creation or a cleared
|
|
160
|
-
* residue establishes ownership (releasable later); a same-pid token is a
|
|
161
|
-
* cooperative re-acquire — success WITHOUT rewriting the existing file and
|
|
162
|
-
* without release rights. A lock held by another LIVE process refuses with
|
|
163
|
-
* its holder info.
|
|
164
|
-
*/
|
|
165
|
-
export async function acquireWriterLock(dir) {
|
|
166
|
-
await mkdir(dir, { recursive: true });
|
|
167
|
-
const path = writerLockPath(dir);
|
|
168
|
-
for (let round = 0; round < MAX_CLAIM_ROUNDS; round++) {
|
|
169
|
-
if (await claimFresh(path)) {
|
|
170
|
-
established.add(resolve(dir));
|
|
171
|
-
return { ok: true };
|
|
172
|
-
}
|
|
173
|
-
const holder = await readHolder(path);
|
|
174
|
-
if (holder !== undefined && holder.pid === process.pid) {
|
|
175
|
-
// Cooperative re-acquire: inherit, do not rewrite, never releasable.
|
|
176
|
-
return { ok: true };
|
|
177
|
-
}
|
|
178
|
-
if (holder !== undefined && processAlive(holder.pid)) {
|
|
179
|
-
return { ok: false, holder };
|
|
180
|
-
}
|
|
181
|
-
// Dead-or-corrupt-or-raced-away holder: collect the residue and re-try
|
|
182
|
-
// the atomic claim; any live winner introduced meanwhile is re-read and
|
|
183
|
-
// respected on the next round.
|
|
184
|
-
await clearResidue(path);
|
|
185
|
-
}
|
|
186
|
-
fail(`writer.lock contention did not settle after ${MAX_CLAIM_ROUNDS} rounds: ${path}`);
|
|
187
|
-
}
|
|
188
|
-
function fail(message) {
|
|
189
|
-
throw new Error(message);
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Release the lock on `dir`, but ONLY when this process established it.
|
|
193
|
-
* A cooperative/inherited token — or a dir we never touched — is untouched.
|
|
194
|
-
*/
|
|
195
|
-
export async function releaseOwnedWriterLock(dir) {
|
|
196
|
-
const key = resolve(dir);
|
|
197
|
-
if (!established.delete(key))
|
|
198
|
-
return;
|
|
199
|
-
try {
|
|
200
|
-
await unlink(writerLockPath(dir));
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
// Already gone (or raced away) — nothing left to clean.
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
//# sourceMappingURL=writer-lock.js.map
|
package/lib/writer-lock.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"writer-lock.js","sourceRoot":"","sources":["../src/writer-lock.ts"],"names":[],"mappings":"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"}
|