@zhanguym/chatroom 1.0.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.
- package/LICENSE +202 -0
- package/README.md +52 -0
- package/assets/chatroom.svg +15 -0
- package/chatroom.config.example.json +24 -0
- package/dist/app/application.d.ts +20 -0
- package/dist/app/application.js +75 -0
- package/dist/app/event-bus.d.ts +25 -0
- package/dist/app/event-bus.js +11 -0
- package/dist/app/external-access-registry.d.ts +17 -0
- package/dist/app/external-access-registry.js +41 -0
- package/dist/app/lifecycle.d.ts +14 -0
- package/dist/app/lifecycle.js +130 -0
- package/dist/auth/auth-service.d.ts +88 -0
- package/dist/auth/auth-service.js +221 -0
- package/dist/auth/ingress-policy.d.ts +23 -0
- package/dist/auth/ingress-policy.js +61 -0
- package/dist/auth/passkey-service.d.ts +39 -0
- package/dist/auth/passkey-service.js +145 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/run-cli.d.ts +1 -0
- package/dist/cli/run-cli.js +41 -0
- package/dist/config/load-config.d.ts +7 -0
- package/dist/config/load-config.js +197 -0
- package/dist/config/platform-paths.d.ts +7 -0
- package/dist/config/platform-paths.js +34 -0
- package/dist/config/types.d.ts +24 -0
- package/dist/config/types.js +1 -0
- package/dist/core/auth/passkey-repository.d.ts +18 -0
- package/dist/core/auth/passkey-repository.js +1 -0
- package/dist/core/auth/repository.d.ts +36 -0
- package/dist/core/auth/repository.js +1 -0
- package/dist/core/auth/session-repository.d.ts +6 -0
- package/dist/core/auth/session-repository.js +1 -0
- package/dist/core/errors/chatroom-error.d.ts +9 -0
- package/dist/core/errors/chatroom-error.js +31 -0
- package/dist/core/logging/types.d.ts +31 -0
- package/dist/core/logging/types.js +1 -0
- package/dist/core/operations/bounded-value.d.ts +6 -0
- package/dist/core/operations/bounded-value.js +57 -0
- package/dist/core/operations/redactor.d.ts +4 -0
- package/dist/core/operations/redactor.js +69 -0
- package/dist/core/operations/repository.d.ts +18 -0
- package/dist/core/operations/repository.js +1 -0
- package/dist/core/operations/types.d.ts +28 -0
- package/dist/core/operations/types.js +8 -0
- package/dist/core/runtime/child-environment.d.ts +1 -0
- package/dist/core/runtime/child-environment.js +39 -0
- package/dist/core/runtime/command-runner.d.ts +16 -0
- package/dist/core/runtime/command-runner.js +48 -0
- package/dist/core/runtime/identity.d.ts +2 -0
- package/dist/core/runtime/identity.js +9 -0
- package/dist/infrastructure/database/app-database.d.ts +6 -0
- package/dist/infrastructure/database/app-database.js +22 -0
- package/dist/infrastructure/database/mcp-tool-settings-repository.d.ts +8 -0
- package/dist/infrastructure/database/mcp-tool-settings-repository.js +22 -0
- package/dist/infrastructure/database/oauth-repository.d.ts +20 -0
- package/dist/infrastructure/database/oauth-repository.js +140 -0
- package/dist/infrastructure/database/operation-repository.d.ts +16 -0
- package/dist/infrastructure/database/operation-repository.js +120 -0
- package/dist/infrastructure/database/passkey-repository.d.ts +11 -0
- package/dist/infrastructure/database/passkey-repository.js +57 -0
- package/dist/infrastructure/database/schema.d.ts +1 -0
- package/dist/infrastructure/database/schema.js +90 -0
- package/dist/infrastructure/database/web-session-repository.d.ts +10 -0
- package/dist/infrastructure/database/web-session-repository.js +26 -0
- package/dist/infrastructure/http/http-server.d.ts +28 -0
- package/dist/infrastructure/http/http-server.js +146 -0
- package/dist/infrastructure/http/ingress-middleware.d.ts +6 -0
- package/dist/infrastructure/http/ingress-middleware.js +58 -0
- package/dist/infrastructure/logging/file-log-store.d.ts +9 -0
- package/dist/infrastructure/logging/file-log-store.js +173 -0
- package/dist/infrastructure/logging/system-log.d.ts +22 -0
- package/dist/infrastructure/logging/system-log.js +117 -0
- package/dist/mcp/server/create-mcp-server.d.ts +3 -0
- package/dist/mcp/server/create-mcp-server.js +16 -0
- package/dist/mcp/server/plugin-mcp-registrar.d.ts +31 -0
- package/dist/mcp/server/plugin-mcp-registrar.js +70 -0
- package/dist/mcp/server/request-context.d.ts +3 -0
- package/dist/mcp/server/request-context.js +8 -0
- package/dist/mcp/server/tool-control.d.ts +31 -0
- package/dist/mcp/server/tool-control.js +76 -0
- package/dist/mcp/server/tool-support.d.ts +44 -0
- package/dist/mcp/server/tool-support.js +72 -0
- package/dist/native/linux/chatroom-computer-helper +0 -0
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/Info.plist +14 -0
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/_CodeSignature/CodeResources +115 -0
- package/dist/native/windows/chatroom-computer-helper.exe +0 -0
- package/dist/operations/operation-log.d.ts +24 -0
- package/dist/operations/operation-log.js +98 -0
- package/dist/plugins/cloud/api-client.d.ts +50 -0
- package/dist/plugins/cloud/api-client.js +112 -0
- package/dist/plugins/cloud/controller.d.ts +58 -0
- package/dist/plugins/cloud/controller.js +425 -0
- package/dist/plugins/cloud/device-proof.d.ts +9 -0
- package/dist/plugins/cloud/device-proof.js +17 -0
- package/dist/plugins/cloud/plugin.d.ts +4 -0
- package/dist/plugins/cloud/plugin.js +17 -0
- package/dist/plugins/cloud/state-store.d.ts +7 -0
- package/dist/plugins/cloud/state-store.js +79 -0
- package/dist/plugins/cloud/tunnel-client.d.ts +84 -0
- package/dist/plugins/cloud/tunnel-client.js +427 -0
- package/dist/plugins/cloud/types.d.ts +72 -0
- package/dist/plugins/cloud/types.js +60 -0
- package/dist/plugins/computer/audit.d.ts +34 -0
- package/dist/plugins/computer/audit.js +42 -0
- package/dist/plugins/computer/computer-native-backend.d.ts +11 -0
- package/dist/plugins/computer/computer-native-backend.js +58 -0
- package/dist/plugins/computer/computer-native-host.d.ts +30 -0
- package/dist/plugins/computer/computer-native-host.js +411 -0
- package/dist/plugins/computer/computer-protocol.d.ts +22 -0
- package/dist/plugins/computer/computer-protocol.js +74 -0
- package/dist/plugins/computer/computer-schemas.d.ts +285 -0
- package/dist/plugins/computer/computer-schemas.js +152 -0
- package/dist/plugins/computer/computer-service.d.ts +29 -0
- package/dist/plugins/computer/computer-service.js +117 -0
- package/dist/plugins/computer/computer-settings-repository.d.ts +8 -0
- package/dist/plugins/computer/computer-settings-repository.js +41 -0
- package/dist/plugins/computer/mcp.d.ts +3 -0
- package/dist/plugins/computer/mcp.js +126 -0
- package/dist/plugins/computer/plugin.d.ts +4 -0
- package/dist/plugins/computer/plugin.js +29 -0
- package/dist/plugins/computer/types.d.ts +174 -0
- package/dist/plugins/computer/types.js +1 -0
- package/dist/plugins/git/git-service.d.ts +30 -0
- package/dist/plugins/git/git-service.js +428 -0
- package/dist/plugins/git/plugin.d.ts +4 -0
- package/dist/plugins/git/plugin.js +12 -0
- package/dist/plugins/git/types.d.ts +32 -0
- package/dist/plugins/git/types.js +1 -0
- package/dist/plugins/plugin-manager.d.ts +11 -0
- package/dist/plugins/plugin-manager.js +63 -0
- package/dist/plugins/process/backend.d.ts +18 -0
- package/dist/plugins/process/backend.js +1 -0
- package/dist/plugins/process/head-tail-buffer.d.ts +17 -0
- package/dist/plugins/process/head-tail-buffer.js +49 -0
- package/dist/plugins/process/infrastructure/pipe-process-backend.d.ts +8 -0
- package/dist/plugins/process/infrastructure/pipe-process-backend.js +71 -0
- package/dist/plugins/process/infrastructure/pty-process-backend.d.ts +8 -0
- package/dist/plugins/process/infrastructure/pty-process-backend.js +51 -0
- package/dist/plugins/process/mcp.d.ts +3 -0
- package/dist/plugins/process/mcp.js +58 -0
- package/dist/plugins/process/plugin.d.ts +4 -0
- package/dist/plugins/process/plugin.js +25 -0
- package/dist/plugins/process/process-supervisor.d.ts +39 -0
- package/dist/plugins/process/process-supervisor.js +244 -0
- package/dist/plugins/process/types.d.ts +30 -0
- package/dist/plugins/process/types.js +1 -0
- package/dist/plugins/types.d.ts +35 -0
- package/dist/plugins/types.js +17 -0
- package/dist/plugins/web/api-types.d.ts +56 -0
- package/dist/plugins/web/api-types.js +1 -0
- package/dist/plugins/web/http/api-router.d.ts +13 -0
- package/dist/plugins/web/http/api-router.js +47 -0
- package/dist/plugins/web/http/auth-api-router.d.ts +8 -0
- package/dist/plugins/web/http/auth-api-router.js +123 -0
- package/dist/plugins/web/http/cloud-api-router.d.ts +4 -0
- package/dist/plugins/web/http/cloud-api-router.js +29 -0
- package/dist/plugins/web/http/computer-api-router.d.ts +5 -0
- package/dist/plugins/web/http/computer-api-router.js +69 -0
- package/dist/plugins/web/http/git-api-router.d.ts +3 -0
- package/dist/plugins/web/http/git-api-router.js +85 -0
- package/dist/plugins/web/http/logs-api-router.d.ts +3 -0
- package/dist/plugins/web/http/logs-api-router.js +49 -0
- package/dist/plugins/web/http/process-api-router.d.ts +3 -0
- package/dist/plugins/web/http/process-api-router.js +16 -0
- package/dist/plugins/web/http/runtime-api-router.d.ts +10 -0
- package/dist/plugins/web/http/runtime-api-router.js +34 -0
- package/dist/plugins/web/http/workspace-api-router.d.ts +3 -0
- package/dist/plugins/web/http/workspace-api-router.js +91 -0
- package/dist/plugins/web/plugin.d.ts +8 -0
- package/dist/plugins/web/plugin.js +17 -0
- package/dist/plugins/web/runtime.d.ts +18 -0
- package/dist/plugins/web/runtime.js +31 -0
- package/dist/plugins/workspace/mcp.d.ts +3 -0
- package/dist/plugins/workspace/mcp.js +24 -0
- package/dist/plugins/workspace/metadata.d.ts +6 -0
- package/dist/plugins/workspace/metadata.js +111 -0
- package/dist/plugins/workspace/plugin.d.ts +4 -0
- package/dist/plugins/workspace/plugin.js +22 -0
- package/dist/plugins/workspace/types.d.ts +23 -0
- package/dist/plugins/workspace/types.js +1 -0
- package/dist/plugins/workspace/workspace-fs.d.ts +28 -0
- package/dist/plugins/workspace/workspace-fs.js +200 -0
- package/dist/plugins/workspace/workspace-service.d.ts +15 -0
- package/dist/plugins/workspace/workspace-service.js +140 -0
- package/dist/presentation/http/http-utils.d.ts +9 -0
- package/dist/presentation/http/http-utils.js +89 -0
- package/dist/presentation/http/oauth-router.d.ts +5 -0
- package/dist/presentation/http/oauth-router.js +172 -0
- package/dist/web/assets/CloudView-CHjVbp-5.css +1 -0
- package/dist/web/assets/CloudView-CvX0dkgu.js +1 -0
- package/dist/web/assets/CodeViewer-CH1DEJge.css +1 -0
- package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-B5TxGkUW.js +3 -0
- package/dist/web/assets/ComputerView-91Z9Cefw.css +1 -0
- package/dist/web/assets/ComputerView-OOyEqWml.js +1 -0
- package/dist/web/assets/McpToolsView-BTnolyS6.css +1 -0
- package/dist/web/assets/McpToolsView-DjXWTKrZ.js +1 -0
- package/dist/web/assets/OperationsView-DqTY51Cr.js +4 -0
- package/dist/web/assets/ProcessesView-C13BykIa.js +4 -0
- package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-DyOhJmR6.js +1 -0
- package/dist/web/assets/SystemLogsView-Ci0u4BTb.js +1 -0
- package/dist/web/assets/VSelect-CAxEESw-.css +1 -0
- package/dist/web/assets/VSelect-DrRpKyYI.js +1 -0
- package/dist/web/assets/VSpacer-C3wAY3F0.js +1 -0
- package/dist/web/assets/VSpacer-DfbUir7X.css +1 -0
- package/dist/web/assets/VSwitch-Cos8PNSP.css +1 -0
- package/dist/web/assets/VSwitch-CxusR4iO.js +1 -0
- package/dist/web/assets/VTabs-DSWgbwhc.css +1 -0
- package/dist/web/assets/VTabs-qBcuuXh3.js +1 -0
- package/dist/web/assets/WorkspacesView-BzQ0aH74.css +1 -0
- package/dist/web/assets/WorkspacesView-CijIvfsp.js +2 -0
- package/dist/web/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
- package/dist/web/assets/chatroom-B5p-5n3y.svg +15 -0
- package/dist/web/assets/index-BDkarAkX.js +2 -0
- package/dist/web/assets/index-BegKQ3Yd.js +19 -0
- package/dist/web/assets/index-CkjqXRSj.css +1 -0
- package/dist/web/assets/syntax-highlight-B5ArnyrC.js +3 -0
- package/dist/web/assets/useOperationLabels-BDjzGDkT.js +1 -0
- package/dist/web/assets/utils-rtPwWNel.js +1 -0
- package/dist/web/index.html +19 -0
- package/package.json +125 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const CLOUD_SERVICES = ["remote_mcp", "remote_web"];
|
|
3
|
+
export const CLOUD_HTTP_URL_SCHEMA = z
|
|
4
|
+
.string()
|
|
5
|
+
.url()
|
|
6
|
+
.refine((value) => {
|
|
7
|
+
const protocol = new URL(value).protocol;
|
|
8
|
+
return protocol === "http:" || protocol === "https:";
|
|
9
|
+
}, "URL must use HTTP or HTTPS");
|
|
10
|
+
export const CLOUD_WEBSOCKET_URL_SCHEMA = z
|
|
11
|
+
.string()
|
|
12
|
+
.url()
|
|
13
|
+
.refine((value) => {
|
|
14
|
+
const protocol = new URL(value).protocol;
|
|
15
|
+
return protocol === "ws:" || protocol === "wss:";
|
|
16
|
+
}, "URL must use WS or WSS");
|
|
17
|
+
export const CLOUD_SERVICE_SCHEMA = z.enum(CLOUD_SERVICES);
|
|
18
|
+
const PUBLIC_SERVICE_TO_CLOUD_SERVICE = {
|
|
19
|
+
mcp: "remote_mcp",
|
|
20
|
+
web: "remote_web",
|
|
21
|
+
};
|
|
22
|
+
export const CLOUD_DESIRED_SERVICES_SCHEMA = z
|
|
23
|
+
.object({ remote_mcp: z.boolean(), remote_web: z.boolean() })
|
|
24
|
+
.strict();
|
|
25
|
+
export function cloudServiceForPublicService(service) {
|
|
26
|
+
return PUBLIC_SERVICE_TO_CLOUD_SERVICE[service];
|
|
27
|
+
}
|
|
28
|
+
export const CLOUD_ENTITLEMENT_SCHEMA = z
|
|
29
|
+
.object({
|
|
30
|
+
service: CLOUD_SERVICE_SCHEMA,
|
|
31
|
+
status: z.literal("active"),
|
|
32
|
+
sourceProvider: z.string().min(1),
|
|
33
|
+
sourceId: z.string().min(1),
|
|
34
|
+
validUntil: z.string().datetime().nullable(),
|
|
35
|
+
})
|
|
36
|
+
.strict();
|
|
37
|
+
export const CLOUD_LEASE_SCHEMA = z
|
|
38
|
+
.object({
|
|
39
|
+
token: z.string().min(1),
|
|
40
|
+
expiresAt: z.string().datetime(),
|
|
41
|
+
tunnelUrl: CLOUD_WEBSOCKET_URL_SCHEMA,
|
|
42
|
+
mcpBaseUrl: CLOUD_HTTP_URL_SCHEMA.nullable(),
|
|
43
|
+
webBaseUrl: CLOUD_HTTP_URL_SCHEMA.nullable(),
|
|
44
|
+
services: z.array(CLOUD_SERVICE_SCHEMA),
|
|
45
|
+
})
|
|
46
|
+
.strict()
|
|
47
|
+
.superRefine((value, context) => {
|
|
48
|
+
if (value.services.includes("remote_mcp") && !value.mcpBaseUrl)
|
|
49
|
+
context.addIssue({
|
|
50
|
+
code: "custom",
|
|
51
|
+
path: ["mcpBaseUrl"],
|
|
52
|
+
message: "remote_mcp lease requires mcpBaseUrl",
|
|
53
|
+
});
|
|
54
|
+
if (value.services.includes("remote_web") && !value.webBaseUrl)
|
|
55
|
+
context.addIssue({
|
|
56
|
+
code: "custom",
|
|
57
|
+
path: ["webBaseUrl"],
|
|
58
|
+
message: "remote_web lease requires webBaseUrl",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ComputerAction, ComputerActionResult, ComputerSnapshot } from "./types.js";
|
|
2
|
+
export declare function auditComputerActions(input: {
|
|
3
|
+
snapshotId?: string;
|
|
4
|
+
actions: ComputerAction[];
|
|
5
|
+
observeAfter?: boolean;
|
|
6
|
+
}): {
|
|
7
|
+
snapshotId: string | null;
|
|
8
|
+
observeAfter: boolean;
|
|
9
|
+
actions: unknown[];
|
|
10
|
+
};
|
|
11
|
+
export declare function auditSnapshotOutput(value: ComputerSnapshot): {
|
|
12
|
+
snapshotId: string;
|
|
13
|
+
revision: number;
|
|
14
|
+
display: import("./types.js").ComputerDisplay | null;
|
|
15
|
+
activeApp: string | null;
|
|
16
|
+
activeWindow: string | null;
|
|
17
|
+
elementCount: number;
|
|
18
|
+
screenshot: boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function auditActionOutput(value: ComputerActionResult): {
|
|
21
|
+
success: true;
|
|
22
|
+
revision: number;
|
|
23
|
+
executionMode: "semantic" | "background" | "foreground" | "mixed";
|
|
24
|
+
focusChanged: boolean;
|
|
25
|
+
snapshot: {
|
|
26
|
+
snapshotId: string;
|
|
27
|
+
revision: number;
|
|
28
|
+
display: import("./types.js").ComputerDisplay | null;
|
|
29
|
+
activeApp: string | null;
|
|
30
|
+
activeWindow: string | null;
|
|
31
|
+
elementCount: number;
|
|
32
|
+
screenshot: boolean;
|
|
33
|
+
} | null;
|
|
34
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function auditComputerActions(input) {
|
|
2
|
+
return {
|
|
3
|
+
snapshotId: input.snapshotId ?? null,
|
|
4
|
+
observeAfter: input.observeAfter ?? true,
|
|
5
|
+
actions: input.actions.map((action) => sanitizeAction(action)),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function sanitizeAction(action) {
|
|
9
|
+
if (action.type === "type_text")
|
|
10
|
+
return {
|
|
11
|
+
type: action.type,
|
|
12
|
+
characters: action.text.length,
|
|
13
|
+
elementId: action.elementId ?? null,
|
|
14
|
+
};
|
|
15
|
+
if (action.type === "set_value")
|
|
16
|
+
return {
|
|
17
|
+
type: action.type,
|
|
18
|
+
characters: action.value.length,
|
|
19
|
+
elementId: action.elementId,
|
|
20
|
+
};
|
|
21
|
+
return action;
|
|
22
|
+
}
|
|
23
|
+
export function auditSnapshotOutput(value) {
|
|
24
|
+
return {
|
|
25
|
+
snapshotId: value.snapshotId,
|
|
26
|
+
revision: value.revision,
|
|
27
|
+
display: value.display,
|
|
28
|
+
activeApp: value.activeApp,
|
|
29
|
+
activeWindow: value.activeWindow,
|
|
30
|
+
elementCount: value.elements.length,
|
|
31
|
+
screenshot: Boolean(value.screenshot),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function auditActionOutput(value) {
|
|
35
|
+
return {
|
|
36
|
+
success: value.success,
|
|
37
|
+
revision: value.revision,
|
|
38
|
+
executionMode: value.executionMode,
|
|
39
|
+
focusChanged: value.focusChanged,
|
|
40
|
+
snapshot: value.snapshot ? auditSnapshotOutput(value.snapshot) : null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComputerActionRequest, ComputerActionResult, ComputerBackend, ComputerPermission, ComputerSnapshot, ComputerSnapshotRequest, ComputerStatus } from "./types.js";
|
|
2
|
+
export declare class NativeComputerBackend implements ComputerBackend {
|
|
3
|
+
private readonly host;
|
|
4
|
+
private readonly restartWhenGranted;
|
|
5
|
+
status(): Promise<Omit<ComputerStatus, "settings">>;
|
|
6
|
+
requestPermission(permission: ComputerPermission): Promise<Omit<ComputerStatus, "settings">>;
|
|
7
|
+
snapshot(request: ComputerSnapshotRequest, revision: number): Promise<ComputerSnapshot>;
|
|
8
|
+
action(request: ComputerActionRequest, revision: number): Promise<ComputerActionResult>;
|
|
9
|
+
dispose(): Promise<void>;
|
|
10
|
+
private readStatus;
|
|
11
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ChatRoomError } from "#core/errors/chatroom-error";
|
|
2
|
+
import { ComputerNativeHost } from "./computer-native-host.js";
|
|
3
|
+
import { parseNativeResult } from "./computer-protocol.js";
|
|
4
|
+
export class NativeComputerBackend {
|
|
5
|
+
host = new ComputerNativeHost();
|
|
6
|
+
restartWhenGranted = new Set();
|
|
7
|
+
async status() {
|
|
8
|
+
const value = await this.readStatus().catch(() => ({
|
|
9
|
+
platform: this.host.platform,
|
|
10
|
+
helper: "unavailable",
|
|
11
|
+
permissions: {
|
|
12
|
+
accessibility: "unknown",
|
|
13
|
+
screenRecording: "unknown",
|
|
14
|
+
},
|
|
15
|
+
displays: [],
|
|
16
|
+
}));
|
|
17
|
+
if (this.host.platform !== "macos" || value.helper !== "running")
|
|
18
|
+
return value;
|
|
19
|
+
const newlyGranted = [...this.restartWhenGranted].filter((permission) => value.permissions[permission] === "granted");
|
|
20
|
+
if (!newlyGranted.length || !this.host.idle)
|
|
21
|
+
return value;
|
|
22
|
+
for (const permission of newlyGranted)
|
|
23
|
+
this.restartWhenGranted.delete(permission);
|
|
24
|
+
this.host.restart("Computer helper restarting after permission change");
|
|
25
|
+
return this.readStatus();
|
|
26
|
+
}
|
|
27
|
+
async requestPermission(permission) {
|
|
28
|
+
if (this.host.platform !== "macos")
|
|
29
|
+
throw new ChatRoomError("UNSUPPORTED", "Permission requests are currently supported only on macOS");
|
|
30
|
+
const result = await this.host.request("requestPermission", { permission });
|
|
31
|
+
const value = parseNativeResult("requestPermission", result);
|
|
32
|
+
if (value.permissions[permission] !== "granted") {
|
|
33
|
+
this.restartWhenGranted.add(permission);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
this.restartWhenGranted.delete(permission);
|
|
37
|
+
this.host.restart("Computer helper restarting after permission change");
|
|
38
|
+
return this.readStatus();
|
|
39
|
+
}
|
|
40
|
+
async snapshot(request, revision) {
|
|
41
|
+
const result = await this.host.request("snapshot", {
|
|
42
|
+
...request,
|
|
43
|
+
revision,
|
|
44
|
+
});
|
|
45
|
+
return parseNativeResult("snapshot", result);
|
|
46
|
+
}
|
|
47
|
+
async action(request, revision) {
|
|
48
|
+
const result = await this.host.request("action", { ...request, revision });
|
|
49
|
+
return parseNativeResult("action", result);
|
|
50
|
+
}
|
|
51
|
+
async dispose() {
|
|
52
|
+
this.restartWhenGranted.clear();
|
|
53
|
+
await this.host.dispose();
|
|
54
|
+
}
|
|
55
|
+
async readStatus() {
|
|
56
|
+
return parseNativeResult("status", await this.host.request("status", {}));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ComputerNativeMethod } from "./computer-protocol.js";
|
|
2
|
+
import type { ComputerPlatform } from "./types.js";
|
|
3
|
+
export declare class ComputerNativeHost {
|
|
4
|
+
private child;
|
|
5
|
+
private socket;
|
|
6
|
+
private socketServer;
|
|
7
|
+
private socketPath;
|
|
8
|
+
private lines;
|
|
9
|
+
private starting;
|
|
10
|
+
private cancelStarting;
|
|
11
|
+
private readonly pending;
|
|
12
|
+
private sequence;
|
|
13
|
+
private generation;
|
|
14
|
+
get platform(): ComputerPlatform;
|
|
15
|
+
get idle(): boolean;
|
|
16
|
+
request(method: ComputerNativeMethod, params: unknown): Promise<unknown>;
|
|
17
|
+
restart(reason?: string): void;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
private ensureStarted;
|
|
20
|
+
private startHelper;
|
|
21
|
+
private startMacHelper;
|
|
22
|
+
private attachSocket;
|
|
23
|
+
private startPipeHelper;
|
|
24
|
+
private failTransport;
|
|
25
|
+
private reset;
|
|
26
|
+
private rejectRequest;
|
|
27
|
+
private rejectPending;
|
|
28
|
+
private cleanupSocketPath;
|
|
29
|
+
private handleLine;
|
|
30
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { spawn, spawnSync, } from "node:child_process";
|
|
3
|
+
import { chmodSync, existsSync, rmSync } from "node:fs";
|
|
4
|
+
import { createServer } from "node:net";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { ChatRoomError } from "#core/errors/chatroom-error";
|
|
9
|
+
import { COMPUTER_NATIVE_PROTOCOL_VERSION, nativeError, parseNativeEnvelope, } from "./computer-protocol.js";
|
|
10
|
+
export class ComputerNativeHost {
|
|
11
|
+
child = null;
|
|
12
|
+
socket = null;
|
|
13
|
+
socketServer = null;
|
|
14
|
+
socketPath = null;
|
|
15
|
+
lines = null;
|
|
16
|
+
starting = null;
|
|
17
|
+
cancelStarting = null;
|
|
18
|
+
pending = new Map();
|
|
19
|
+
sequence = 0;
|
|
20
|
+
generation = 0;
|
|
21
|
+
get platform() {
|
|
22
|
+
if (process.platform === "darwin")
|
|
23
|
+
return "macos";
|
|
24
|
+
if (process.platform === "win32")
|
|
25
|
+
return "windows";
|
|
26
|
+
if (process.platform === "linux")
|
|
27
|
+
return "linux";
|
|
28
|
+
return "unsupported";
|
|
29
|
+
}
|
|
30
|
+
get idle() {
|
|
31
|
+
return this.pending.size === 0;
|
|
32
|
+
}
|
|
33
|
+
async request(method, params) {
|
|
34
|
+
await this.ensureStarted();
|
|
35
|
+
const id = `computer_${++this.sequence}`;
|
|
36
|
+
const timeoutMs = requestTimeoutMs(method);
|
|
37
|
+
const promise = new Promise((resolve, reject) => {
|
|
38
|
+
const timer = setTimeout(() => {
|
|
39
|
+
this.pending.delete(id);
|
|
40
|
+
const error = new ChatRoomError("INTERNAL", `Computer helper ${method} request timed out after ${timeoutMs} ms`);
|
|
41
|
+
reject(error);
|
|
42
|
+
this.reset(error);
|
|
43
|
+
}, timeoutMs);
|
|
44
|
+
timer.unref();
|
|
45
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
46
|
+
});
|
|
47
|
+
const line = `${JSON.stringify({
|
|
48
|
+
protocol: COMPUTER_NATIVE_PROTOCOL_VERSION,
|
|
49
|
+
id,
|
|
50
|
+
method,
|
|
51
|
+
params,
|
|
52
|
+
})}\n`;
|
|
53
|
+
const transportError = new ChatRoomError("INTERNAL", "Computer helper transport is unavailable");
|
|
54
|
+
if (this.platform === "macos") {
|
|
55
|
+
const socket = this.socket;
|
|
56
|
+
if (!socket || socket.destroyed)
|
|
57
|
+
this.rejectRequest(id, transportError);
|
|
58
|
+
else
|
|
59
|
+
socket.write(line);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const child = this.child;
|
|
63
|
+
if (!child || child.killed || child.stdin.destroyed)
|
|
64
|
+
this.rejectRequest(id, transportError);
|
|
65
|
+
else
|
|
66
|
+
child.stdin.write(line);
|
|
67
|
+
}
|
|
68
|
+
return promise;
|
|
69
|
+
}
|
|
70
|
+
restart(reason = "Computer helper restarting") {
|
|
71
|
+
this.reset(new Error(reason));
|
|
72
|
+
}
|
|
73
|
+
async dispose() {
|
|
74
|
+
this.reset(new Error("Computer helper stopped"));
|
|
75
|
+
}
|
|
76
|
+
async ensureStarted() {
|
|
77
|
+
const platform = this.platform;
|
|
78
|
+
if (platform === "macos" && this.socket && !this.socket.destroyed)
|
|
79
|
+
return;
|
|
80
|
+
if ((platform === "windows" || platform === "linux") &&
|
|
81
|
+
this.child &&
|
|
82
|
+
!this.child.killed)
|
|
83
|
+
return;
|
|
84
|
+
if (this.starting)
|
|
85
|
+
return this.starting;
|
|
86
|
+
const generation = this.generation;
|
|
87
|
+
const starting = this.startHelper(platform, generation);
|
|
88
|
+
this.starting = starting;
|
|
89
|
+
try {
|
|
90
|
+
await starting;
|
|
91
|
+
if (generation !== this.generation)
|
|
92
|
+
throw new Error("Computer helper start was superseded");
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
if (this.starting === starting)
|
|
96
|
+
this.starting = null;
|
|
97
|
+
if (generation === this.generation)
|
|
98
|
+
this.cancelStarting = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
startHelper(platform, generation) {
|
|
102
|
+
switch (platform) {
|
|
103
|
+
case "macos":
|
|
104
|
+
return this.startMacHelper(generation);
|
|
105
|
+
case "windows":
|
|
106
|
+
return this.startPipeHelper(windowsHelperPath(), "Windows", process.env, generation);
|
|
107
|
+
case "linux":
|
|
108
|
+
return this.startPipeHelper(linuxHelperPath(), "Linux X11", linuxDesktopEnvironment(), generation);
|
|
109
|
+
case "unsupported":
|
|
110
|
+
return Promise.reject(new ChatRoomError("UNSUPPORTED", "Computer Use is supported only on macOS, Windows, and Linux X11"));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async startMacHelper(generation) {
|
|
114
|
+
const app = macHelperAppPath();
|
|
115
|
+
if (!app)
|
|
116
|
+
throw new ChatRoomError("UNSUPPORTED", "macOS Computer helper is missing");
|
|
117
|
+
this.cleanupSocketPath();
|
|
118
|
+
const socketPath = path.join("/tmp", `chatroom-c-${process.pid}-${randomUUID().slice(0, 8)}.sock`);
|
|
119
|
+
this.socketPath = socketPath;
|
|
120
|
+
await new Promise((resolve, reject) => {
|
|
121
|
+
const server = createServer();
|
|
122
|
+
this.socketServer = server;
|
|
123
|
+
let settled = false;
|
|
124
|
+
const timer = setTimeout(() => fail(new Error("Computer helper connection timed out")), 10_000);
|
|
125
|
+
timer.unref();
|
|
126
|
+
const cleanupServer = () => {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
if (this.socketServer === server)
|
|
129
|
+
this.socketServer = null;
|
|
130
|
+
if (this.cancelStarting === fail)
|
|
131
|
+
this.cancelStarting = null;
|
|
132
|
+
if (server.listening)
|
|
133
|
+
server.close();
|
|
134
|
+
};
|
|
135
|
+
const fail = (error) => {
|
|
136
|
+
if (settled)
|
|
137
|
+
return;
|
|
138
|
+
settled = true;
|
|
139
|
+
cleanupServer();
|
|
140
|
+
this.cleanupSocketPath(socketPath);
|
|
141
|
+
reject(error);
|
|
142
|
+
};
|
|
143
|
+
this.cancelStarting = fail;
|
|
144
|
+
server.once("error", fail);
|
|
145
|
+
server.once("connection", (socket) => {
|
|
146
|
+
if (settled || generation !== this.generation) {
|
|
147
|
+
socket.destroy();
|
|
148
|
+
if (!settled)
|
|
149
|
+
fail(new Error("Computer helper start was superseded"));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
settled = true;
|
|
153
|
+
server.removeListener("error", fail);
|
|
154
|
+
cleanupServer();
|
|
155
|
+
this.attachSocket(socket);
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
server.listen(socketPath, () => {
|
|
159
|
+
if (generation !== this.generation) {
|
|
160
|
+
fail(new Error("Computer helper start was superseded"));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
chmodSync(socketPath, 0o600);
|
|
164
|
+
const launcher = spawn("/usr/bin/open", ["-n", "-g", app, "--args", "--connect", socketPath], { stdio: "ignore" });
|
|
165
|
+
launcher.once("error", fail);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
attachSocket(socket) {
|
|
170
|
+
this.socket = socket;
|
|
171
|
+
this.lines = createInterface({ input: socket });
|
|
172
|
+
this.lines.on("line", (line) => this.handleLine(line));
|
|
173
|
+
socket.once("error", (error) => this.failTransport(socket, error));
|
|
174
|
+
socket.once("close", () => this.failTransport(socket, new Error("Computer helper exited")));
|
|
175
|
+
}
|
|
176
|
+
async startPipeHelper(executable, platformName, env, generation) {
|
|
177
|
+
if (!executable)
|
|
178
|
+
throw new ChatRoomError("UNSUPPORTED", `${platformName} Computer helper is missing`);
|
|
179
|
+
const child = spawn(executable, [], {
|
|
180
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
181
|
+
windowsHide: process.platform === "win32",
|
|
182
|
+
env,
|
|
183
|
+
});
|
|
184
|
+
this.child = child;
|
|
185
|
+
this.lines = createInterface({ input: child.stdout });
|
|
186
|
+
this.lines.on("line", (line) => this.handleLine(line));
|
|
187
|
+
child.stderr.on("data", (chunk) => console.error("[computer-helper]", String(chunk).trimEnd()));
|
|
188
|
+
child.once("error", (error) => this.failTransport(child, error));
|
|
189
|
+
child.once("exit", () => this.failTransport(child, new Error("Computer helper exited")));
|
|
190
|
+
await waitForSpawn(child);
|
|
191
|
+
if (generation !== this.generation || this.child !== child) {
|
|
192
|
+
child.kill();
|
|
193
|
+
throw new Error("Computer helper start was superseded");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
failTransport(transport, error) {
|
|
197
|
+
const isCurrentSocket = transport === this.socket;
|
|
198
|
+
const isCurrentChild = transport === this.child;
|
|
199
|
+
if (!isCurrentSocket && !isCurrentChild)
|
|
200
|
+
return;
|
|
201
|
+
this.generation += 1;
|
|
202
|
+
const cancelStarting = this.cancelStarting;
|
|
203
|
+
this.cancelStarting = null;
|
|
204
|
+
cancelStarting?.(error);
|
|
205
|
+
if (isCurrentSocket)
|
|
206
|
+
this.socket = null;
|
|
207
|
+
if (isCurrentChild)
|
|
208
|
+
this.child = null;
|
|
209
|
+
this.lines?.close();
|
|
210
|
+
this.lines = null;
|
|
211
|
+
this.cleanupSocketPath();
|
|
212
|
+
this.rejectPending(error);
|
|
213
|
+
}
|
|
214
|
+
reset(error) {
|
|
215
|
+
this.generation += 1;
|
|
216
|
+
const cancelStarting = this.cancelStarting;
|
|
217
|
+
this.cancelStarting = null;
|
|
218
|
+
cancelStarting?.(error);
|
|
219
|
+
this.lines?.close();
|
|
220
|
+
this.lines = null;
|
|
221
|
+
this.socket?.destroy();
|
|
222
|
+
this.socket = null;
|
|
223
|
+
this.socketServer?.close();
|
|
224
|
+
this.socketServer = null;
|
|
225
|
+
this.child?.kill();
|
|
226
|
+
this.child = null;
|
|
227
|
+
this.cleanupSocketPath();
|
|
228
|
+
this.rejectPending(error);
|
|
229
|
+
}
|
|
230
|
+
rejectRequest(id, error) {
|
|
231
|
+
const item = this.pending.get(id);
|
|
232
|
+
if (!item)
|
|
233
|
+
return;
|
|
234
|
+
this.pending.delete(id);
|
|
235
|
+
clearTimeout(item.timer);
|
|
236
|
+
item.reject(error);
|
|
237
|
+
}
|
|
238
|
+
rejectPending(error) {
|
|
239
|
+
for (const item of this.pending.values()) {
|
|
240
|
+
clearTimeout(item.timer);
|
|
241
|
+
item.reject(error);
|
|
242
|
+
}
|
|
243
|
+
this.pending.clear();
|
|
244
|
+
}
|
|
245
|
+
cleanupSocketPath(target = this.socketPath) {
|
|
246
|
+
if (!target)
|
|
247
|
+
return;
|
|
248
|
+
rmSync(target, { force: true });
|
|
249
|
+
if (this.socketPath === target)
|
|
250
|
+
this.socketPath = null;
|
|
251
|
+
}
|
|
252
|
+
handleLine(line) {
|
|
253
|
+
let raw;
|
|
254
|
+
try {
|
|
255
|
+
raw = JSON.parse(line);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const rawId = raw && typeof raw === "object" && "id" in raw
|
|
261
|
+
? raw.id
|
|
262
|
+
: undefined;
|
|
263
|
+
if (typeof rawId !== "string")
|
|
264
|
+
return;
|
|
265
|
+
const pending = this.pending.get(rawId);
|
|
266
|
+
if (!pending)
|
|
267
|
+
return;
|
|
268
|
+
let message;
|
|
269
|
+
try {
|
|
270
|
+
message = parseNativeEnvelope(raw);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.pending.delete(rawId);
|
|
274
|
+
clearTimeout(pending.timer);
|
|
275
|
+
pending.reject(error);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.pending.delete(message.id);
|
|
279
|
+
clearTimeout(pending.timer);
|
|
280
|
+
if (message.error)
|
|
281
|
+
pending.reject(nativeError(message.error));
|
|
282
|
+
else
|
|
283
|
+
pending.resolve(message.result);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function waitForSpawn(child) {
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
const onSpawn = () => {
|
|
289
|
+
cleanup();
|
|
290
|
+
resolve();
|
|
291
|
+
};
|
|
292
|
+
const onError = (error) => {
|
|
293
|
+
cleanup();
|
|
294
|
+
reject(error);
|
|
295
|
+
};
|
|
296
|
+
const onExit = () => {
|
|
297
|
+
cleanup();
|
|
298
|
+
reject(new Error("Computer helper exited before startup"));
|
|
299
|
+
};
|
|
300
|
+
const cleanup = () => {
|
|
301
|
+
child.off("spawn", onSpawn);
|
|
302
|
+
child.off("error", onError);
|
|
303
|
+
child.off("exit", onExit);
|
|
304
|
+
};
|
|
305
|
+
child.once("spawn", onSpawn);
|
|
306
|
+
child.once("error", onError);
|
|
307
|
+
child.once("exit", onExit);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function requestTimeoutMs(method) {
|
|
311
|
+
switch (method) {
|
|
312
|
+
case "status":
|
|
313
|
+
return 5_000;
|
|
314
|
+
case "snapshot":
|
|
315
|
+
return 15_000;
|
|
316
|
+
case "requestPermission":
|
|
317
|
+
return 30_000;
|
|
318
|
+
case "action":
|
|
319
|
+
return 60_000;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function projectRoot() {
|
|
323
|
+
const current = fileURLToPath(import.meta.url);
|
|
324
|
+
return path.resolve(path.dirname(current), "../../..");
|
|
325
|
+
}
|
|
326
|
+
function macHelperAppPath() {
|
|
327
|
+
return nativeHelperPath("macos", "ChatRoomComputerHelper.app");
|
|
328
|
+
}
|
|
329
|
+
function windowsHelperPath() {
|
|
330
|
+
return nativeHelperPath("windows", "chatroom-computer-helper.exe");
|
|
331
|
+
}
|
|
332
|
+
function linuxHelperPath() {
|
|
333
|
+
return nativeHelperPath("linux", "chatroom-computer-helper");
|
|
334
|
+
}
|
|
335
|
+
function nativeHelperPath(...parts) {
|
|
336
|
+
const target = path.join(projectRoot(), "dist", "native", ...parts);
|
|
337
|
+
return existsSync(target) ? target : null;
|
|
338
|
+
}
|
|
339
|
+
function linuxDesktopEnvironment() {
|
|
340
|
+
const env = { ...process.env };
|
|
341
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
342
|
+
if (!env.XDG_RUNTIME_DIR && uid !== null) {
|
|
343
|
+
const runtimeDir = `/run/user/${uid}`;
|
|
344
|
+
if (existsSync(runtimeDir))
|
|
345
|
+
env.XDG_RUNTIME_DIR = runtimeDir;
|
|
346
|
+
}
|
|
347
|
+
if (!env.DISPLAY) {
|
|
348
|
+
const display = activeX11Display(uid);
|
|
349
|
+
if (display) {
|
|
350
|
+
env.DISPLAY = display;
|
|
351
|
+
env.XDG_SESSION_TYPE = "x11";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!env.XAUTHORITY) {
|
|
355
|
+
const candidates = [
|
|
356
|
+
env.HOME ? path.join(env.HOME, ".Xauthority") : null,
|
|
357
|
+
env.XDG_RUNTIME_DIR
|
|
358
|
+
? path.join(env.XDG_RUNTIME_DIR, "gdm", "Xauthority")
|
|
359
|
+
: null,
|
|
360
|
+
];
|
|
361
|
+
const authority = candidates.find((candidate) => !!candidate && existsSync(candidate));
|
|
362
|
+
if (authority)
|
|
363
|
+
env.XAUTHORITY = authority;
|
|
364
|
+
}
|
|
365
|
+
if (!env.DBUS_SESSION_BUS_ADDRESS && env.XDG_RUNTIME_DIR) {
|
|
366
|
+
const bus = path.join(env.XDG_RUNTIME_DIR, "bus");
|
|
367
|
+
if (existsSync(bus))
|
|
368
|
+
env.DBUS_SESSION_BUS_ADDRESS = `unix:path=${bus}`;
|
|
369
|
+
}
|
|
370
|
+
return env;
|
|
371
|
+
}
|
|
372
|
+
function activeX11Display(uid) {
|
|
373
|
+
if (process.platform !== "linux" || uid === null)
|
|
374
|
+
return null;
|
|
375
|
+
const loginctl = existsSync("/usr/bin/loginctl")
|
|
376
|
+
? "/usr/bin/loginctl"
|
|
377
|
+
: "loginctl";
|
|
378
|
+
const sessions = spawnSync(loginctl, ["list-sessions", "--no-legend", "--no-pager"], {
|
|
379
|
+
encoding: "utf8",
|
|
380
|
+
timeout: 2_000,
|
|
381
|
+
});
|
|
382
|
+
if (sessions.status !== 0 || !sessions.stdout)
|
|
383
|
+
return null;
|
|
384
|
+
const userId = String(uid);
|
|
385
|
+
for (const line of sessions.stdout.split("\n")) {
|
|
386
|
+
const [sessionId, sessionUid] = line.trim().split(/\s+/, 3);
|
|
387
|
+
if (!sessionId || sessionUid !== userId)
|
|
388
|
+
continue;
|
|
389
|
+
const details = spawnSync(loginctl, [
|
|
390
|
+
"show-session",
|
|
391
|
+
sessionId,
|
|
392
|
+
"--no-pager",
|
|
393
|
+
"-p",
|
|
394
|
+
"Type",
|
|
395
|
+
"-p",
|
|
396
|
+
"Active",
|
|
397
|
+
"-p",
|
|
398
|
+
"Display",
|
|
399
|
+
], { encoding: "utf8", timeout: 2_000 });
|
|
400
|
+
if (details.status !== 0 || !details.stdout)
|
|
401
|
+
continue;
|
|
402
|
+
const values = Object.fromEntries(details.stdout
|
|
403
|
+
.split("\n")
|
|
404
|
+
.map((item) => item.split("=", 2))
|
|
405
|
+
.filter((item) => item.length === 2));
|
|
406
|
+
if (values.Type === "x11" && values.Active === "yes" && values.Display) {
|
|
407
|
+
return values.Display;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ChatRoomError } from "#core/errors/chatroom-error";
|
|
2
|
+
import type { ComputerActionResult, ComputerSnapshot, ComputerStatus } from "./types.js";
|
|
3
|
+
export declare const COMPUTER_NATIVE_PROTOCOL_VERSION = 1;
|
|
4
|
+
export type ComputerNativeMethod = "status" | "requestPermission" | "snapshot" | "action";
|
|
5
|
+
interface ComputerNativeResultMap {
|
|
6
|
+
status: Omit<ComputerStatus, "settings">;
|
|
7
|
+
requestPermission: Omit<ComputerStatus, "settings">;
|
|
8
|
+
snapshot: ComputerSnapshot;
|
|
9
|
+
action: ComputerActionResult;
|
|
10
|
+
}
|
|
11
|
+
interface ComputerNativeError {
|
|
12
|
+
code?: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseNativeEnvelope(value: unknown): {
|
|
16
|
+
id: string;
|
|
17
|
+
result?: unknown;
|
|
18
|
+
error?: ComputerNativeError;
|
|
19
|
+
};
|
|
20
|
+
export declare function parseNativeResult<M extends ComputerNativeMethod>(method: M, value: unknown): ComputerNativeResultMap[M];
|
|
21
|
+
export declare function nativeError(error: ComputerNativeError): ChatRoomError;
|
|
22
|
+
export {};
|