@bytetrue/byspace 0.14.6 → 0.16.0

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.
Files changed (90) hide show
  1. package/dist/commands/daemon/index.js +16 -0
  2. package/dist/commands/daemon/install-service.d.ts +15 -0
  3. package/dist/commands/daemon/install-service.js +69 -0
  4. package/dist/commands/daemon/local-daemon.d.ts +1 -0
  5. package/dist/commands/daemon/local-daemon.js +1 -1
  6. package/dist/commands/daemon/service/backend.d.ts +16 -0
  7. package/dist/commands/daemon/service/backend.js +50 -0
  8. package/dist/commands/daemon/service/launchd.d.ts +15 -0
  9. package/dist/commands/daemon/service/launchd.js +101 -0
  10. package/dist/commands/daemon/service/service-spec.d.ts +54 -0
  11. package/dist/commands/daemon/service/service-spec.js +95 -0
  12. package/dist/commands/daemon/service/service-status.d.ts +28 -0
  13. package/dist/commands/daemon/service/service-status.js +32 -0
  14. package/dist/commands/daemon/service/systemd.d.ts +22 -0
  15. package/dist/commands/daemon/service/systemd.js +90 -0
  16. package/dist/commands/daemon/service/windows-task.d.ts +12 -0
  17. package/dist/commands/daemon/service/windows-task.js +75 -0
  18. package/dist/commands/daemon/service-status.d.ts +13 -0
  19. package/dist/commands/daemon/service-status.js +30 -0
  20. package/dist/commands/daemon/uninstall-service.d.ts +11 -0
  21. package/dist/commands/daemon/uninstall-service.js +53 -0
  22. package/node_modules/@bytetrue/client/package.json +3 -3
  23. package/node_modules/@bytetrue/highlight/package.json +1 -1
  24. package/node_modules/@bytetrue/protocol/dist/generated/validation/ws-outbound.aot.js +10901 -10861
  25. package/node_modules/@bytetrue/protocol/dist/messages.d.ts +231 -92
  26. package/node_modules/@bytetrue/protocol/dist/messages.js +29 -0
  27. package/node_modules/@bytetrue/protocol/dist/validation/ws-outbound-schema-metadata.d.ts +44 -22
  28. package/node_modules/@bytetrue/protocol/package.json +1 -1
  29. package/node_modules/@bytetrue/relay/package.json +1 -1
  30. package/node_modules/@bytetrue/server/dist/server/server/byspace-env.d.ts +1 -0
  31. package/node_modules/@bytetrue/server/dist/server/server/byspace-env.js +9 -0
  32. package/node_modules/@bytetrue/server/dist/server/server/daemon-config-store.d.ts +11 -0
  33. package/node_modules/@bytetrue/server/dist/server/server/daemon-config-store.js +71 -1
  34. package/node_modules/@bytetrue/server/dist/server/server/session/daemon/daemon-service-install.d.ts +25 -0
  35. package/node_modules/@bytetrue/server/dist/server/server/session/daemon/daemon-service-install.js +182 -0
  36. package/node_modules/@bytetrue/server/dist/server/server/session/daemon/daemon-service-manager.d.ts +21 -0
  37. package/node_modules/@bytetrue/server/dist/server/server/session/daemon/daemon-service-manager.js +154 -0
  38. package/node_modules/@bytetrue/server/dist/server/server/websocket-server.js +2 -0
  39. package/node_modules/@bytetrue/server/dist/server/terminal/agent-hooks/pi/pi-extension.d.ts +1 -1
  40. package/node_modules/@bytetrue/server/dist/server/terminal/agent-hooks/pi/pi-extension.js +16 -1
  41. package/node_modules/@bytetrue/server/dist/server/terminal/terminal.js +6 -0
  42. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{index-679e8946b965f52a7a27dc9c54f59a4c.js → index-eade2661e21bbc08a50d56f1c6b0917a.js} +537 -535
  43. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-eade2661e21bbc08a50d56f1c6b0917a.js.br +0 -0
  44. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-eade2661e21bbc08a50d56f1c6b0917a.js.gz +0 -0
  45. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-d61d5dd974e90216cec99e62c343ffc7.js → indexeddb-attachment-store-a2e8491be0374dc2a52abfe002ecabe9.js} +1 -1
  46. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-d61d5dd974e90216cec99e62c343ffc7.js.br → indexeddb-attachment-store-a2e8491be0374dc2a52abfe002ecabe9.js.br} +0 -0
  47. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-a2e8491be0374dc2a52abfe002ecabe9.js.gz +0 -0
  48. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-df0c0ebe8d233a5a74eddb59a110f00b.js → native-file-attachment-store-6ba28513708667bbad7dd5434a0f8d5b.js} +3 -3
  49. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ba28513708667bbad7dd5434a0f8d5b.js.br +0 -0
  50. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ba28513708667bbad7dd5434a0f8d5b.js.gz +0 -0
  51. package/node_modules/@bytetrue/server/dist/server/web-ui/apple-touch-icon.png +0 -0
  52. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-attention.9c2d51faf93468f68840811c0d9a8c6f.png +0 -0
  53. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-running.a52155010955dca55546513b60bb2e2b.png +0 -0
  54. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark.ee7787d5c6bd1ad67c31b9949a832f7a.png +0 -0
  55. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-attention.9c2d51faf93468f68840811c0d9a8c6f.png +0 -0
  56. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-running.a52155010955dca55546513b60bb2e2b.png +0 -0
  57. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light.ee7787d5c6bd1ad67c31b9949a832f7a.png +0 -0
  58. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/notification-icon.ba5a7dce236a7febe5d803b16f027b70.png +0 -0
  59. package/node_modules/@bytetrue/server/dist/server/web-ui/favicon.ico +0 -0
  60. package/node_modules/@bytetrue/server/dist/server/web-ui/index.html +1 -1
  61. package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.br +0 -0
  62. package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.gz +0 -0
  63. package/node_modules/@bytetrue/server/dist/server/web-ui/manifest.json +14 -2
  64. package/node_modules/@bytetrue/server/dist/server/web-ui/manifest.json.br +0 -0
  65. package/node_modules/@bytetrue/server/dist/server/web-ui/manifest.json.gz +0 -0
  66. package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-icon-192.png +0 -0
  67. package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-icon-512.png +0 -0
  68. package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-maskable-192.png +0 -0
  69. package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-maskable-512.png +0 -0
  70. package/node_modules/@bytetrue/server/dist/src/server/byspace-env.js +9 -0
  71. package/node_modules/@bytetrue/server/dist/src/server/daemon-config-store.js +71 -1
  72. package/node_modules/@bytetrue/server/dist/src/server/session/daemon/daemon-service-install.js +182 -0
  73. package/node_modules/@bytetrue/server/dist/src/server/session/daemon/daemon-service-manager.js +154 -0
  74. package/node_modules/@bytetrue/server/dist/src/server/session/daemon/install-origin.js +64 -0
  75. package/node_modules/@bytetrue/server/dist/src/server/session/daemon/npm-global-cli.js +98 -0
  76. package/node_modules/@bytetrue/server/dist/src/terminal/terminal.js +6 -0
  77. package/node_modules/@bytetrue/server/package.json +15 -5
  78. package/package.json +7 -7
  79. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-679e8946b965f52a7a27dc9c54f59a4c.js.br +0 -0
  80. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-679e8946b965f52a7a27dc9c54f59a4c.js.gz +0 -0
  81. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-d61d5dd974e90216cec99e62c343ffc7.js.gz +0 -0
  82. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-df0c0ebe8d233a5a74eddb59a110f00b.js.br +0 -0
  83. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-df0c0ebe8d233a5a74eddb59a110f00b.js.gz +0 -0
  84. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  85. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  86. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  87. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  88. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  89. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  90. package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
