Skip to content

Commit d2cd467

Browse files
committed
Recover presenter work after failures
1 parent f65050c commit d2cd467

5 files changed

Lines changed: 408 additions & 47 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"scripts/doctor.mjs",
3434
"scripts/generate-summaries.mjs",
3535
"scripts/present.mjs",
36+
"scripts/presenter-runtime.mjs",
3637
"scripts/serve-built.mjs",
3738
"scripts/summary-path.mjs"
3839
],

scripts/present.mjs

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
#!/usr/bin/env node
22

3-
import { spawn, spawnSync } from 'node:child_process';
3+
import { spawn } from 'node:child_process';
44
import { createHash } from 'node:crypto';
55
import {
6-
existsSync,
76
mkdtempSync,
87
readFileSync,
98
rmSync,
@@ -19,6 +18,7 @@ import {
1918
selectCodingAgent,
2019
} from './coding-agents.mjs';
2120
import { doctorReport } from './doctor.mjs';
21+
import { ensureBuiltAssets, openBrowser } from './presenter-runtime.mjs';
2222

2323
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
2424
const callerDirectory = process.cwd();
@@ -100,18 +100,11 @@ if (agentEnabled) {
100100
agentArgs.push('--snapshot', outputPath);
101101
}
102102

103-
const builtPage = resolve(root, 'dist/index.html');
104-
if (!existsSync(builtPage)) {
105-
const result = spawnSync(
106-
process.platform === 'win32' ? 'npm.cmd' : 'npm',
107-
['run', 'build'],
108-
{ cwd: root, stdio: 'inherit' },
109-
);
110-
if (result.error) {
111-
console.error(`Could not build the local page: ${result.error.message}`);
112-
process.exit(1);
113-
}
114-
if (result.status !== 0) process.exit(result.status || 1);
103+
try {
104+
ensureBuiltAssets({ root });
105+
} catch (error) {
106+
console.error(error.message);
107+
process.exit(1);
115108
}
116109

117110
const feed = spawn(
@@ -127,37 +120,11 @@ let site;
127120
let agent;
128121
let agentTimer;
129122
let agentFingerprint;
123+
let failedAgentFingerprint;
130124
let queuedFingerprint;
131125
let browserOpened = false;
132126
let browserOpenTimer;
133127

134-
function openBrowser(url) {
135-
let command;
136-
let args;
137-
if (process.env.BROWSER) {
138-
command = process.env.BROWSER;
139-
args = [url];
140-
} else if (process.platform === 'darwin') {
141-
command = 'open';
142-
args = [url];
143-
} else if (process.platform === 'win32') {
144-
command = 'cmd.exe';
145-
args = ['/d', '/s', '/c', 'start', '', url];
146-
} else {
147-
command = 'xdg-open';
148-
args = [url];
149-
}
150-
151-
const opener = spawn(command, args, {
152-
detached: true,
153-
stdio: 'ignore',
154-
});
155-
opener.once('error', (error) => {
156-
console.error(`Could not open the browser: ${error.message}`);
157-
});
158-
opener.unref();
159-
}
160-
161128
function startSite() {
162129
if (closing || site) return;
163130
const child = spawn(
@@ -196,7 +163,10 @@ function startSite() {
196163
browserOpenTimer = setTimeout(() => {
197164
browserOpenTimer = undefined;
198165
browserOpened = true;
199-
openBrowser(match[1]);
166+
openBrowser(match[1], {
167+
onError: (error) =>
168+
console.error(`Could not open the browser: ${error.message}`),
169+
});
200170
}, 750);
201171
}
202172
});
@@ -287,11 +257,14 @@ function runAgent(fingerprint) {
287257
settled = true;
288258
const finishedFingerprint = agentFingerprint;
289259
if (agent === child) agent = undefined;
290-
agentFingerprint = finishedFingerprint;
291-
if (!closing && (error || code || signal)) {
260+
agentFingerprint = undefined;
261+
const superseded =
262+
queuedFingerprint && queuedFingerprint !== finishedFingerprint;
263+
if (!closing && (error || code || signal) && !superseded) {
264+
failedAgentFingerprint = finishedFingerprint;
292265
if (error) console.error(error.message);
293266
console.error(
294-
'The coding agent could not write notes. The diff page will stay open.',
267+
'The coding agent could not write notes. It will retry after the diff changes or Diffsplain restarts.',
295268
);
296269
}
297270
const latest = queuedFingerprint || snapshotFingerprint();
@@ -311,10 +284,15 @@ function scheduleAgent(fingerprint) {
311284
state?.hasCurrentAgentNotes &&
312285
selectedFingerprint === state.fingerprint
313286
) {
314-
agentFingerprint = selectedFingerprint;
315287
return;
316288
}
317-
if (!selectedFingerprint || selectedFingerprint === agentFingerprint) return;
289+
if (
290+
!selectedFingerprint ||
291+
selectedFingerprint === agentFingerprint ||
292+
selectedFingerprint === failedAgentFingerprint
293+
) {
294+
return;
295+
}
318296
if (agent) {
319297
if (selectedFingerprint !== agentFingerprint) {
320298
queuedFingerprint = selectedFingerprint;

scripts/presenter-runtime.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { spawn, spawnSync } from 'node:child_process';
2+
import { existsSync } from 'node:fs';
3+
import { join } from 'node:path';
4+
5+
export function browserCommand({ url, browser, platform = process.platform }) {
6+
if (browser) return { command: browser, args: [url] };
7+
if (platform === 'darwin') return { command: 'open', args: [url] };
8+
if (platform === 'win32') {
9+
return { command: 'cmd.exe', args: ['/d', '/s', '/c', 'start', '', url] };
10+
}
11+
return { command: 'xdg-open', args: [url] };
12+
}
13+
14+
export function openBrowser(
15+
url,
16+
{
17+
browser = process.env.BROWSER,
18+
platform = process.platform,
19+
spawnProcess = spawn,
20+
onError = () => {},
21+
} = {},
22+
) {
23+
try {
24+
const { command, args } = browserCommand({ url, browser, platform });
25+
const opener = spawnProcess(command, args, { detached: true, stdio: 'ignore' });
26+
opener.once('error', onError);
27+
opener.unref();
28+
return opener;
29+
} catch (error) {
30+
onError(error);
31+
return undefined;
32+
}
33+
}
34+
35+
function builtAssetsReady({ root, exists = existsSync }) {
36+
const dist = join(root, 'dist');
37+
return exists(join(dist, 'index.html')) && exists(join(dist, 'assets'));
38+
}
39+
40+
function npmCommand(platform) {
41+
return platform === 'win32' ? 'npm.cmd' : 'npm';
42+
}
43+
44+
function buildAssets(runtime) {
45+
const result = runtime.run(npmCommand(runtime.platform), ['run', 'build'], {
46+
cwd: runtime.root,
47+
stdio: 'inherit',
48+
});
49+
if (result.error) {
50+
throw new Error(`Could not build the local page: ${result.error.message}`);
51+
}
52+
if (result.status === 0) return;
53+
const status = Number.isInteger(result.status) ? Number(result.status) : 1;
54+
throw new Error(`Could not build the local page: npm run build exited with ${status}.`);
55+
}
56+
57+
export function ensureBuiltAssets(options) {
58+
const runtime = {
59+
exists: existsSync,
60+
run: spawnSync,
61+
platform: process.platform,
62+
...options,
63+
};
64+
if (builtAssetsReady({ root: runtime.root, exists: runtime.exists })) return false;
65+
66+
buildAssets(runtime);
67+
if (!builtAssetsReady({ root: runtime.root, exists: runtime.exists })) {
68+
throw new Error('Could not build the local page: built assets are still missing.');
69+
}
70+
return true;
71+
}

0 commit comments

Comments
 (0)