agent-dag 1.33.65 → 1.33.67
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/web/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agents-deck</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CcEqayMe.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CF2yG9oG.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.67",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch parallel subagents fork, call tools, and return on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/server/installer.mjs
CHANGED
|
@@ -324,5 +324,7 @@ export { AGENT_DAG_DIR, CLAUDE_DIR, CODEX_DIR, CLAUDE_EVENTS, CODEX_EVENTS };
|
|
|
324
324
|
// parse is never treated as an empty one, and the replacement is a single
|
|
325
325
|
// rename rather than a truncate a reader can land inside. installScript carries
|
|
326
326
|
// the same guarantee to the hook scripts themselves, which are the files live
|
|
327
|
-
// sessions are actually executing
|
|
328
|
-
|
|
327
|
+
// sessions are actually executing, and renameWithRetry goes out on its own for
|
|
328
|
+
// the files writeFileAtomic cannot write — the fetched uv binary in
|
|
329
|
+
// uv-bootstrap.mjs — which still need the Windows retry.
|
|
330
|
+
export { readSettingsForWrite, writeFileAtomic, installScript, renameWithRetry };
|
|
@@ -14,11 +14,17 @@
|
|
|
14
14
|
// is no other way: uv, pipx, and python -m pipx have all already been ruled out
|
|
15
15
|
// by the caller. AGENTS_DECK_NO_INSTALL=1 disables it along with everything else.
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
|
-
import { mkdir, writeFile, rm, chmod, readdir } from "node:fs/promises";
|
|
17
|
+
import { mkdir, writeFile, rm, chmod, readdir, copyFile, open } from "node:fs/promises";
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { homedir, tmpdir } from "node:os";
|
|
21
21
|
import { run } from "./exec.mjs";
|
|
22
|
+
// The same rename used to replace settings.json, for the same reason: on
|
|
23
|
+
// Windows a rename over an existing file fails outright while any other process
|
|
24
|
+
// holds the target open, and a virus scanner or the search indexer opens a
|
|
25
|
+
// freshly written executable the instant it appears. Sharing one implementation
|
|
26
|
+
// keeps the retryable-error list from drifting apart between the two callers.
|
|
27
|
+
import { renameWithRetry } from "./installer.mjs";
|
|
22
28
|
|
|
23
29
|
const TOOL_DIR = join(homedir(), ".agents-deck", "tools");
|
|
24
30
|
const UV_DIR = join(TOOL_DIR, "uv");
|
|
@@ -160,8 +166,22 @@ async function findUv(dir, depth = 0) {
|
|
|
160
166
|
* every caller treats a missing uv as an expected state.
|
|
161
167
|
*/
|
|
162
168
|
export async function bootstrapUv() {
|
|
169
|
+
// A uv from an earlier run is reused, but only once it has proved it still
|
|
170
|
+
// runs. Existing-and-therefore-good was a one-way door: the caller has no
|
|
171
|
+
// other installer left by the time it gets here, and nothing anywhere ever
|
|
172
|
+
// deletes or re-checks this path, so a uv that had been damaged — by an
|
|
173
|
+
// interrupted copy from a deck older than this fix, a half-finished disk
|
|
174
|
+
// restore, a truncating backup tool — made every boot from then on fail with
|
|
175
|
+
// `install_failed` until the user found and deleted the directory by hand.
|
|
176
|
+
// One `--version` costs milliseconds and turns that into a re-download.
|
|
163
177
|
const already = existingBootstrappedUv();
|
|
164
|
-
if (already
|
|
178
|
+
if (already && (await run(already, ["--version"], { timeout: 15_000 })).ok) {
|
|
179
|
+
return { ok: true, bin: already, version: "existing" };
|
|
180
|
+
}
|
|
181
|
+
// A broken one is deliberately left where it is rather than deleted: the
|
|
182
|
+
// download below renames its replacement over it, and if the download cannot
|
|
183
|
+
// happen the file is harmless — every caller probes `--version` before using
|
|
184
|
+
// it, so a uv that does not answer is already treated as absent.
|
|
165
185
|
|
|
166
186
|
// Downloading an executable is a bigger step than installing a package with a
|
|
167
187
|
// tool the user already chose, so it gets its own off switch as well as the
|
|
@@ -175,6 +195,9 @@ export async function bootstrapUv() {
|
|
|
175
195
|
const base = `${DOWNLOAD_BASE}/${version}/${asset}`;
|
|
176
196
|
|
|
177
197
|
const staging = join(tmpdir(), `agents-deck-uv-${process.pid}`);
|
|
198
|
+
// Set while a half-finished binary exists under a name of its own, cleared the
|
|
199
|
+
// moment it is renamed into place; the finally below removes whatever is left.
|
|
200
|
+
let partial = null;
|
|
178
201
|
try {
|
|
179
202
|
const [archive, sumText] = await Promise.all([
|
|
180
203
|
download(base),
|
|
@@ -201,19 +224,41 @@ export async function bootstrapUv() {
|
|
|
201
224
|
|
|
202
225
|
await mkdir(UV_DIR, { recursive: true });
|
|
203
226
|
const dest = join(UV_DIR, process.platform === "win32" ? "uv.exe" : "uv");
|
|
204
|
-
// Copy rather than rename: the staging dir is in the system temp directory,
|
|
205
|
-
// which is often a different filesystem, and rename across one fails.
|
|
206
|
-
const { copyFile } = await import("node:fs/promises");
|
|
207
|
-
await copyFile(found, dest);
|
|
208
|
-
if (process.platform !== "win32") await chmod(dest, 0o755);
|
|
209
227
|
|
|
210
|
-
|
|
228
|
+
// Copy rather than rename out of staging: the staging dir is in the system
|
|
229
|
+
// temp directory, which is often a different filesystem, and rename across
|
|
230
|
+
// one fails. But the copy goes to a name of its own INSIDE UV_DIR — same
|
|
231
|
+
// filesystem as the destination, so THAT rename is the atomic kind — and
|
|
232
|
+
// only becomes `uv` once it is whole, flushed, executable and has answered
|
|
233
|
+
// `--version`. Copying 40MB straight to the final name is many writes long,
|
|
234
|
+
// and a Ctrl-C in the middle of them (the boot install runs before the
|
|
235
|
+
// signal handlers are up) left a truncated file under the name every later
|
|
236
|
+
// run trusts. Interrupt this instead and all that is left is an inert temp
|
|
237
|
+
// file: `uv` either does not exist or is a binary that ran once already.
|
|
238
|
+
// The pid keeps two decks starting at once off each other's copy, and the
|
|
239
|
+
// .exe keeps the file executable on Windows, where the version check below
|
|
240
|
+
// would otherwise have nothing to spawn.
|
|
241
|
+
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
242
|
+
partial = join(UV_DIR, `.uv-${process.pid}-${Date.now().toString(36)}${suffix}`);
|
|
243
|
+
await copyFile(found, partial);
|
|
244
|
+
// Flushed before the rename, so a crash or power cut just after a
|
|
245
|
+
// successful bootstrap cannot leave the name pointing at blocks that were
|
|
246
|
+
// never written — the same guarantee writeFileAtomic gives settings.json.
|
|
247
|
+
const handle = await open(partial, "r+");
|
|
248
|
+
try { await handle.sync(); } finally { await handle.close(); }
|
|
249
|
+
if (process.platform !== "win32") await chmod(partial, 0o755);
|
|
250
|
+
|
|
251
|
+
const check = await run(partial, ["--version"], { timeout: 15_000 });
|
|
211
252
|
if (!check.ok) return { ok: false, reason: "does_not_run" };
|
|
212
253
|
|
|
254
|
+
await renameWithRetry(partial, dest);
|
|
255
|
+
partial = null;
|
|
256
|
+
|
|
213
257
|
return { ok: true, bin: dest, version };
|
|
214
258
|
} catch (err) {
|
|
215
259
|
return { ok: false, reason: "error", detail: String(err?.message ?? err).slice(0, 200) };
|
|
216
260
|
} finally {
|
|
217
261
|
await rm(staging, { recursive: true, force: true }).catch(() => {});
|
|
262
|
+
if (partial) await rm(partial, { force: true }).catch(() => {});
|
|
218
263
|
}
|
|
219
264
|
}
|