@bytetrue/byspace 0.15.0 → 0.16.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/dist/commands/agent/open.js +3 -3
- package/dist/commands/onboard.js +2 -3
- package/dist/commands/open.d.ts +17 -2
- package/dist/commands/open.js +91 -68
- package/dist/run.js +2 -2
- package/node_modules/@bytetrue/client/package.json +3 -3
- package/node_modules/@bytetrue/highlight/package.json +1 -1
- package/node_modules/@bytetrue/protocol/dist/agent-deep-link.d.ts +0 -2
- package/node_modules/@bytetrue/protocol/dist/agent-deep-link.js +0 -33
- package/node_modules/@bytetrue/protocol/package.json +1 -1
- package/node_modules/@bytetrue/relay/package.json +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/byspace-env.d.ts +1 -0
- package/node_modules/@bytetrue/server/dist/server/server/byspace-env.js +9 -0
- package/node_modules/@bytetrue/server/dist/server/terminal/agent-hooks/pi/pi-extension.d.ts +1 -1
- package/node_modules/@bytetrue/server/dist/server/terminal/agent-hooks/pi/pi-extension.js +16 -1
- package/node_modules/@bytetrue/server/dist/server/terminal/terminal.js +6 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{index-1e1ff7510a612ab12641e5e07e0a7208.js → index-3e1fce95f28be7888ddabb4aad3c337a.js} +527 -526
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-3e1fce95f28be7888ddabb4aad3c337a.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-3e1fce95f28be7888ddabb4aad3c337a.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-1e67472b9640c869a55ff6915d1f7afb.js → indexeddb-attachment-store-a2e8491be0374dc2a52abfe002ecabe9.js} +1 -1
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-1e67472b9640c869a55ff6915d1f7afb.js.br → indexeddb-attachment-store-a2e8491be0374dc2a52abfe002ecabe9.js.br} +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-a2e8491be0374dc2a52abfe002ecabe9.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-63659fda781a1cc594f259795f1cc29f.js → native-file-attachment-store-6ba28513708667bbad7dd5434a0f8d5b.js} +3 -3
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ba28513708667bbad7dd5434a0f8d5b.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ba28513708667bbad7dd5434a0f8d5b.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-attention.9c2d51faf93468f68840811c0d9a8c6f.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-running.a52155010955dca55546513b60bb2e2b.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark.ee7787d5c6bd1ad67c31b9949a832f7a.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-attention.9c2d51faf93468f68840811c0d9a8c6f.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-running.a52155010955dca55546513b60bb2e2b.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light.ee7787d5c6bd1ad67c31b9949a832f7a.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/notification-icon.ba5a7dce236a7febe5d803b16f027b70.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/favicon.ico +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/index.html +1 -1
- package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/manifest.json +14 -2
- package/node_modules/@bytetrue/server/dist/server/web-ui/manifest.json.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/manifest.json.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-maskable-192.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/pwa-maskable-512.png +0 -0
- package/node_modules/@bytetrue/server/dist/src/server/byspace-env.js +9 -0
- package/node_modules/@bytetrue/server/dist/src/terminal/terminal.js +6 -0
- package/node_modules/@bytetrue/server/package.json +5 -5
- package/package.json +7 -7
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-1e1ff7510a612ab12641e5e07e0a7208.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-1e1ff7510a612ab12641e5e07e0a7208.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-1e67472b9640c869a55ff6915d1f7afb.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-63659fda781a1cc594f259795f1cc29f.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-63659fda781a1cc594f259795f1cc29f.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildDaemonConnectionCommandError, connectToDaemon } from "../../utils/client.js";
|
|
2
|
-
import {
|
|
2
|
+
import { openAgentInWebApp } from "../open.js";
|
|
3
3
|
const openAgentSchema = {
|
|
4
4
|
idField: "agentId",
|
|
5
5
|
columns: [
|
|
@@ -10,7 +10,7 @@ const openAgentSchema = {
|
|
|
10
10
|
};
|
|
11
11
|
export function addOpenOptions(command) {
|
|
12
12
|
return command
|
|
13
|
-
.description("Open an existing agent in BySpace
|
|
13
|
+
.description("Open an existing agent in the BySpace web app")
|
|
14
14
|
.argument("<agent-id>", "Existing agent ID")
|
|
15
15
|
.option("--server <server-id>", "Server ID (defaults to the local daemon)");
|
|
16
16
|
}
|
|
@@ -51,7 +51,7 @@ export async function runOpenCommand(agentIdArg, options, _command) {
|
|
|
51
51
|
throw error;
|
|
52
52
|
}
|
|
53
53
|
const serverId = await resolveServerId(options);
|
|
54
|
-
await
|
|
54
|
+
await openAgentInWebApp({ serverId, agentId }, { host: options.host });
|
|
55
55
|
return {
|
|
56
56
|
type: "single",
|
|
57
57
|
data: { agentId, serverId, status: "opened" },
|
package/dist/commands/onboard.js
CHANGED
|
@@ -106,9 +106,8 @@ function printNextSteps(pairingUrl, byspaceHome, richUi) {
|
|
|
106
106
|
? "1. Open BySpace and scan the QR code above, or paste the pairing link."
|
|
107
107
|
: "1. Open BySpace and connect to your daemon.",
|
|
108
108
|
`2. Web app: ${appBaseUrl}`,
|
|
109
|
-
"3.
|
|
110
|
-
|
|
111
|
-
'5. Example: byspace run --output-schema schema.json "extract fields"',
|
|
109
|
+
"3. Docs: https://github.com/ByteTrue/byspace",
|
|
110
|
+
'4. Example: byspace run --output-schema schema.json "extract fields"',
|
|
112
111
|
];
|
|
113
112
|
const quickReferenceLines = [
|
|
114
113
|
"1. byspace --help",
|
package/dist/commands/open.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { type AgentDeepLinkTarget } from "@bytetrue/protocol/agent-deep-link";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The daemon serves its bundled web UI on the same TCP port as `/ws`, so a TCP
|
|
4
|
+
* daemon host doubles as its HTTP origin. Unix sockets, SSH tunnels, and relay
|
|
5
|
+
* offers have no address a browser on this machine can reach.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveWebUiOrigin(rawHost: string): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Mirrors `buildHostWorkspaceRoute` in `packages/app/src/utils/host-routes.ts`.
|
|
10
|
+
* Workspace IDs are generated as `wks_<hex>` (packages/server/src/server/workspace-registry-model.ts),
|
|
11
|
+
* so percent-encoding always suffices; the app's base64 path segment form only
|
|
12
|
+
* exists for pre-v0.1.95 path-shaped IDs, which this fork does not carry.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildWorkspaceWebRoute(serverId: string, workspaceId: string): string;
|
|
15
|
+
export declare function openProjectInWebApp(projectPath: string): Promise<void>;
|
|
16
|
+
export declare function openAgentInWebApp(target: AgentDeepLinkTarget, options?: {
|
|
17
|
+
host?: string;
|
|
18
|
+
}): Promise<void>;
|
|
4
19
|
//# sourceMappingURL=open.d.ts.map
|
package/dist/commands/open.js
CHANGED
|
@@ -1,89 +1,112 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import { spawnProcess } from "@bytetrue/server";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
2
|
+
import { buildAgentDeepLinkRoute, } from "@bytetrue/protocol/agent-deep-link";
|
|
3
|
+
import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./daemon/local-daemon.js";
|
|
4
|
+
import { buildDaemonConnectionCommandError, connectToDaemon, getDaemonHost, getExplicitDaemonHost, } from "../utils/client.js";
|
|
5
|
+
/**
|
|
6
|
+
* The daemon serves its bundled web UI on the same TCP port as `/ws`, so a TCP
|
|
7
|
+
* daemon host doubles as its HTTP origin. Unix sockets, SSH tunnels, and relay
|
|
8
|
+
* offers have no address a browser on this machine can reach.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveWebUiOrigin(rawHost) {
|
|
11
|
+
const trimmed = rawHost.trim();
|
|
12
|
+
if (!trimmed || trimmed.startsWith("ssh://")) {
|
|
17
13
|
return null;
|
|
18
14
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"/opt/BySpace/BySpace",
|
|
23
|
-
path.join(homedir(), "Applications", "BySpace.AppImage"),
|
|
24
|
-
];
|
|
25
|
-
for (const candidate of candidates) {
|
|
26
|
-
if (existsSync(candidate)) {
|
|
27
|
-
return candidate;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
15
|
+
const endpoint = trimmed.startsWith("tcp://") ? trimmed.slice("tcp://".length) : trimmed;
|
|
16
|
+
const match = /^\[?([^\]/]+?)]?:(\d+)$/.exec(endpoint);
|
|
17
|
+
if (!match) {
|
|
30
18
|
return null;
|
|
31
19
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
const [, rawHostname, port] = match;
|
|
21
|
+
const hostname = rawHostname === "0.0.0.0" ? "127.0.0.1" : rawHostname;
|
|
22
|
+
const hostPart = hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
23
|
+
return `http://${hostPart}:${port}`;
|
|
24
|
+
}
|
|
25
|
+
function resolveLocalWebUiOrigin() {
|
|
26
|
+
const listen = resolveLocalDaemonState().listen;
|
|
27
|
+
const host = resolveTcpHostFromListen(listen) ?? listen;
|
|
28
|
+
return { origin: resolveWebUiOrigin(host), host };
|
|
29
|
+
}
|
|
30
|
+
function resolveWebUiOriginForHost(host) {
|
|
31
|
+
const explicitHost = getExplicitDaemonHost(host);
|
|
32
|
+
if (!explicitHost) {
|
|
33
|
+
return resolveLocalWebUiOrigin();
|
|
39
34
|
}
|
|
40
|
-
return
|
|
35
|
+
return { origin: resolveWebUiOrigin(explicitHost), host: explicitHost };
|
|
41
36
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Mirrors `buildHostWorkspaceRoute` in `packages/app/src/utils/host-routes.ts`.
|
|
39
|
+
* Workspace IDs are generated as `wks_<hex>` (packages/server/src/server/workspace-registry-model.ts),
|
|
40
|
+
* so percent-encoding always suffices; the app's base64 path segment form only
|
|
41
|
+
* exists for pre-v0.1.95 path-shaped IDs, which this fork does not carry.
|
|
42
|
+
*/
|
|
43
|
+
export function buildWorkspaceWebRoute(serverId, workspaceId) {
|
|
44
|
+
return `/h/${encodeURIComponent(serverId)}/workspace/${encodeURIComponent(workspaceId)}`;
|
|
45
|
+
}
|
|
46
|
+
function openInBrowser(url) {
|
|
47
|
+
let command;
|
|
48
|
+
let args;
|
|
49
|
+
if (process.platform === "darwin") {
|
|
50
|
+
command = "open";
|
|
51
|
+
args = [url];
|
|
52
|
+
}
|
|
53
|
+
else if (process.platform === "win32") {
|
|
54
|
+
command = process.env.ComSpec ?? "cmd";
|
|
55
|
+
args = ["/c", "start", "", url];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
command = "xdg-open";
|
|
59
|
+
args = [url];
|
|
60
|
+
}
|
|
61
|
+
spawnProcess(command, args, { detached: true, stdio: "ignore", shell: false }).unref();
|
|
62
|
+
process.stderr.write(`${url}\n`);
|
|
51
63
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
env: cleanEnvForDesktopLaunch(),
|
|
57
|
-
}).unref();
|
|
64
|
+
function reportWebUiUnavailable(host) {
|
|
65
|
+
process.stderr.write(`Cannot open the web app for ${host}: the daemon has no HTTP endpoint reachable from here.\n` +
|
|
66
|
+
"Start it with `byspace daemon start` and open the Web UI URL it prints.\n");
|
|
67
|
+
process.exitCode = 1;
|
|
58
68
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
export async function openProjectInWebApp(projectPath) {
|
|
70
|
+
const { origin, host } = resolveLocalWebUiOrigin();
|
|
71
|
+
if (!origin) {
|
|
72
|
+
reportWebUiUnavailable(host);
|
|
73
|
+
return;
|
|
62
74
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
let client;
|
|
76
|
+
try {
|
|
77
|
+
client = await connectToDaemon();
|
|
66
78
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
spawnDetached("open", ["-n", "-g", "-a", desktopApp, "--args", ...args]);
|
|
79
|
+
catch (error) {
|
|
80
|
+
const failure = buildDaemonConnectionCommandError({ error });
|
|
81
|
+
process.stderr.write(`${failure.message}\n${failure.details}\n`);
|
|
82
|
+
process.exitCode = 1;
|
|
72
83
|
return;
|
|
73
84
|
}
|
|
74
|
-
spawnDetached(desktopApp, args);
|
|
75
|
-
}
|
|
76
|
-
export async function openDesktopWithProject(projectPath) {
|
|
77
85
|
try {
|
|
78
|
-
|
|
86
|
+
const serverId = client.getLastServerInfoMessage()?.serverId.trim();
|
|
87
|
+
if (!serverId) {
|
|
88
|
+
throw new Error("The daemon did not report a server ID.");
|
|
89
|
+
}
|
|
90
|
+
const result = await client.openProject(projectPath);
|
|
91
|
+
if (result.error || !result.workspace) {
|
|
92
|
+
throw new Error(result.error ?? `The daemon did not open a workspace for ${projectPath}`);
|
|
93
|
+
}
|
|
94
|
+
openInBrowser(`${origin}${buildWorkspaceWebRoute(serverId, result.workspace.id)}`);
|
|
79
95
|
}
|
|
80
96
|
catch (error) {
|
|
81
|
-
|
|
82
|
-
process.stderr.write(`${message}\n`);
|
|
97
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
83
98
|
process.exitCode = 1;
|
|
84
99
|
}
|
|
100
|
+
finally {
|
|
101
|
+
await client.close().catch(() => { });
|
|
102
|
+
}
|
|
85
103
|
}
|
|
86
|
-
export async function
|
|
87
|
-
|
|
104
|
+
export async function openAgentInWebApp(target, options = {}) {
|
|
105
|
+
const { origin, host } = resolveWebUiOriginForHost(options.host);
|
|
106
|
+
if (!origin) {
|
|
107
|
+
reportWebUiUnavailable(host || getDaemonHost());
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
openInBrowser(`${origin}${buildAgentDeepLinkRoute(target)}`);
|
|
88
111
|
}
|
|
89
112
|
//# sourceMappingURL=open.js.map
|
package/dist/run.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createCli } from "./cli.js";
|
|
2
2
|
import { classifyInvocation } from "./classify.js";
|
|
3
|
-
import {
|
|
3
|
+
import { openProjectInWebApp } from "./commands/open.js";
|
|
4
4
|
export function createCliParseArgv(input) {
|
|
5
5
|
const program = createCli();
|
|
6
6
|
const knownCommands = new Set(program.commands.map((command) => command.name()));
|
|
@@ -30,7 +30,7 @@ export async function runCli(argv, options = {}) {
|
|
|
30
30
|
nodeArgv: options.nodeArgv,
|
|
31
31
|
});
|
|
32
32
|
if (!Array.isArray(parseArgv)) {
|
|
33
|
-
await
|
|
33
|
+
await openProjectInWebApp(parseArgv.resolvedPath);
|
|
34
34
|
return typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
35
35
|
}
|
|
36
36
|
const program = createCli();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytetrue/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "BySpace client SDK package",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"test": "vitest run"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@bytetrue/protocol": "0.
|
|
43
|
-
"@bytetrue/relay": "0.
|
|
42
|
+
"@bytetrue/protocol": "0.16.1",
|
|
43
|
+
"@bytetrue/relay": "0.16.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^20.9.0",
|
|
@@ -3,6 +3,4 @@ export interface AgentDeepLinkTarget {
|
|
|
3
3
|
agentId: string;
|
|
4
4
|
}
|
|
5
5
|
export declare function buildAgentDeepLinkRoute(target: AgentDeepLinkTarget): `/h/${string}/agent/${string}`;
|
|
6
|
-
export declare function buildAgentDeepLink(target: AgentDeepLinkTarget): string;
|
|
7
|
-
export declare function parseAgentDeepLink(input: string): AgentDeepLinkTarget | null;
|
|
8
6
|
//# sourceMappingURL=agent-deep-link.d.ts.map
|
|
@@ -13,37 +13,4 @@ export function buildAgentDeepLinkRoute(target) {
|
|
|
13
13
|
const { serverId, agentId } = normalizeAgentDeepLinkTarget(target);
|
|
14
14
|
return `/h/${encodeURIComponent(serverId)}/agent/${encodeURIComponent(agentId)}`;
|
|
15
15
|
}
|
|
16
|
-
export function buildAgentDeepLink(target) {
|
|
17
|
-
return `byspace:/${buildAgentDeepLinkRoute(target)}`;
|
|
18
|
-
}
|
|
19
|
-
export function parseAgentDeepLink(input) {
|
|
20
|
-
let url;
|
|
21
|
-
try {
|
|
22
|
-
url = new URL(input);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
if (url.protocol !== "byspace:" ||
|
|
28
|
-
url.hostname !== "h" ||
|
|
29
|
-
url.username ||
|
|
30
|
-
url.password ||
|
|
31
|
-
url.port ||
|
|
32
|
-
url.search ||
|
|
33
|
-
url.hash) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
37
|
-
if (segments.length !== 3 || segments[1] !== "agent") {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const serverId = normalizeSegment(decodeURIComponent(segments[0] ?? ""));
|
|
42
|
-
const agentId = normalizeSegment(decodeURIComponent(segments[2] ?? ""));
|
|
43
|
-
return serverId && agentId ? { serverId, agentId } : null;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
16
|
//# sourceMappingURL=agent-deep-link.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const PI_TERMINAL_HOOK_OWNER_PID_ENV_KEY = "BYSPACE_PI_TERMINAL_HOOK_OWNER_PID";
|
|
1
2
|
export type BySpaceNodeEnv = "development" | "production" | "test";
|
|
2
3
|
export type ProcessEnvRecord = Record<string, string | undefined>;
|
|
3
4
|
export type ExternalProcessEnv = NodeJS.ProcessEnv & Record<string, string>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const BYSPACE_NODE_ENV = "BYSPACE_NODE_ENV";
|
|
2
2
|
const ELECTRON_RUN_AS_NODE = "ELECTRON_RUN_AS_NODE";
|
|
3
|
+
// Shared with the installed Pi extension (which cannot import from here).
|
|
4
|
+
export const PI_TERMINAL_HOOK_OWNER_PID_ENV_KEY = "BYSPACE_PI_TERMINAL_HOOK_OWNER_PID";
|
|
3
5
|
const RUNTIME_CONTROL_ENV_KEYS = [
|
|
4
6
|
BYSPACE_NODE_ENV,
|
|
5
7
|
"BYSPACE_DESKTOP_MANAGED",
|
|
@@ -7,6 +9,13 @@ const RUNTIME_CONTROL_ENV_KEYS = [
|
|
|
7
9
|
ELECTRON_RUN_AS_NODE,
|
|
8
10
|
"ELECTRON_NO_ATTACH_CONSOLE",
|
|
9
11
|
"ESBUILD_BINARY_PATH",
|
|
12
|
+
// Written into `process.env` by the Pi terminal-activity extension so child Pi
|
|
13
|
+
// processes do not register a second reporter for the same terminal. If it
|
|
14
|
+
// survives into a fresh terminal or agent, that Pi process sees a marker whose
|
|
15
|
+
// value is a stale pid, concludes it is an inherited child, and registers no
|
|
16
|
+
// handlers at all — the terminal then never reports running/idle. It is
|
|
17
|
+
// per-process state, never inherited. See docs/terminal-activity.md.
|
|
18
|
+
PI_TERMINAL_HOOK_OWNER_PID_ENV_KEY,
|
|
10
19
|
];
|
|
11
20
|
function buildInternalProcessEnv(baseEnv) {
|
|
12
21
|
return { ...baseEnv };
|
|
@@ -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);
|