@berthdev/berth 0.3.6 → 0.4.3
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 +7 -1
- package/dist/src/agents/capabilities.d.ts +13 -0
- package/dist/src/agents/capabilities.js +52 -0
- package/dist/src/agents/capabilities.js.map +1 -0
- package/dist/src/agents/codex-binding.d.ts +10 -0
- package/dist/src/agents/codex-binding.js +105 -0
- package/dist/src/agents/codex-binding.js.map +1 -0
- package/dist/src/agents/commands.d.ts +4 -2
- package/dist/src/agents/commands.js +41 -9
- package/dist/src/agents/commands.js.map +1 -1
- package/dist/src/agents/copilot-state.d.ts +3 -0
- package/dist/src/agents/copilot-state.js +94 -0
- package/dist/src/agents/copilot-state.js.map +1 -0
- package/dist/src/agents/gemini-state.d.ts +3 -0
- package/dist/src/agents/gemini-state.js +41 -0
- package/dist/src/agents/gemini-state.js.map +1 -0
- package/dist/src/agents/hook-files.d.ts +1 -3
- package/dist/src/agents/hook-files.js +69 -53
- package/dist/src/agents/hook-files.js.map +1 -1
- package/dist/src/agents/hook-health.d.ts +4 -0
- package/dist/src/agents/hook-health.js +95 -0
- package/dist/src/agents/hook-health.js.map +1 -0
- package/dist/src/agents/hooks.d.ts +4 -1
- package/dist/src/agents/hooks.js +153 -21
- package/dist/src/agents/hooks.js.map +1 -1
- package/dist/src/agents/jsonl-titles.d.ts +8 -0
- package/dist/src/agents/jsonl-titles.js +124 -0
- package/dist/src/agents/jsonl-titles.js.map +1 -0
- package/dist/src/agents/late-binding.d.ts +2 -0
- package/dist/src/agents/late-binding.js +25 -0
- package/dist/src/agents/late-binding.js.map +1 -0
- package/dist/src/agents/profile.d.ts +14 -0
- package/dist/src/agents/profile.js +75 -0
- package/dist/src/agents/profile.js.map +1 -0
- package/dist/src/agents/registry.d.ts +12 -3
- package/dist/src/agents/registry.js +52 -19
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/roots.d.ts +2 -0
- package/dist/src/agents/roots.js +21 -0
- package/dist/src/agents/roots.js.map +1 -0
- package/dist/src/agents/titles.d.ts +14 -0
- package/dist/src/agents/titles.js +82 -0
- package/dist/src/agents/titles.js.map +1 -0
- package/dist/src/agents/transcript-cursor.d.ts +9 -0
- package/dist/src/agents/transcript-cursor.js +86 -0
- package/dist/src/agents/transcript-cursor.js.map +1 -0
- package/dist/src/cli/main.js +40 -7
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/config/index.d.ts +6 -0
- package/dist/src/config/index.js +37 -6
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/write.d.ts +2 -0
- package/dist/src/config/write.js +41 -0
- package/dist/src/config/write.js.map +1 -0
- package/dist/src/context.js +1 -1
- package/dist/src/context.js.map +1 -1
- package/dist/src/index.d.ts +11 -5
- package/dist/src/index.js +10 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/iterm2/install.d.ts +2 -0
- package/dist/src/iterm2/install.js +37 -12
- package/dist/src/iterm2/install.js.map +1 -1
- package/dist/src/present/attach.js +17 -5
- package/dist/src/present/attach.js.map +1 -1
- package/dist/src/present/state.d.ts +3 -0
- package/dist/src/present/state.js +12 -8
- package/dist/src/present/state.js.map +1 -1
- package/dist/src/process-arguments.d.ts +2 -0
- package/dist/src/process-arguments.js +59 -0
- package/dist/src/process-arguments.js.map +1 -0
- package/dist/src/process-directory.d.ts +2 -0
- package/dist/src/process-directory.js +37 -0
- package/dist/src/process-directory.js.map +1 -0
- package/dist/src/process-owner.d.ts +7 -0
- package/dist/src/process-owner.js +69 -0
- package/dist/src/process-owner.js.map +1 -0
- package/dist/src/registry/directory.d.ts +2 -0
- package/dist/src/registry/directory.js +19 -0
- package/dist/src/registry/directory.js.map +1 -0
- package/dist/src/registry/discover.d.ts +15 -5
- package/dist/src/registry/discover.js +142 -38
- package/dist/src/registry/discover.js.map +1 -1
- package/dist/src/registry/doctor.d.ts +5 -1
- package/dist/src/registry/doctor.js +53 -12
- package/dist/src/registry/doctor.js.map +1 -1
- package/dist/src/registry/hosts.d.ts +28 -0
- package/dist/src/registry/hosts.js +73 -0
- package/dist/src/registry/hosts.js.map +1 -0
- package/dist/src/registry/launch.d.ts +3 -0
- package/dist/src/registry/launch.js +29 -0
- package/dist/src/registry/launch.js.map +1 -0
- package/dist/src/registry/native-state.d.ts +19 -0
- package/dist/src/registry/native-state.js +135 -0
- package/dist/src/registry/native-state.js.map +1 -0
- package/dist/src/registry/sessions.d.ts +21 -3
- package/dist/src/registry/sessions.js +179 -38
- package/dist/src/registry/sessions.js.map +1 -1
- package/dist/src/restore/ensure.d.ts +12 -2
- package/dist/src/restore/ensure.js +171 -37
- package/dist/src/restore/ensure.js.map +1 -1
- package/dist/src/restore/lock.d.ts +3 -5
- package/dist/src/restore/lock.js +82 -52
- package/dist/src/restore/lock.js.map +1 -1
- package/dist/src/restore/status.d.ts +9 -0
- package/dist/src/restore/status.js +16 -0
- package/dist/src/restore/status.js.map +1 -0
- package/dist/src/snapshot/identity.d.ts +7 -0
- package/dist/src/snapshot/identity.js +20 -0
- package/dist/src/snapshot/identity.js.map +1 -0
- package/dist/src/snapshot/lifecycle.d.ts +9 -0
- package/dist/src/snapshot/lifecycle.js +37 -0
- package/dist/src/snapshot/lifecycle.js.map +1 -0
- package/dist/src/snapshot/store.d.ts +11 -1
- package/dist/src/snapshot/store.js +28 -9
- package/dist/src/snapshot/store.js.map +1 -1
- package/dist/src/snapshot/take.d.ts +2 -1
- package/dist/src/snapshot/take.js +29 -25
- package/dist/src/snapshot/take.js.map +1 -1
- package/dist/src/tmux/client.d.ts +6 -2
- package/dist/src/tmux/client.js +49 -25
- package/dist/src/tmux/client.js.map +1 -1
- package/dist/src/tmux/connections.d.ts +24 -0
- package/dist/src/tmux/connections.js +86 -0
- package/dist/src/tmux/connections.js.map +1 -0
- package/dist/src/tmux/fields.d.ts +7 -0
- package/dist/src/tmux/fields.js +23 -0
- package/dist/src/tmux/fields.js.map +1 -0
- package/dist/src/tmux/install.js +31 -18
- package/dist/src/tmux/install.js.map +1 -1
- package/dist/src/tools.d.ts +3 -1
- package/dist/src/tools.js +23 -11
- package/dist/src/tools.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/agents/name-key.d.ts +0 -1
- package/dist/src/agents/name-key.js +0 -66
- package/dist/src/agents/name-key.js.map +0 -1
|
@@ -1,57 +1,191 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { ARGUMENTS_UNAVAILABLE } from '../registry/discover.js';
|
|
2
|
+
import { applyLaunchEnvironment, captureProfile, profileAtRoot, readProfile } from '../agents/profile.js';
|
|
3
|
+
import { recordIdentity, nameCollision, COLLISION_HINT } from '../snapshot/identity.js';
|
|
4
|
+
import { withPlaceholder } from '../registry/launch.js';
|
|
5
|
+
import { usableDirectory } from '../registry/directory.js';
|
|
6
|
+
import { wasClosed, serverOwnerAlive } from '../snapshot/lifecycle.js';
|
|
7
|
+
import { existsSync, statSync } from 'node:fs';
|
|
8
|
+
import { executableOnPath } from '../tools.js';
|
|
9
|
+
import { keptArgs, recoveryOptionsRequired, launchCommand, resumeCommand, requireAdapter } from '../agents/commands.js';
|
|
10
|
+
import { shellQuote, shellSplit } from '../shell.js';
|
|
11
|
+
import { recoveryStatus } from './status.js';
|
|
12
|
+
import { BerthError } from '../errors.js';
|
|
13
|
+
import { takeSnapshot } from '../snapshot/take.js';
|
|
5
14
|
import { withLock } from './lock.js';
|
|
6
|
-
function onPath(binary, env = process.env) {
|
|
7
|
-
if (!binary)
|
|
8
|
-
return true;
|
|
9
|
-
if (binary.includes('/'))
|
|
10
|
-
return existsSync(binary);
|
|
11
|
-
return (env.PATH ?? '').split(delimiter).some((dir) => dir && existsSync(join(dir, binary)));
|
|
12
|
-
}
|
|
13
15
|
function selected(s, options) {
|
|
16
|
+
if (options.ownerWorkspace && s.ownerWorkspace !== options.ownerWorkspace)
|
|
17
|
+
return false;
|
|
14
18
|
if (options.session && s.name !== options.session)
|
|
15
19
|
return false;
|
|
16
20
|
if (options.origin && options.origin !== 'all' && s.origin !== options.origin)
|
|
17
21
|
return false;
|
|
18
22
|
return true;
|
|
19
23
|
}
|
|
20
|
-
function
|
|
21
|
-
if (
|
|
24
|
+
function prepare(ctx, s) {
|
|
25
|
+
if (s.bindingIssue)
|
|
26
|
+
return { name: s.name, outcome: 'skipped-binding-conflict', detail: s.bindingIssue };
|
|
27
|
+
if (!usableDirectory(s.cwd))
|
|
22
28
|
return { name: s.name, outcome: 'skipped-missing-cwd', detail: s.cwd };
|
|
23
29
|
const adapter = requireAdapter(s.agent);
|
|
30
|
+
if (s.agent !== 'shell' && !s.conversation)
|
|
31
|
+
return { name: s.name, outcome: 'skipped-unbound', detail: 'Use the agent native conversation picker, or explicitly start a new session.' };
|
|
32
|
+
if (s.agent !== 'shell' && !readProfile(s.agent, s.profile))
|
|
33
|
+
return { name: s.name, outcome: 'skipped-unknown-profile', detail: 'Choose the agent profile for this saved conversation (Choose Recovery Profile, or berth recovery-profile <session> --root <directory>).' };
|
|
34
|
+
if (recoveryOptionsRequired(s.agent, s.argv, s.argvVerified))
|
|
35
|
+
return { name: s.name, outcome: 'skipped-unverified-arguments', detail: 'Saved launch options need review. Use Review Recovery Options or berth recovery-options <session> -- <options>.' };
|
|
24
36
|
const input = { adapter, name: s.name, cwd: s.cwd, overrides: ctx.config.agents };
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
:
|
|
28
|
-
let command = resumed ?? launchCommand({ ...input, extraArgs: [] });
|
|
29
|
-
let outcome = resumed ? 'resumed' : 'launched';
|
|
37
|
+
const command = s.agent === 'shell' ? launchCommand(input) : resumeCommand({ ...input, conversation: s.conversation, originalArgv: s.argv });
|
|
38
|
+
if (command === undefined)
|
|
39
|
+
return { name: s.name, outcome: 'skipped-unsupported-resume', detail: 'This agent has no exact-ID resume command.' };
|
|
30
40
|
const [binary] = command ? shellSplit(command) : [''];
|
|
31
|
-
if (!
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
ctx
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
if (binary && !executableOnPath(binary, ctx.env, s.cwd))
|
|
42
|
+
return { name: s.name, outcome: 'skipped-missing-binary', detail: `Install ${binary}, then retry.` };
|
|
43
|
+
return { command };
|
|
44
|
+
}
|
|
45
|
+
function rebuild(ctx, s) {
|
|
46
|
+
const prepared = prepare(ctx, s);
|
|
47
|
+
if (!('command' in prepared))
|
|
48
|
+
return prepared;
|
|
49
|
+
const { command } = prepared;
|
|
50
|
+
const adapter = requireAdapter(s.agent);
|
|
51
|
+
// Register expected identity before the agent can emit its start hook.
|
|
52
|
+
return withPlaceholder(ctx, s.name, s.cwd, () => {
|
|
53
|
+
const profile = readProfile(s.agent, s.profile);
|
|
54
|
+
if (profile)
|
|
55
|
+
applyLaunchEnvironment(ctx, s.name, s.agent, profile);
|
|
56
|
+
ctx.tmux.setMeta(s.name, {
|
|
57
|
+
profile: profile ? JSON.stringify(profile) : undefined,
|
|
58
|
+
agent: s.agent, cwd: s.cwd, argv: s.argv, argvVerified: s.argvVerified ? '1' : undefined, restoreSource: recordIdentity(s),
|
|
59
|
+
conversation: s.conversation || undefined, origin: s.origin, ownerWorkspace: s.ownerWorkspace,
|
|
60
|
+
...(s.agent === 'shell' ? {} : {
|
|
61
|
+
recovery: adapter.events.start.length ? 'requested' : 'unconfirmed',
|
|
62
|
+
recoveryAt: String(Date.now()),
|
|
63
|
+
recoveryDetail: adapter.events.start.length ? 'Waiting for the expected conversation.' : 'This agent cannot acknowledge recovery. Inspect the terminal.',
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
ctx.tmux.setTitle(s.name);
|
|
67
|
+
if (s.agent !== 'shell')
|
|
68
|
+
ctx.tmux.run(['set-option', '-w', '-t', `=${s.name}:`, 'remain-on-exit', 'on']);
|
|
69
|
+
ctx.tmux.run(['respawn-pane', '-k', '-t', `=${s.name}:`, ...(command ? [command] : [])]);
|
|
70
|
+
return { name: s.name, outcome: s.agent === 'shell' ? 'launched' : 'resume-requested' };
|
|
42
71
|
});
|
|
43
|
-
ctx.tmux.setTitle(s.name);
|
|
44
|
-
return { name: s.name, outcome, detail: outcome === 'shell' ? `${binary} not found` : undefined };
|
|
45
72
|
}
|
|
46
|
-
/**
|
|
47
|
-
|
|
73
|
+
/** A failed attempt never turns an owned placeholder into a successful recovery. */
|
|
74
|
+
function attempt(ctx, s) {
|
|
75
|
+
try {
|
|
76
|
+
if (ctx.tmux.hasSession(s.name)) {
|
|
77
|
+
const live = ctx.tmux.listSessions().find((session) => session.name === s.name);
|
|
78
|
+
if (live)
|
|
79
|
+
return { name: s.name, outcome: nameCollision(ctx, live, [s]) ? 'skipped-name-conflict' : 'skipped-live' };
|
|
80
|
+
}
|
|
81
|
+
return rebuild(ctx, s);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return { name: s.name, outcome: 'failed', detail: error.message };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function restoreSelected(ctx, snapshot, options) {
|
|
48
88
|
if (!snapshot)
|
|
49
89
|
return [];
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
90
|
+
const live = new Set(ctx.tmux.listSessions().map((session) => session.name));
|
|
91
|
+
const sessions = snapshot.sessions.filter((s) => selected(s, options) && !wasClosed(ctx, s)
|
|
92
|
+
&& (live.has(s.name) || s.recovery === 'failed' || !serverOwnerAlive(ctx, s.serverStart)));
|
|
93
|
+
const results = sessions.map((s) => attempt(ctx, s));
|
|
94
|
+
const pending = sessions.flatMap((s, index) => results[index].outcome === 'failed'
|
|
95
|
+
? [{ ...s, sessionId: undefined, recovery: 'failed', recoveryDetail: results[index].detail }] : []);
|
|
96
|
+
if (pending.length || results.some((r) => r.outcome === 'resume-requested' || r.outcome === 'launched')) {
|
|
97
|
+
takeSnapshot(ctx, new Date(), [], pending);
|
|
98
|
+
}
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
/** Explicit restore uses its chosen snapshot; ensure reads latest inside the lock. */
|
|
102
|
+
export function restoreFrom(ctx, snapshot, options = {}) {
|
|
103
|
+
return withLock(ctx.config.stateDir, () => restoreSelected(ctx, snapshot, options), options.lockTimeoutMs);
|
|
53
104
|
}
|
|
54
105
|
export function ensure(ctx, options = {}) {
|
|
55
|
-
return
|
|
106
|
+
return withLock(ctx.config.stateDir, () => restoreSelected(ctx, ctx.store.newest(), options), options.lockTimeoutMs);
|
|
107
|
+
}
|
|
108
|
+
/** Replace only an explicitly failed/unconfirmed attempt, keeping its exact target. */
|
|
109
|
+
export function retryRecovery(ctx, name) {
|
|
110
|
+
return withLock(ctx.config.stateDir, () => {
|
|
111
|
+
const live = ctx.tmux.listSessions().find((session) => session.name === name);
|
|
112
|
+
if (live && nameCollision(ctx, live, ctx.store.newest()?.sessions ?? [])) {
|
|
113
|
+
return { name, outcome: 'skipped-name-conflict', detail: COLLISION_HINT };
|
|
114
|
+
}
|
|
115
|
+
if (live) {
|
|
116
|
+
const status = recoveryStatus(ctx.tmux, name, live.meta).recovery;
|
|
117
|
+
if (status !== 'failed' && status !== 'unconfirmed')
|
|
118
|
+
throw new BerthError('This session has no failed or unconfirmed recovery to retry.');
|
|
119
|
+
takeSnapshot(ctx);
|
|
120
|
+
}
|
|
121
|
+
const saved = ctx.store.newest()?.sessions.find((session) => session.name === name);
|
|
122
|
+
if (!saved)
|
|
123
|
+
throw new BerthError(`No recovery record for ${name}. Refresh the list.`);
|
|
124
|
+
const prepared = prepare(ctx, saved);
|
|
125
|
+
if (!('command' in prepared))
|
|
126
|
+
return prepared;
|
|
127
|
+
if (live)
|
|
128
|
+
ctx.tmux.killSession(name);
|
|
129
|
+
const result = attempt(ctx, saved);
|
|
130
|
+
takeSnapshot(ctx, new Date(), [], result.outcome === 'failed' ? [{ ...saved, sessionId: undefined, recovery: 'failed', recoveryDetail: result.detail }] : []);
|
|
131
|
+
return result;
|
|
132
|
+
}, 3000);
|
|
133
|
+
}
|
|
134
|
+
/** Relocate one stopped recovery record without changing its conversation identity. */
|
|
135
|
+
export function relocateRecovery(ctx, name, cwd) {
|
|
136
|
+
if (!existsSync(cwd) || !statSync(cwd).isDirectory())
|
|
137
|
+
throw new BerthError('Choose an existing folder.');
|
|
138
|
+
withLock(ctx.config.stateDir, () => {
|
|
139
|
+
if (ctx.tmux.hasSession(name))
|
|
140
|
+
throw new BerthError('End the live session before changing its recovery folder.');
|
|
141
|
+
const saved = ctx.store.newest();
|
|
142
|
+
if (!saved?.sessions.some((session) => session.name === name))
|
|
143
|
+
throw new BerthError(`No recovery record for ${name}.`);
|
|
144
|
+
const sessions = saved.sessions.map((session) => session.name === name ? { ...session, cwd } : session);
|
|
145
|
+
const scrollback = retainedScrollback(ctx, saved, sessions);
|
|
146
|
+
ctx.store.write(sessions, scrollback);
|
|
147
|
+
}, 3000);
|
|
148
|
+
}
|
|
149
|
+
/** Explicitly choose a stopped conversation's profile; never infer it from the host. */
|
|
150
|
+
export function setRecoveryProfile(ctx, name, root) {
|
|
151
|
+
return withLock(ctx.config.stateDir, () => {
|
|
152
|
+
if (ctx.tmux.hasSession(name))
|
|
153
|
+
throw new BerthError('Profile changes require a stopped session. A live agent reports its profile through its start hook.');
|
|
154
|
+
const saved = ctx.store.newest();
|
|
155
|
+
const record = saved?.sessions.find((session) => session.name === name);
|
|
156
|
+
if (!saved || !record || record.agent === 'shell')
|
|
157
|
+
throw new BerthError('No saved agent conversation for this session.');
|
|
158
|
+
if (root && !usableDirectory(root))
|
|
159
|
+
throw new BerthError('Choose an existing accessible agent profile folder.');
|
|
160
|
+
const profile = root ? profileAtRoot(record.agent, ctx.env, root) : captureProfile(record.agent, ctx.env);
|
|
161
|
+
const sessions = saved.sessions.map((session) => session.name === name ? { ...session, profile } : session);
|
|
162
|
+
const scrollback = retainedScrollback(ctx, saved, sessions);
|
|
163
|
+
ctx.store.write(sessions, scrollback);
|
|
164
|
+
return profile;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** Explicitly replace only the options an adapter can preserve on resume. */
|
|
168
|
+
export function setRecoveryOptions(ctx, name, options) {
|
|
169
|
+
withLock(ctx.config.stateDir, () => {
|
|
170
|
+
if (ctx.tmux.hasSession(name))
|
|
171
|
+
throw new BerthError('Recovery options require a stopped session.');
|
|
172
|
+
const saved = ctx.store.newest();
|
|
173
|
+
const record = saved?.sessions.find((session) => session.name === name);
|
|
174
|
+
if (!saved || !record || record.agent === 'shell')
|
|
175
|
+
throw new BerthError('No saved agent conversation for this session.');
|
|
176
|
+
const adapter = requireAdapter(record.agent);
|
|
177
|
+
const argv = [adapter.binary, ...options].map(shellQuote).join(' ');
|
|
178
|
+
if (JSON.stringify(keptArgs(adapter, argv)) !== JSON.stringify(options))
|
|
179
|
+
throw new BerthError('Use only supported resume options, without a prompt or -- terminator.');
|
|
180
|
+
const sessions = saved.sessions.map((session) => session.name === name ? { ...session, argv, argvVerified: true, ...(session.bindingIssue === ARGUMENTS_UNAVAILABLE ? { bindingIssue: undefined } : {}) } : session);
|
|
181
|
+
const scrollback = retainedScrollback(ctx, saved, sessions);
|
|
182
|
+
ctx.store.write(sessions, scrollback);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function retainedScrollback(ctx, saved, sessions) {
|
|
186
|
+
return Object.fromEntries(sessions.flatMap((session) => {
|
|
187
|
+
const text = ctx.store.optionalScrollback(saved.dir, session.name);
|
|
188
|
+
return text === undefined ? [] : [[session.name, text]];
|
|
189
|
+
}));
|
|
56
190
|
}
|
|
57
191
|
//# sourceMappingURL=ensure.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensure.js","sourceRoot":"","sources":["../../../src/restore/ensure.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ensure.js","sourceRoot":"","sources":["../../../src/restore/ensure.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Directory lock (implementation §2.4): created atomically, holds the owner
|
|
3
|
-
* PID. An abandoned lock is taken over by renaming it away first, so two
|
|
4
|
-
* waiters cannot both remove it and both proceed. Others wait up to `timeoutMs`.
|
|
5
|
-
*/
|
|
1
|
+
/** Synchronous session operations permit nested calls on their owning stack. */
|
|
6
2
|
export declare function withLock<T>(stateDir: string, fn: () => T, timeoutMs?: number): T;
|
|
3
|
+
/** Awaited host transactions retain ownership until completion without blocking the event loop. */
|
|
4
|
+
export declare function withAsyncLock<T>(stateDir: string, fn: () => Promise<T>, timeoutMs?: number): Promise<T>;
|
package/dist/src/restore/lock.js
CHANGED
|
@@ -1,74 +1,104 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { processLifetime, processOwnerAlive } from '../process-owner.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
3
5
|
import { BerthError } from '../errors.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function
|
|
6
|
+
const held = new Set();
|
|
7
|
+
function sleep() { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20); }
|
|
8
|
+
function readTickets(dir) {
|
|
9
|
+
return readdirSync(dir).filter((name) => name.endsWith('.json')).flatMap((name) => {
|
|
10
|
+
try {
|
|
11
|
+
const value = JSON.parse(readFileSync(join(dir, name), 'utf8'));
|
|
12
|
+
return value.id === name.slice(0, -5) && Number.isSafeInteger(value.number) && value.number >= 0 && processOwnerAlive(value.pid, value.lifetime, value.createdAt ?? statSync(join(dir, name)).mtimeMs) ? [value] : [];
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.code === 'ENOENT')
|
|
16
|
+
return [];
|
|
17
|
+
throw error; // A corrupt active contender must not be silently bypassed.
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function legacyOwnerAlive(stateDir) {
|
|
7
22
|
try {
|
|
8
|
-
|
|
9
|
-
return
|
|
23
|
+
const file = join(stateDir, 'ensure.lock', 'pid');
|
|
24
|
+
return processOwnerAlive(Number(readFileSync(file, 'utf8')), undefined, statSync(file).mtimeMs);
|
|
10
25
|
}
|
|
11
26
|
catch (error) {
|
|
12
|
-
|
|
27
|
+
if (error.code === 'ENOENT')
|
|
28
|
+
return false;
|
|
29
|
+
throw error;
|
|
13
30
|
}
|
|
14
31
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
/** Immutable ticket identities avoid stale-lock replacement races. */
|
|
33
|
+
function contender(stateDir) {
|
|
34
|
+
const dir = join(resolve(stateDir), 'ensure-tickets');
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
const id = randomUUID();
|
|
37
|
+
const file = join(dir, `${id}.json`);
|
|
38
|
+
const temporary = `${file}.tmp`;
|
|
39
|
+
const lifetime = processLifetime(process.pid).key;
|
|
40
|
+
const createdAt = Date.now();
|
|
41
|
+
const release = () => {
|
|
42
|
+
rmSync(file, { force: true });
|
|
43
|
+
rmSync(temporary, { force: true });
|
|
44
|
+
};
|
|
45
|
+
const publish = (number) => {
|
|
46
|
+
writeFileSync(temporary, JSON.stringify({ id, pid: process.pid, number, lifetime, createdAt }), { mode: 0o600 });
|
|
47
|
+
renameSync(temporary, file);
|
|
48
|
+
};
|
|
20
49
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
publish(0);
|
|
51
|
+
const number = Math.max(0, ...readTickets(dir).map((ticket) => ticket.number)) + 1;
|
|
52
|
+
publish(number);
|
|
53
|
+
return {
|
|
54
|
+
blocked: () => legacyOwnerAlive(stateDir) || readTickets(dir).some((ticket) => ticket.id !== id && (ticket.number === 0 || ticket.number < number || (ticket.number === number && ticket.id < id))),
|
|
55
|
+
release,
|
|
56
|
+
};
|
|
23
57
|
}
|
|
24
|
-
catch {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return false; // released meanwhile
|
|
30
|
-
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
release();
|
|
60
|
+
throw error;
|
|
31
61
|
}
|
|
32
62
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Directory lock (implementation §2.4): created atomically, holds the owner
|
|
35
|
-
* PID. An abandoned lock is taken over by renaming it away first, so two
|
|
36
|
-
* waiters cannot both remove it and both proceed. Others wait up to `timeoutMs`.
|
|
37
|
-
*/
|
|
63
|
+
/** Synchronous session operations permit nested calls on their owning stack. */
|
|
38
64
|
export function withLock(stateDir, fn, timeoutMs = 30_000) {
|
|
39
|
-
|
|
40
|
-
|
|
65
|
+
const key = resolve(stateDir);
|
|
66
|
+
if (held.has(key))
|
|
67
|
+
return fn();
|
|
68
|
+
const ticket = contender(key);
|
|
41
69
|
const deadline = Date.now() + timeoutMs;
|
|
42
|
-
|
|
70
|
+
try {
|
|
71
|
+
while (ticket.blocked()) {
|
|
72
|
+
if (Date.now() >= deadline)
|
|
73
|
+
throw new BerthError(`another restore is running (${stateDir})`);
|
|
74
|
+
sleep();
|
|
75
|
+
}
|
|
76
|
+
held.add(key);
|
|
43
77
|
try {
|
|
44
|
-
|
|
45
|
-
writeFileSync(join(dir, 'pid'), String(process.pid));
|
|
46
|
-
break;
|
|
78
|
+
return fn();
|
|
47
79
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw error;
|
|
51
|
-
if (abandoned(dir)) {
|
|
52
|
-
const stale = `${dir}.stale-${process.pid}-${Date.now()}`;
|
|
53
|
-
try {
|
|
54
|
-
renameSync(dir, stale);
|
|
55
|
-
rmSync(stale, { recursive: true, force: true });
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Another waiter took it over first; try again.
|
|
59
|
-
}
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (Date.now() > deadline)
|
|
63
|
-
throw new BerthError(`another restore is running (${dir})`);
|
|
64
|
-
sleep(100);
|
|
80
|
+
finally {
|
|
81
|
+
held.delete(key);
|
|
65
82
|
}
|
|
66
83
|
}
|
|
84
|
+
finally {
|
|
85
|
+
ticket.release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Awaited host transactions retain ownership until completion without blocking the event loop. */
|
|
89
|
+
export async function withAsyncLock(stateDir, fn, timeoutMs = 30_000) {
|
|
90
|
+
const ticket = contender(stateDir);
|
|
91
|
+
const deadline = Date.now() + timeoutMs;
|
|
67
92
|
try {
|
|
68
|
-
|
|
93
|
+
while (ticket.blocked()) {
|
|
94
|
+
if (Date.now() >= deadline)
|
|
95
|
+
throw new BerthError('Another operation is using this state. Try again when it finishes.');
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
97
|
+
}
|
|
98
|
+
return await fn();
|
|
69
99
|
}
|
|
70
100
|
finally {
|
|
71
|
-
|
|
101
|
+
ticket.release();
|
|
72
102
|
}
|
|
73
103
|
}
|
|
74
104
|
//# sourceMappingURL=lock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/restore/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/restore/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Tmux, SessionMeta } from '../tmux/client.js';
|
|
2
|
+
export type RecoveryState = 'requested' | 'confirmed' | 'unconfirmed' | 'failed';
|
|
3
|
+
export interface RecoveryStatus {
|
|
4
|
+
recovery?: RecoveryState;
|
|
5
|
+
recoveryAt?: string;
|
|
6
|
+
recoveryDetail?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A running pane proves process existence, not successful conversation resume. */
|
|
9
|
+
export declare function recoveryStatus(tmux: Tmux, name: string, meta: SessionMeta): RecoveryStatus;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** A running pane proves process existence, not successful conversation resume. */
|
|
2
|
+
export function recoveryStatus(tmux, name, meta) {
|
|
3
|
+
if (!meta.recovery)
|
|
4
|
+
return {};
|
|
5
|
+
const base = { recovery: meta.recovery, recoveryAt: meta.recoveryAt, recoveryDetail: meta.recoveryDetail };
|
|
6
|
+
if (meta.recovery === 'failed' || meta.recovery === 'confirmed')
|
|
7
|
+
return base;
|
|
8
|
+
const dead = tmux.run(['display-message', '-p', '-t', `=${name}:`, '#{pane_dead}'], true);
|
|
9
|
+
if (dead.ok && dead.stdout.trim() === '1')
|
|
10
|
+
return { ...base, recovery: 'failed', recoveryDetail: 'Agent process exited. Inspect terminal output before retrying.' };
|
|
11
|
+
if (meta.recovery === 'requested' && Date.now() - Number(meta.recoveryAt) >= 30_000) {
|
|
12
|
+
return { ...base, recovery: 'unconfirmed', recoveryDetail: 'No matching conversation acknowledgement. Inspect the terminal before continuing.' };
|
|
13
|
+
}
|
|
14
|
+
return base;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/restore/status.ts"],"names":[],"mappings":"AAKA,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,IAAY,EAAE,IAAiB;IACxE,IAAI,CAAC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAyB,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;IAC5H,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,IAAI,GAAG,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1F,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,gEAAgE,EAAE,CAAC;IACpK,IAAI,IAAI,CAAC,QAAQ,KAAK,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,MAAM,EAAE,CAAC;QACpF,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,mFAAmF,EAAE,CAAC;IACnJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Context } from '../context.js';
|
|
2
|
+
import type { LiveSession } from '../tmux/client.js';
|
|
3
|
+
import type { SnapshotSession } from './store.js';
|
|
4
|
+
export declare function recordIdentity(saved: SnapshotSession): string;
|
|
5
|
+
export declare function sameSession(saved: SnapshotSession, live: LiveSession): boolean;
|
|
6
|
+
export declare function nameCollision(ctx: Context, live: LiveSession, records: readonly SnapshotSession[]): SnapshotSession | undefined;
|
|
7
|
+
export declare const COLLISION_HINT = "A stopped conversation reserves this name. Rename or end this live session to recover the saved conversation; its record has been preserved.";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { wasClosed } from './lifecycle.js';
|
|
3
|
+
export function recordIdentity(saved) {
|
|
4
|
+
return createHash('sha256').update(JSON.stringify([saved.serverStart, saved.sessionId, saved.name, saved.agent, saved.cwd, saved.conversation, saved.profile])).digest('hex');
|
|
5
|
+
}
|
|
6
|
+
export function sameSession(saved, live) {
|
|
7
|
+
if (live.meta.restoreSource === recordIdentity(saved) && live.meta.agent === saved.agent && (live.meta.conversation ?? '') === saved.conversation)
|
|
8
|
+
return true;
|
|
9
|
+
if (!saved.serverStart || saved.serverStart !== live.serverStart)
|
|
10
|
+
return false;
|
|
11
|
+
if (saved.sessionId)
|
|
12
|
+
return saved.sessionId === live.sessionId;
|
|
13
|
+
// Upgrade a legacy record only with matching lifetime and binding evidence.
|
|
14
|
+
return saved.agent === live.meta.agent && saved.conversation === (live.meta.conversation ?? '');
|
|
15
|
+
}
|
|
16
|
+
export function nameCollision(ctx, live, records) {
|
|
17
|
+
return records.find((saved) => saved.name === live.name && !wasClosed(ctx, saved) && !sameSession(saved, live));
|
|
18
|
+
}
|
|
19
|
+
export const COLLISION_HINT = 'A stopped conversation reserves this name. Rename or end this live session to recover the saved conversation; its record has been preserved.';
|
|
20
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/snapshot/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAsB,EAAE,IAAiB;IACnE,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,KAAK,cAAc,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAC/J,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/E,IAAI,KAAK,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC;IAC/D,4EAA4E;IAC5E,OAAO,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB,EAAE,OAAmC;IAChG,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AAClH,CAAC;AACD,MAAM,CAAC,MAAM,cAAc,GAAG,8IAA8I,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Context } from '../context.js';
|
|
2
|
+
/** Runtime-only closure evidence survives the last session and server disappearing. */
|
|
3
|
+
export declare function watchClosures(ctx: Context): void;
|
|
4
|
+
export declare function wasClosed(ctx: Context, session: {
|
|
5
|
+
serverStart?: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
}): boolean;
|
|
8
|
+
/** A still-running owner can finish its closure hook before recovery is considered. */
|
|
9
|
+
export declare function serverOwnerAlive(ctx: Context, serverStart: string | undefined): boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { shellQuote } from '../shell.js';
|
|
4
|
+
import { formatLiteral } from '../tmux/fields.js';
|
|
5
|
+
/** Runtime-only closure evidence survives the last session and server disappearing. */
|
|
6
|
+
export function watchClosures(ctx) {
|
|
7
|
+
if (!ctx.tmux.serverRunning())
|
|
8
|
+
return;
|
|
9
|
+
const directory = join(ctx.config.stateDir, 'closed');
|
|
10
|
+
const installed = ctx.tmux.run(['show-option', '-gqv', '@berth_closure_directory'], true).stdout.trim();
|
|
11
|
+
if (installed === directory)
|
|
12
|
+
return;
|
|
13
|
+
mkdirSync(directory, { recursive: true });
|
|
14
|
+
const marker = `${formatLiteral(directory)}/#{start_time}-#{pid}-#{hook_session}`;
|
|
15
|
+
const command = `: > ${shellQuote(marker)}`;
|
|
16
|
+
try {
|
|
17
|
+
ctx.tmux.run(['set-hook', '-g', 'session-closed[98]', `run-shell ${shellQuote(command)}`]);
|
|
18
|
+
ctx.tmux.run(['set-option', '-g', '@berth_closure_directory', directory]);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (ctx.tmux.serverRunning())
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function wasClosed(ctx, session) {
|
|
26
|
+
if (!session.serverStart || !session.sessionId)
|
|
27
|
+
return false;
|
|
28
|
+
return existsSync(join(ctx.config.stateDir, 'closed', `${session.serverStart}-${session.sessionId}`));
|
|
29
|
+
}
|
|
30
|
+
/** A still-running owner can finish its closure hook before recovery is considered. */
|
|
31
|
+
export function serverOwnerAlive(ctx, serverStart) {
|
|
32
|
+
if (!serverStart)
|
|
33
|
+
return false;
|
|
34
|
+
const current = ctx.tmux.run(['display-message', '-p', '#{start_time}-#{pid}'], true);
|
|
35
|
+
return current.ok && current.stdout.trim() === serverStart;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../../src/snapshot/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,uFAAuF;AACvF,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE;QAAE,OAAO;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,MAAM,EAAE,0BAA0B,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxG,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO;IACpC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,GAAG,aAAa,CAAC,SAAS,CAAC,uCAAuC,CAAC;IAClF,MAAM,OAAO,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5C,IAAI,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,oBAAoB,EAAE,aAAa,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3F,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,0BAA0B,EAAE,SAAS,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE;YAAE,MAAM,KAAK,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAY,EAAE,OAAqD;IAC3F,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7D,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;AACxG,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,WAA+B;IAC5E,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,EAAE,IAAI,EAAE,sBAAsB,CAAC,EAAE,IAAI,CAAC,CAAC;IACtF,OAAO,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,WAAW,CAAC;AAC7D,CAAC"}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
import type { AgentProfile } from '../agents/profile.js';
|
|
2
|
+
import type { RecoveryStatus } from '../restore/status.js';
|
|
1
3
|
/** One managed session as recorded in a snapshot (implementation §2.1). */
|
|
2
|
-
export interface SnapshotSession {
|
|
4
|
+
export interface SnapshotSession extends RecoveryStatus {
|
|
3
5
|
name: string;
|
|
4
6
|
cwd: string;
|
|
5
7
|
agent: string;
|
|
6
8
|
argv: string;
|
|
9
|
+
argvVerified?: boolean;
|
|
7
10
|
conversation: string;
|
|
8
11
|
origin: string;
|
|
9
12
|
/** The tmux server lifetime the session was last seen in (absent in older snapshots). */
|
|
10
13
|
serverStart?: string;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
retained?: boolean;
|
|
16
|
+
profile?: AgentProfile;
|
|
17
|
+
bindingIssue?: string;
|
|
18
|
+
ownerWorkspace?: string;
|
|
11
19
|
}
|
|
12
20
|
export interface Snapshot {
|
|
13
21
|
version: 1;
|
|
@@ -27,5 +35,7 @@ export declare class SnapshotStore {
|
|
|
27
35
|
/** Write atomically: build in a temporary directory, then rename into place. */
|
|
28
36
|
write(sessions: SnapshotSession[], scrollback: Record<string, string>, now?: Date): string;
|
|
29
37
|
prune(keep: number): void;
|
|
38
|
+
/** Missing output is optional; unreadable output must not be silently discarded. */
|
|
39
|
+
optionalScrollback(dir: string, session: string): string | undefined;
|
|
30
40
|
scrollback(dir: string, session: string): string;
|
|
31
41
|
}
|