@@ -0,0 +1,182 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveBySpaceHome } from "../../byspace-home.js";
6
+ export function buildServerServiceSpec(home) {
7
+ const env = {};
8
+ for (const [key, value] of Object.entries(process.env)) {
9
+ if (key.startsWith("BYSPACE_") && value !== undefined)
10
+ env[key] = value;
11
+ }
12
+ env.PATH = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
13
+ return {
14
+ label: "cc.cd.byspace.daemon",
15
+ nodePath: process.execPath,
16
+ // The running daemon knows its own entry: the supervisor entry resolved this
17
+ // process. Use the daemon-worker path via the server package root.
18
+ runnerEntry: resolveSelfRunnerEntry(),
19
+ logPath: path.join(home, "daemon.log"),
20
+ env,
21
+ };
22
+ }
23
+ function resolveSelfRunnerEntry() {
24
+ // Walk up from this module to the @bytetrue/server package root (name-checked, so
25
+ // it works under both src/ (tsx) and dist/ (compiled) tree shapes), then locate the
26
+ // supervisor entrypoint the way the CLI's resolveDaemonRunnerEntry does.
27
+ const here = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
28
+ let dir = here;
29
+ let packageRoot = null;
30
+ for (let i = 0; i < 12; i += 1) {
31
+ const pkgPath = path.join(dir, "package.json");
32
+ if (existsSync(pkgPath)) {
33
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
34
+ if (parsed.name === "@bytetrue/server") {
35
+ packageRoot = dir;
36
+ break;
37
+ }
38
+ }
39
+ const parent = path.dirname(dir);
40
+ if (parent === dir)
41
+ break;
42
+ dir = parent;
43
+ }
44
+ if (!packageRoot) {
45
+ throw new Error("Unable to locate the @bytetrue/server package root from the daemon module");
46
+ }
47
+ const distRunner = path.join(packageRoot, "dist", "scripts", "supervisor-entrypoint.js");
48
+ if (existsSync(distRunner))
49
+ return distRunner;
50
+ const srcRunner = path.join(packageRoot, "scripts", "supervisor-entrypoint.ts");
51
+ if (existsSync(srcRunner))
52
+ return srcRunner;
53
+ throw new Error("Unable to locate the supervisor entrypoint in the server package");
54
+ }
55
+ function escapeXml(value) {
56
+ return value
57
+ .replace(/&/g, "&amp;")
58
+ .replace(/</g, "&lt;")
59
+ .replace(/>/g, "&gt;")
60
+ .replace(/"/g, "&quot;");
61
+ }
62
+ export function renderLaunchdPlist(spec) {
63
+ const programArgs = [spec.nodePath, spec.runnerEntry];
64
+ const args = programArgs.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
65
+ const envEntries = Object.entries(spec.env)
66
+ .map(([key, value]) => ` <key>${escapeXml(key)}</key>\n <string>${escapeXml(value)}</string>`)
67
+ .join("\n");
68
+ return `<?xml version="1.0" encoding="UTF-8"?>
69
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
70
+ <plist version="1.0">
71
+ <dict>
72
+ <key>Label</key>
73
+ <string>${escapeXml(spec.label)}</string>
74
+ <key>ProgramArguments</key>
75
+ <array>
76
+ ${args}
77
+ </array>
78
+ <key>EnvironmentVariables</key>
79
+ <dict>
80
+ ${envEntries}
81
+ </dict>
82
+ <key>RunAtLoad</key>
83
+ <true/>
84
+ <key>KeepAlive</key>
85
+ <false/>
86
+ <key>StandardOutPath</key>
87
+ <string>${escapeXml(spec.logPath)}</string>
88
+ <key>StandardErrorPath</key>
89
+ <string>${escapeXml(spec.logPath)}</string>
90
+ <key>ProcessType</key>
91
+ <string>Background</string>
92
+ </dict>
93
+ </plist>
94
+ `;
95
+ }
96
+ function launchctl(args) {
97
+ const result = spawnSync("launchctl", args, { encoding: "utf8", timeout: 15000 });
98
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
99
+ return { ok: result.status === 0, output };
100
+ }
101
+ /** Write the plist and load it. The daemon exits after install; launchd takes over. */
102
+ export function installLaunchdServiceServer(spec) {
103
+ const home = resolveBySpaceHome();
104
+ const plistPath = path.join(home, "Library", "LaunchAgents", `${spec.label}.plist`);
105
+ mkdirSync(path.dirname(plistPath), { recursive: true });
106
+ writeFileSync(plistPath, renderLaunchdPlist(spec), { mode: 0o644 });
107
+ const uid = process.getuid?.();
108
+ if (uid === undefined)
109
+ throw new Error("cannot resolve uid for launchctl bootstrap");
110
+ launchctl(["bootout", `gui/${uid}/${spec.label}`]);
111
+ const boot = launchctl(["bootstrap", `gui/${String(uid)}`, plistPath]);
112
+ if (!boot.ok) {
113
+ throw new Error(`launchctl bootstrap failed: ${boot.output}`);
114
+ }
115
+ }
116
+ /** Stop and remove the service definition. The daemon keeps running until stopped. */
117
+ export function uninstallLaunchdServiceServer(label) {
118
+ const uid = process.getuid?.();
119
+ if (uid !== undefined) {
120
+ launchctl(["bootout", `gui/${uid}/${label}`]);
121
+ }
122
+ const home = resolveBySpaceHome();
123
+ rmSync(path.join(home, "Library", "LaunchAgents", `${label}.plist`), { force: true });
124
+ }
125
+ export function installSystemdServiceServer(spec) {
126
+ const configHome = process.env.XDG_CONFIG_HOME ?? path.join(process.env.HOME ?? "", ".config");
127
+ const unitPath = path.join(configHome, "systemd", "user", `${spec.label}.service`);
128
+ mkdirSync(path.dirname(unitPath), { recursive: true });
129
+ writeFileSync(unitPath, renderSystemdUnit(spec), { mode: 0o644 });
130
+ const run = (args) => {
131
+ const result = spawnSync("systemctl", ["--user", ...args], {
132
+ encoding: "utf8",
133
+ timeout: 20000,
134
+ });
135
+ if (result.status !== 0) {
136
+ throw new Error(`systemctl --user ${args[0]} failed: ${result.stderr ?? result.stdout ?? ""}`);
137
+ }
138
+ };
139
+ run(["daemon-reload"]);
140
+ run(["enable", "--now", `${spec.label}.service`]);
141
+ }
142
+ function renderSystemdUnit(spec) {
143
+ const execStart = [spec.nodePath, spec.runnerEntry]
144
+ .map((part) => part.replace(/([\\"])/g, "\\$1"))
145
+ .join(" ");
146
+ const envLines = Object.entries(spec.env)
147
+ .map(([key, value]) => `Environment="${key}=${value.replace(/([\\"])/g, "\\$1")}"`)
148
+ .join("\n");
149
+ return `# Generated by the BySpace daemon (install-service). Edits are overwritten on reinstall.
150
+ [Unit]
151
+ Description=BySpace daemon
152
+ After=network-online.target
153
+
154
+ [Service]
155
+ Type=exec
156
+ ExecStart=${execStart}
157
+ ${envLines}
158
+ Restart=on-failure
159
+ RestartSec=5
160
+
161
+ [Install]
162
+ WantedBy=default.target
163
+ `;
164
+ }
165
+ export function uninstallSystemdServiceServer(label) {
166
+ const run = (args) => {
167
+ spawnSync("systemctl", ["--user", ...args], { encoding: "utf8", timeout: 20000 });
168
+ };
169
+ run(["disable", "--now", `${label}.service`]);
170
+ const configHome = process.env.XDG_CONFIG_HOME ?? path.join(process.env.HOME ?? "", ".config");
171
+ rmSync(path.join(configHome, "systemd", "user", `${label}.service`), { force: true });
172
+ run(["daemon-reload"]);
173
+ }
174
+ export function uninstallWindowsTaskServer(label) {
175
+ spawnSync("powershell.exe", [
176
+ "-NoProfile",
177
+ "-NonInteractive",
178
+ "-Command",
179
+ `Unregister-ScheduledTask -TaskPath '\\BySpace\\' -TaskName '${label}' -Confirm:$false -ErrorAction SilentlyContinue`,
180
+ ], { encoding: "utf8", timeout: 30000 });
181
+ }
182
+ //# sourceMappingURL=daemon-service-install.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Daemon-side OS service management (issue 043). The daemon manages its own service
3
+ * registration because install requires the daemon's install-origin context — the
4
+ * npm-global verification and the runner entry resolution live in this process.
5
+ *
6
+ * KeepAlive/Restart is deliberately not "always": `byspace daemon stop` must stay a
7
+ * real stop. The supervisor's own crash-restart handles worker failures.
8
+ */
9
+ export declare const DAEMON_SERVICE_LABEL = "cc.cd.byspace.daemon";
10
+ export type DaemonServiceState = "not-installed" | "installed-stopped" | "installed-running-not-this-process" | "managed-by-service" | "unknown";
11
+ export interface DaemonServiceView {
12
+ state: DaemonServiceState;
13
+ label: string;
14
+ /** Linux only: whether the user manager survives logout (systemd linger). */
15
+ linger?: boolean;
16
+ }
17
+ /** Guard: only a daemon from the published npm global install may self-register. */
18
+ export declare function validateServiceOriginSync(): string | null;
19
+ /** Query the current service view. Never caches: each call re-probes the manager. */
20
+ export declare function queryDaemonServiceView(): DaemonServiceView;
21
+ //# sourceMappingURL=daemon-service-manager.d.ts.map
@@ -0,0 +1,154 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { BYSPACE_CLI_PACKAGE } from "./npm-global-cli.js";
4
+ import path from "node:path";
5
+ import { validateDaemonInstallOrigin, daemonInstallOriginRuntime } from "./install-origin.js";
6
+ /**
7
+ * Daemon-side OS service management (issue 043). The daemon manages its own service
8
+ * registration because install requires the daemon's install-origin context — the
9
+ * npm-global verification and the runner entry resolution live in this process.
10
+ *
11
+ * KeepAlive/Restart is deliberately not "always": `byspace daemon stop` must stay a
12
+ * real stop. The supervisor's own crash-restart handles worker failures.
13
+ */
14
+ export const DAEMON_SERVICE_LABEL = "cc.cd.byspace.daemon";
15
+ function launchdProbe(home) {
16
+ const plistPath = path.join(home, "Library", "LaunchAgents", `${DAEMON_SERVICE_LABEL}.plist`);
17
+ return {
18
+ definitionExists: () => existsSync(plistPath),
19
+ servicePid: () => {
20
+ try {
21
+ const uid = process.getuid?.();
22
+ if (uid === undefined)
23
+ return null;
24
+ const output = execFileSync("launchctl", ["print", `gui/${uid}/${DAEMON_SERVICE_LABEL}`], {
25
+ encoding: "utf8",
26
+ timeout: 10000,
27
+ });
28
+ const match = output.match(/^\s*pid\s*=\s*(\d+)/m);
29
+ return match ? Number(match[1]) : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ },
35
+ viewFlags: () => ({}),
36
+ };
37
+ }
38
+ function systemdProbe() {
39
+ const configHome = process.env.XDG_CONFIG_HOME ?? path.join(process.env.HOME ?? "", ".config");
40
+ const unitPath = path.join(configHome, "systemd", "user", `${DAEMON_SERVICE_LABEL}.service`);
41
+ return {
42
+ definitionExists: () => existsSync(unitPath),
43
+ servicePid: () => {
44
+ try {
45
+ const output = execFileSync("systemctl", ["--user", "show", `${DAEMON_SERVICE_LABEL}.service`, "--property", "MainPID"], { encoding: "utf8", timeout: 10000 });
46
+ const match = output.match(/^MainPID=(\d+)$/m);
47
+ const pid = match ? Number(match[1]) : 0;
48
+ return pid > 0 ? pid : null;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ },
54
+ viewFlags: () => {
55
+ try {
56
+ const output = execFileSync("loginctl", ["show-user", process.env.USER ?? "", "--property", "Linger"], { encoding: "utf8", timeout: 10000 });
57
+ return { linger: /Linger=yes/.test(output) };
58
+ }
59
+ catch {
60
+ return {};
61
+ }
62
+ },
63
+ };
64
+ }
65
+ function windowsProbe() {
66
+ return {
67
+ definitionExists: () => {
68
+ try {
69
+ const output = execFileSync("powershell.exe", [
70
+ "-NoProfile",
71
+ "-NonInteractive",
72
+ "-Command",
73
+ `(Get-ScheduledTask -TaskPath '\\BySpace\\' -TaskName '${DAEMON_SERVICE_LABEL}' -ErrorAction SilentlyContinue) -ne $null`,
74
+ ], { encoding: "utf8", timeout: 15000 });
75
+ return output.trim() === "True";
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ },
81
+ servicePid: () => null,
82
+ viewFlags: () => ({}),
83
+ };
84
+ }
85
+ function probeForPlatform() {
86
+ switch (process.platform) {
87
+ case "darwin":
88
+ return launchdProbe(resolveBySpaceHomeForService());
89
+ case "linux":
90
+ return systemdProbe();
91
+ case "win32":
92
+ return windowsProbe();
93
+ default:
94
+ return null;
95
+ }
96
+ }
97
+ function resolveBySpaceHomeForService() {
98
+ return process.env.BYSPACE_HOME ?? path.join(process.env.HOME ?? "", ".byspace");
99
+ }
100
+ /** Guard: only a daemon from the published npm global install may self-register. */
101
+ export function validateServiceOriginSync() {
102
+ if (existsSync("/.dockerenv") || existsSync("/run/.containerenv")) {
103
+ return "Registering a service is not supported inside Docker. Use the container runtime's restart policy instead.";
104
+ }
105
+ // The npm -g listing is the authoritative origin: it proves the published install
106
+ // exists on this host. validateDaemonInstallOrigin then checks that THIS daemon
107
+ // process actually runs from it — a dev checkout parses fine but fails the
108
+ // package-root containment check, which is exactly the guard that matters here.
109
+ try {
110
+ const npmPrefix = execFileSync("npm", ["prefix", "-g"], {
111
+ encoding: "utf8",
112
+ timeout: 15000,
113
+ }).trim();
114
+ const packagePath = path.join(npmPrefix, "lib", "node_modules", "@bytetrue", "byspace");
115
+ const pkgJsonPath = path.join(packagePath, "package.json");
116
+ if (!existsSync(pkgJsonPath)) {
117
+ return `${BYSPACE_CLI_PACKAGE} is not installed with npm -g on this host`;
118
+ }
119
+ const version = JSON.parse(readFileSync(pkgJsonPath, "utf8")).version ?? "";
120
+ const install = {
121
+ version,
122
+ packagePath,
123
+ globalRootPath: npmPrefix,
124
+ isLinked: false,
125
+ };
126
+ return validateDaemonInstallOrigin(install, process.env.npm_package_version ?? version, daemonInstallOriginRuntime);
127
+ }
128
+ catch (error) {
129
+ return error instanceof Error ? error.message : String(error);
130
+ }
131
+ }
132
+ /** Query the current service view. Never caches: each call re-probes the manager. */
133
+ export function queryDaemonServiceView() {
134
+ const probe = probeForPlatform();
135
+ if (!probe) {
136
+ return { state: "unknown", label: DAEMON_SERVICE_LABEL };
137
+ }
138
+ if (!probe.definitionExists()) {
139
+ return { state: "not-installed", label: DAEMON_SERVICE_LABEL, ...probe.viewFlags() };
140
+ }
141
+ const servicePid = probe.servicePid();
142
+ if (servicePid === null) {
143
+ return { state: "installed-stopped", label: DAEMON_SERVICE_LABEL, ...probe.viewFlags() };
144
+ }
145
+ if (servicePid === process.pid) {
146
+ return { state: "managed-by-service", label: DAEMON_SERVICE_LABEL, ...probe.viewFlags() };
147
+ }
148
+ return {
149
+ state: "installed-running-not-this-process",
150
+ label: DAEMON_SERVICE_LABEL,
151
+ ...probe.viewFlags(),
152
+ };
153
+ }
154
+ //# sourceMappingURL=daemon-service-manager.js.map
@@ -1170,6 +1170,8 @@ export class DaemonWebSocketServer {
1170
1170
  agentConfigApply: true,
1171
1171
  // COMPAT(daemonNetworkConfig): added in v0.14.3, remove gate after 2027-07-14.
1172
1172
  daemonNetworkConfig: true,
1173
+ // COMPAT(daemonServiceInstall): added in v0.14.7, remove gate after 2027-09-20.
1174
+ daemonServiceInstall: true,
1173
1175
  },
1174
1176
  };
1175
1177
  }
@@ -1,2 +1,2 @@
1
- export declare const PI_TERMINAL_ACTIVITY_EXTENSION_SOURCE = "// byspace.pi-terminal-activity\nconst ownerPidKey = \"BYSPACE_PI_TERMINAL_HOOK_OWNER_PID\";\n\n// Keep one request in flight and only the latest state waiting behind it.\nexport function createReportQueue(sendReport) {\n let pendingReport;\n let reporting;\n\n async function flushReports() {\n while (pendingReport) {\n const next = pendingReport;\n pendingReport = undefined;\n try {\n await sendReport(next);\n } catch {}\n }\n reporting = undefined;\n }\n\n return {\n enqueue(next) {\n pendingReport = next;\n reporting ??= Promise.resolve().then(flushReports);\n return reporting;\n },\n wait() {\n return reporting ?? Promise.resolve();\n },\n };\n}\n\nasync function sendReport(next) {\n await fetch(next.url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n terminalId: next.terminalId,\n token: next.token,\n state: next.state,\n }),\n signal: AbortSignal.timeout(1_000),\n });\n}\n\nconst reportQueue = createReportQueue(sendReport);\n\nfunction report(state) {\n const url =\n process.env.BYSPACE_TERMINAL_ACTIVITY_URL;\n const terminalId = process.env.BYSPACE_TERMINAL_ID;\n const token = process.env.BYSPACE_ACTIVITY_TOKEN;\n if (!url || !terminalId || !token) return reportQueue.wait();\n\n return reportQueue.enqueue({ url, terminalId, token, state });\n}\n\nexport default function byspaceTerminalActivity(pi) {\n const ownerPid = process.env[ownerPidKey];\n if (ownerPid && ownerPid !== String(process.pid)) return;\n process.env[ownerPidKey] = String(process.pid);\n\n pi.on(\"agent_start\", () => void report(\"running\"));\n pi.on(\"ui_prompt_start\", () => void report(\"needs-input\"));\n pi.on(\"ui_prompt_end\", () => void report(\"running\"));\n pi.on(\"agent_settled\", () => void report(\"idle\"));\n pi.on(\"session_shutdown\", () => report(\"idle\"));\n}\n";
1
+ export declare const PI_TERMINAL_ACTIVITY_EXTENSION_SOURCE = "// byspace.pi-terminal-activity\nconst ownerPidKey = \"BYSPACE_PI_TERMINAL_HOOK_OWNER_PID\";\n\n// Keep one request in flight and only the latest state waiting behind it.\nexport function createReportQueue(sendReport) {\n let pendingReport;\n let reporting;\n\n async function flushReports() {\n while (pendingReport) {\n const next = pendingReport;\n pendingReport = undefined;\n try {\n await sendReport(next);\n } catch {}\n }\n reporting = undefined;\n }\n\n return {\n enqueue(next) {\n pendingReport = next;\n reporting ??= Promise.resolve().then(flushReports);\n return reporting;\n },\n wait() {\n return reporting ?? Promise.resolve();\n },\n };\n}\n\nasync function sendReport(next) {\n await fetch(next.url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n terminalId: next.terminalId,\n token: next.token,\n state: next.state,\n }),\n signal: AbortSignal.timeout(1_000),\n });\n}\n\nconst reportQueue = createReportQueue(sendReport);\n\n// The marker is inherited by Pi child processes so they do not register a second\n// reporter for the same terminal. A marker left by a process that is gone is\n// stale, not an owner: treating it as an owner makes this Pi report nothing at\n// all, which looks exactly like \"the terminal stopped showing running\".\nfunction ownerIsLive(ownerPid) {\n const pid = Number(ownerPid);\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return error?.code === \"EPERM\";\n }\n}\n\nfunction report(state) {\n const url =\n process.env.BYSPACE_TERMINAL_ACTIVITY_URL;\n const terminalId = process.env.BYSPACE_TERMINAL_ID;\n const token = process.env.BYSPACE_ACTIVITY_TOKEN;\n if (!url || !terminalId || !token) return reportQueue.wait();\n\n return reportQueue.enqueue({ url, terminalId, token, state });\n}\n\nexport default function byspaceTerminalActivity(pi) {\n const ownerPid = process.env[ownerPidKey];\n if (ownerPid && ownerPid !== String(process.pid) && ownerIsLive(ownerPid)) return;\n process.env[ownerPidKey] = String(process.pid);\n\n pi.on(\"agent_start\", () => void report(\"running\"));\n pi.on(\"ui_prompt_start\", () => void report(\"needs-input\"));\n pi.on(\"ui_prompt_end\", () => void report(\"running\"));\n pi.on(\"agent_settled\", () => void report(\"idle\"));\n pi.on(\"session_shutdown\", () => report(\"idle\"));\n}\n";
2
2
  //# sourceMappingURL=pi-extension.d.ts.map
@@ -44,6 +44,21 @@ async function sendReport(next) {
44
44
 
45
45
  const reportQueue = createReportQueue(sendReport);
46
46
 
47
+ // The marker is inherited by Pi child processes so they do not register a second
48
+ // reporter for the same terminal. A marker left by a process that is gone is
49
+ // stale, not an owner: treating it as an owner makes this Pi report nothing at
50
+ // all, which looks exactly like "the terminal stopped showing running".
51
+ function ownerIsLive(ownerPid) {
52
+ const pid = Number(ownerPid);
53
+ if (!Number.isInteger(pid) || pid <= 0) return false;
54
+ try {
55
+ process.kill(pid, 0);
56
+ return true;
57
+ } catch (error) {
58
+ return error?.code === "EPERM";
59
+ }
60
+ }
61
+
47
62
  function report(state) {
48
63
  const url =
49
64
  process.env.BYSPACE_TERMINAL_ACTIVITY_URL;
@@ -56,7 +71,7 @@ function report(state) {
56
71
 
57
72
  export default function byspaceTerminalActivity(pi) {
58
73
  const ownerPid = process.env[ownerPidKey];
59
- if (ownerPid && ownerPid !== String(process.pid)) return;
74
+ if (ownerPid && ownerPid !== String(process.pid) && ownerIsLive(ownerPid)) return;
60
75
  process.env[ownerPidKey] = String(process.pid);
61
76
 
62
77
  pi.on("agent_start", () => void report("running"));
@@ -786,6 +786,12 @@ export async function createTerminal(options) {
786
786
  if (!commandFinished) {
787
787
  return true;
788
788
  }
789
+ // The interactive shell only emits this when its foreground job ends. A
790
+ // terminal agent that exits without reporting idle (SIGKILL, crash, a
791
+ // suspend that never resumes) leaves `working` set forever, so treat the
792
+ // shell regaining the prompt as proof that the agent is gone. `interrupt`
793
+ // only clears `working`; a finished-attention idle state is untouched.
794
+ activityTracker.interrupt();
789
795
  for (const listener of Array.from(commandFinishedListeners)) {
790
796
  try {
791
797
  listener(commandFinished);