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/kill.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// STOPPING A PROCESS, on a platform that has signals and on one that does not.
|
|
2
|
+
//
|
|
3
|
+
// Brief 21 session 3, ruling 4A (2026-09-07). Every kill in this package
|
|
4
|
+
// was `process.kill(pid, "SIGTERM")` then `"SIGKILL"` after a grace, and on
|
|
5
|
+
// Windows that is a fiction twice over: Node maps BOTH signals to
|
|
6
|
+
// TerminateProcess, so the grace the code promises never happens and the child
|
|
7
|
+
// never flushes its log; and it terminates ONE pid, while `claude.cmd` runs the
|
|
8
|
+
// real agent as a grandchild - which then survives and keeps billing. The
|
|
9
|
+
// Windows equivalent of the pair is `taskkill /pid <pid> /t` and, after the
|
|
10
|
+
// same grace, `taskkill /pid <pid> /t /f`: /t is the tree, /f is the force.
|
|
11
|
+
//
|
|
12
|
+
// THE POSIX PATH IS UNCHANGED, deliberately and to the byte. The call it makes
|
|
13
|
+
// is the caller's own `signal` - `child.kill(sig)` in child.mjs, so the "one
|
|
14
|
+
// pid and never the process group" ruling of 2026-09-06 still holds there
|
|
15
|
+
// (lib/child.mjs's kill closure says why that is load-bearing), and
|
|
16
|
+
// `process.kill(pid, sig)` everywhere else, so an ESRCH still throws and a
|
|
17
|
+
// caller that reads a throw as "gone" (lib/devserver.mjs) keeps reading it
|
|
18
|
+
// that way. Windows mirrors that: a taskkill that exits non-zero throws.
|
|
19
|
+
//
|
|
20
|
+
// NOTHING HERE DECIDES WHETHER A PID MAY BE KILLED. That decision lives with
|
|
21
|
+
// the callers - `verifyRecordedServer` in devserver.mjs, `isOursToKill` in
|
|
22
|
+
// procs.mjs - and ruling 4A keeps it there: never a kill on a bare pid.
|
|
23
|
+
|
|
24
|
+
import { execFile } from "node:child_process";
|
|
25
|
+
|
|
26
|
+
/** The Windows binary. Named once so a test can assert the argv without
|
|
27
|
+
* repeating the string. */
|
|
28
|
+
export const TASKKILL = "taskkill";
|
|
29
|
+
|
|
30
|
+
/** The argv `taskkill` is given for a pid. `/t` is the whole tree, which is
|
|
31
|
+
* the point: the agent is a grandchild of the `.cmd` shim. `/f` is only added
|
|
32
|
+
* for the force pass, so the first pass still asks politely and the process
|
|
33
|
+
* still gets the grace to flush. Pure, so the sequence can be pinned on any
|
|
34
|
+
* platform. */
|
|
35
|
+
export function taskkillArgs(pid, force = false) {
|
|
36
|
+
const args = ["/pid", String(pid), "/t"];
|
|
37
|
+
if (force) args.push("/f");
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** "The process exists, and it will not go politely."
|
|
42
|
+
*
|
|
43
|
+
* 2026-09-07, brief 21, the first windows-latest run. `taskkill /pid <pid> /t`
|
|
44
|
+
* with no `/f` asks a process to close by posting WM_CLOSE to its windows - so
|
|
45
|
+
* for a console-less process, which every child this package spawns is, the
|
|
46
|
+
* polite pass ALWAYS fails, with exit 1 and "This process with PID x could not
|
|
47
|
+
* be terminated. Reason: This process can only be terminated forcefully (with
|
|
48
|
+
* /F option)." killTree threw that, and `stopRecordedServer` reads a throw from
|
|
49
|
+
* the first pass as "gone" and returns without ever reaching the force pass -
|
|
50
|
+
* so a verified detached dev server was never stopped on Windows at all.
|
|
51
|
+
*
|
|
52
|
+
* That answer is not a failure and it is not "gone": it is the grace being
|
|
53
|
+
* declined, which is precisely what the second pass is for. Everything else
|
|
54
|
+
* taskkill can say - not found, access denied - still throws, so a caller that
|
|
55
|
+
* reads a throw as "the process is already gone" keeps reading it that way. */
|
|
56
|
+
export const refusedTheGrace = (text) =>
|
|
57
|
+
/can only be terminated forcefully|with\s*\/F\s*option/i.test(String(text ?? ""));
|
|
58
|
+
|
|
59
|
+
/** The signal a non-Windows kill sends. */
|
|
60
|
+
export const signalFor = (force) => (force ? "SIGKILL" : "SIGTERM");
|
|
61
|
+
|
|
62
|
+
const defaultExec = (bin, args) =>
|
|
63
|
+
new Promise((resolve) => {
|
|
64
|
+
execFile(bin, args, { timeout: 10_000, windowsHide: true }, (error, stdout, stderr) =>
|
|
65
|
+
resolve({
|
|
66
|
+
ok: !error,
|
|
67
|
+
code: error?.code ?? 0,
|
|
68
|
+
stdout: String(stdout ?? ""),
|
|
69
|
+
stderr: String(stderr ?? ""),
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const defaultSignal = (pid, sig) => process.kill(pid, sig);
|
|
75
|
+
|
|
76
|
+
/** Stop `pid`. `force` picks the second pass (SIGKILL / `/t /f`) rather than
|
|
77
|
+
* the first (SIGTERM / `/t`).
|
|
78
|
+
*
|
|
79
|
+
* `platform`, `exec` and `signal` are injected so Windows behaviour is proven
|
|
80
|
+
* without a Windows machine and macOS behaviour is proven to be what it was.
|
|
81
|
+
* Returns what it did; THROWS what the underlying kill throws, because "the
|
|
82
|
+
* process is already gone" is a caller's decision to make and every existing
|
|
83
|
+
* caller makes it from the throw. */
|
|
84
|
+
export async function killTree(pid, { force = false, platform = process.platform, exec = defaultExec, signal = defaultSignal } = {}) {
|
|
85
|
+
const usable = Number.isFinite(Number(pid)) && Number(pid) > 1;
|
|
86
|
+
if (platform === "win32" && usable) {
|
|
87
|
+
const args = taskkillArgs(Number(pid), force);
|
|
88
|
+
const result = await exec(TASKKILL, args);
|
|
89
|
+
if (!result?.ok) {
|
|
90
|
+
const said = `${result?.stderr ?? ""}\n${result?.stdout ?? ""}`;
|
|
91
|
+
// The grace declined is not a failure and not a disappearance: say so,
|
|
92
|
+
// and let the caller's force pass be the answer. See refusedTheGrace.
|
|
93
|
+
if (!force && refusedTheGrace(said)) {
|
|
94
|
+
return { how: "taskkill", argv: [TASKKILL, ...args], force, refusedTheGrace: true };
|
|
95
|
+
}
|
|
96
|
+
const detail = String(result?.stderr ?? "").trim() || `exit ${result?.code ?? "?"}`;
|
|
97
|
+
throw new Error(`taskkill ${args.join(" ")} failed: ${detail}`);
|
|
98
|
+
}
|
|
99
|
+
return { how: "taskkill", argv: [TASKKILL, ...args], force };
|
|
100
|
+
}
|
|
101
|
+
const sig = signalFor(force);
|
|
102
|
+
signal(pid, sig);
|
|
103
|
+
return { how: "signal", signal: sig, force };
|
|
104
|
+
}
|