viewable-one 0.0.0-stage → 0.12.1
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/CHANGELOG.md +1460 -0
- package/LICENSE +21 -0
- package/README.md +755 -2
- package/SPAWN.md +2105 -0
- package/glytch.mjs +517 -0
- package/lib/acp.mjs +978 -0
- package/lib/admission.mjs +105 -0
- package/lib/agent-selection.mjs +123 -0
- package/lib/agents.mjs +913 -0
- package/lib/atomic.mjs +96 -0
- package/lib/auth.mjs +1387 -0
- package/lib/authenticatedHttp.mjs +68 -0
- package/lib/boardside.mjs +225 -0
- package/lib/child.mjs +1252 -0
- package/lib/cli-output.mjs +176 -0
- package/lib/cloudflared-release.json +25 -0
- package/lib/cloudflared.mjs +81 -0
- package/lib/codex-rpc.mjs +92 -0
- package/lib/codex.mjs +747 -0
- package/lib/codexApps.mjs +332 -0
- package/lib/codexPlanWindow.mjs +84 -0
- package/lib/codexToolInputs.mjs +139 -0
- package/lib/codingChecks.mjs +55 -0
- package/lib/compose.mjs +76 -0
- package/lib/computerApps.mjs +110 -0
- package/lib/conversationRules.mjs +45 -0
- package/lib/detect.mjs +605 -0
- package/lib/devserver.mjs +659 -0
- package/lib/digest.mjs +290 -0
- package/lib/doctor.mjs +464 -0
- package/lib/exitClass.mjs +64 -0
- package/lib/glytchFolder.mjs +105 -0
- package/lib/hosted.mjs +112 -0
- package/lib/imageAttachments.mjs +31 -0
- package/lib/init-connect.mjs +418 -0
- package/lib/kill.mjs +104 -0
- package/lib/logindaemon.mjs +976 -0
- package/lib/logs.mjs +120 -0
- package/lib/mcpjson.mjs +242 -0
- package/lib/memberPreview.mjs +165 -0
- package/lib/multiwatch.mjs +428 -0
- package/lib/pagePictures.mjs +396 -0
- package/lib/pageRoutes.mjs +67 -0
- package/lib/paths.mjs +9 -0
- package/lib/permissionAsk.mjs +418 -0
- package/lib/picker.mjs +300 -0
- package/lib/platform.mjs +60 -0
- package/lib/previewGateway.mjs +129 -0
- package/lib/previewTunnel.mjs +85 -0
- package/lib/procs.mjs +428 -0
- package/lib/project-mcp.mjs +288 -0
- package/lib/projectPaths.mjs +31 -0
- package/lib/refusal.mjs +70 -0
- package/lib/registry.mjs +204 -0
- package/lib/repomap.mjs +322 -0
- package/lib/restartPass.mjs +151 -0
- package/lib/retired-address.mjs +78 -0
- package/lib/rowsAnswers.mjs +389 -0
- package/lib/rowsEvents.mjs +86 -0
- package/lib/run-engine.mjs +16 -0
- package/lib/runner-health.mjs +57 -0
- package/lib/runner.mjs +1569 -0
- package/lib/runnerAdmission.mjs +35 -0
- package/lib/runnerApi.mjs +312 -0
- package/lib/runnerCompletion.mjs +406 -0
- package/lib/runnerLease.mjs +37 -0
- package/lib/runnerMaintenance.mjs +27 -0
- package/lib/runnerPace.mjs +283 -0
- package/lib/runnerPerformance.mjs +26 -0
- package/lib/runnerRealtime.mjs +340 -0
- package/lib/runnerRegister.mjs +96 -0
- package/lib/runnerSettings.mjs +40 -0
- package/lib/runnerSettlement.mjs +138 -0
- package/lib/runnerWorkspace.mjs +162 -0
- package/lib/serverSweep.mjs +90 -0
- package/lib/sharedPreview.mjs +292 -0
- package/lib/signIn.mjs +47 -0
- package/lib/spawn.mjs +330 -0
- package/lib/sweep.mjs +161 -0
- package/lib/sync.mjs +484 -0
- package/lib/uninstall-project.mjs +588 -0
- package/lib/uninstall.mjs +457 -0
- package/lib/update.mjs +302 -0
- package/lib/usageLimit.mjs +108 -0
- package/lib/windows-command.mjs +85 -0
- package/lib/worktree.mjs +1138 -0
- package/lib/writePolicy.mjs +45 -0
- package/package.json +58 -4
- package/spawn-runner.mjs +167 -0
package/lib/atomic.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// WRITE-THEN-RENAME, for every file the bridge writes that somebody else
|
|
2
|
+
// polls.
|
|
3
|
+
//
|
|
4
|
+
// `writeFile` is not atomic: it truncates (or creates) the destination and
|
|
5
|
+
// then streams bytes into it, so there is a real window in which a reader
|
|
6
|
+
// sees an empty or half-written file. Nothing in this repo has a lock, and
|
|
7
|
+
// every reader here is a POLLER - the app on its 10s ingestion tick, the
|
|
8
|
+
// runner on its poll loop, a session's own `check_answer` - so that window
|
|
9
|
+
// is not theoretical, it is the thing polling is guaranteed to find
|
|
10
|
+
// eventually.
|
|
11
|
+
//
|
|
12
|
+
// It has already cost twice, on the OTHER side of the same contract:
|
|
13
|
+
//
|
|
14
|
+
// - `lib/ops.mjs` readReceipt returns null rather than `{ok: false}` while
|
|
15
|
+
// the app's own non-atomic `writeFolderFile` is still in flight, because
|
|
16
|
+
// a poll landing in that window read "the app refused startSession",
|
|
17
|
+
// killed the request, and spawned nothing (2026-08-26). That guard reads
|
|
18
|
+
// around the app's window; this file removes the bridge's own.
|
|
19
|
+
// - a spawn request read mid-write parsed as invalid JSON and was CLAIMED
|
|
20
|
+
// with an ERROR receipt, and a claim is terminal - so a human click
|
|
21
|
+
// never ran.
|
|
22
|
+
//
|
|
23
|
+
// The temp file is a sibling (same directory, so the rename cannot cross a
|
|
24
|
+
// filesystem) and carries the pid plus a counter, so two processes writing
|
|
25
|
+
// the same destination never share a scratch name.
|
|
26
|
+
|
|
27
|
+
import { link, rename, writeFile, rm } from "node:fs/promises";
|
|
28
|
+
import { dirname, join, basename } from "node:path";
|
|
29
|
+
|
|
30
|
+
let seq = 0;
|
|
31
|
+
|
|
32
|
+
/** The rename failures that are worth waiting out rather than reporting.
|
|
33
|
+
*
|
|
34
|
+
* On Windows file locking is MANDATORY, not advisory: a rename over a
|
|
35
|
+
* destination another process has open fails EPERM or EBUSY, and every reader
|
|
36
|
+
* on the other side of this contract is a poller that opens these files
|
|
37
|
+
* constantly - plus the antivirus scanner and the search indexer, which open
|
|
38
|
+
* a file the instant it is created. The same rename a moment later succeeds.
|
|
39
|
+
* On a Mac neither code is ever produced here, so the loop below is dead code
|
|
40
|
+
* there and every existing test sees exactly what it saw before. Brief 21,
|
|
41
|
+
* session 3. */
|
|
42
|
+
const RETRYABLE_RENAME = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
43
|
+
|
|
44
|
+
/** How many times the rename is retried, and how long it waits between tries:
|
|
45
|
+
* 20ms, 40, 80, 120, 160 - about 420ms of patience in total, bounded so a
|
|
46
|
+
* genuinely locked file still fails rather than hanging a poll loop. */
|
|
47
|
+
export const RENAME_RETRIES = 5;
|
|
48
|
+
const backoffMs = (attempt) => Math.min(20 * (attempt + 1), 160);
|
|
49
|
+
|
|
50
|
+
/** Write `data` to `path` so a concurrent reader sees either the old file or
|
|
51
|
+
* the new one, never a partial. Returns nothing; throws what writeFile throws.
|
|
52
|
+
*
|
|
53
|
+
* The temp file is removed on failure, so a failed write never leaves a
|
|
54
|
+
* `.tmp-` sibling behind for the next reader to wonder about.
|
|
55
|
+
*
|
|
56
|
+
* `rename` and `sleep` are injected for the retry test, which needs a rename
|
|
57
|
+
* that fails EBUSY twice and no real half-second of waiting. */
|
|
58
|
+
export async function writeFileAtomic(
|
|
59
|
+
path,
|
|
60
|
+
data,
|
|
61
|
+
encoding = "utf8",
|
|
62
|
+
{ rename: renameImpl = rename, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), retries = RENAME_RETRIES, mode } = {},
|
|
63
|
+
) {
|
|
64
|
+
seq += 1;
|
|
65
|
+
const tmp = join(dirname(path), `.${basename(path)}.tmp-${process.pid}-${seq}`);
|
|
66
|
+
try {
|
|
67
|
+
await writeFile(tmp, data, mode === undefined ? encoding : { encoding, mode });
|
|
68
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
69
|
+
try {
|
|
70
|
+
await renameImpl(tmp, path);
|
|
71
|
+
break;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (attempt >= retries || !RETRYABLE_RENAME.has(error?.code)) throw error;
|
|
74
|
+
await sleep(backoffMs(attempt));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Publish a complete file only if the destination does not exist. The link
|
|
84
|
+
* reserves the destination atomically across processes; a crash before it
|
|
85
|
+
* leaves only an ignored temp file, and a crash after it leaves the full file.
|
|
86
|
+
* EEXIST means another publisher already committed this identity. */
|
|
87
|
+
export async function writeFileAtomicExclusive(path, data, encoding = "utf8") {
|
|
88
|
+
seq += 1;
|
|
89
|
+
const tmp = join(dirname(path), `.${basename(path)}.tmp-${process.pid}-${seq}`);
|
|
90
|
+
try {
|
|
91
|
+
await writeFile(tmp, data, encoding);
|
|
92
|
+
await link(tmp, path);
|
|
93
|
+
} finally {
|
|
94
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
95
|
+
}
|
|
96
|
+
}
|