@chatroomcp/chatroom 0.2.1 → 0.2.2
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/README.md +1 -1
- package/dist/app/application.d.ts +0 -1
- package/dist/app/application.js +2 -0
- package/dist/app/event-bus.d.ts +0 -2
- package/dist/app/event-bus.js +0 -1
- package/dist/app/external-access-registry.d.ts +0 -1
- package/dist/app/external-access-registry.js +0 -1
- package/dist/app/lifecycle.d.ts +0 -1
- package/dist/auth/auth-service.js +0 -4
- package/dist/auth/ingress-policy.d.ts +0 -1
- package/dist/auth/ingress-policy.js +0 -1
- package/dist/cli/run-cli.js +0 -1
- package/dist/config/types.d.ts +0 -1
- package/dist/core/auth/repository.d.ts +0 -23
- package/dist/core/auth/repository.js +0 -6
- package/dist/core/errors/chatroom-error.d.ts +0 -15
- package/dist/core/errors/chatroom-error.js +0 -15
- package/dist/core/operations/bounded-value.d.ts +0 -1
- package/dist/core/operations/bounded-value.js +26 -10
- package/dist/core/operations/redactor.js +0 -1
- package/dist/core/operations/repository.d.ts +0 -1
- package/dist/core/operations/types.d.ts +0 -4
- package/dist/core/operations/types.js +0 -2
- package/dist/infrastructure/database/app-database.js +4 -97
- package/dist/infrastructure/database/migrations.d.ts +2 -0
- package/dist/infrastructure/database/migrations.js +44 -0
- package/dist/infrastructure/database/operation-repository.js +3 -9
- package/dist/infrastructure/database/schema.d.ts +2 -0
- package/dist/infrastructure/database/schema.js +85 -0
- package/dist/infrastructure/http/http-server.js +0 -1
- package/dist/mcp/server/create-mcp-server.js +3 -3
- package/dist/mcp/server/plugin-mcp-registrar.d.ts +0 -2
- package/dist/mcp/server/plugin-mcp-registrar.js +0 -4
- package/dist/mcp/server/tool-support.d.ts +0 -61
- package/dist/mcp/server/tool-support.js +0 -54
- package/dist/native/linux/chatroom-computer-helper +0 -0
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/Info.plist +1 -1
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
- package/dist/native/windows/chatroom-computer-helper.exe +0 -0
- package/dist/operations/operation-log.d.ts +0 -3
- package/dist/operations/operation-log.js +0 -15
- package/dist/plugins/cloud/api-client.js +1 -0
- package/dist/plugins/cloud/controller.d.ts +2 -0
- package/dist/plugins/cloud/controller.js +28 -5
- package/dist/plugins/cloud/tunnel-client.js +0 -4
- package/dist/plugins/git/git-service.d.ts +33 -0
- package/dist/plugins/git/git-service.js +414 -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/process/backend.d.ts +0 -1
- package/dist/plugins/process/head-tail-buffer.d.ts +0 -1
- package/dist/plugins/process/infrastructure/pipe-process-backend.js +0 -1
- package/dist/plugins/process/infrastructure/pty-process-backend.js +1 -4
- package/dist/plugins/process/process-supervisor.js +10 -23
- package/dist/plugins/process/types.d.ts +0 -5
- package/dist/plugins/types.d.ts +0 -1
- package/dist/plugins/web/api-types.d.ts +3 -7
- package/dist/plugins/web/http/api-router.d.ts +0 -1
- package/dist/plugins/web/http/api-router.js +2 -4
- package/dist/plugins/web/http/git-api-router.d.ts +3 -0
- package/dist/plugins/web/http/git-api-router.js +98 -0
- package/dist/plugins/web/http/workspace-api-router.js +15 -37
- package/dist/plugins/web/plugin.js +4 -6
- package/dist/plugins/web/runtime.d.ts +5 -27
- package/dist/plugins/web/runtime.js +3 -42
- package/dist/plugins/workspace/mcp.js +17 -135
- package/dist/plugins/workspace/metadata.d.ts +4 -0
- package/dist/plugins/workspace/metadata.js +105 -0
- package/dist/plugins/workspace/plugin.d.ts +2 -8
- package/dist/plugins/workspace/plugin.js +8 -20
- package/dist/plugins/workspace/types.d.ts +18 -0
- package/dist/plugins/workspace/{infrastructure/workspace-fs.d.ts → workspace-fs.d.ts} +2 -15
- package/dist/plugins/workspace/workspace-fs.js +111 -0
- package/dist/plugins/workspace/workspace-service.d.ts +7 -42
- package/dist/plugins/workspace/workspace-service.js +61 -315
- package/dist/presentation/http/http-utils.d.ts +0 -1
- package/dist/presentation/http/oauth-router.d.ts +0 -1
- package/dist/presentation/http/oauth-router.js +0 -1
- package/dist/web/assets/{index-B4vO7wVS.css → index-DFKEvyzR.css} +1 -1
- package/dist/web/assets/index-vzPPspuL.js +26 -0
- package/dist/web/index.html +2 -2
- package/package.json +4 -3
- package/dist/plugins/workspace/domain/filesystem.d.ts +0 -25
- package/dist/plugins/workspace/domain/git.d.ts +0 -7
- package/dist/plugins/workspace/domain/repository.d.ts +0 -16
- package/dist/plugins/workspace/domain/repository.js +0 -1
- package/dist/plugins/workspace/domain/review.d.ts +0 -28
- package/dist/plugins/workspace/domain/review.js +0 -1
- package/dist/plugins/workspace/domain/workspace.d.ts +0 -23
- package/dist/plugins/workspace/domain/workspace.js +0 -1
- package/dist/plugins/workspace/git/git-command-runner.d.ts +0 -6
- package/dist/plugins/workspace/git/git-command-runner.js +0 -15
- package/dist/plugins/workspace/git/git-service.d.ts +0 -14
- package/dist/plugins/workspace/git/git-service.js +0 -107
- package/dist/plugins/workspace/git/git-snapshot-service.d.ts +0 -33
- package/dist/plugins/workspace/git/git-snapshot-service.js +0 -352
- package/dist/plugins/workspace/infrastructure/workspace-fs.js +0 -340
- package/dist/plugins/workspace/infrastructure/workspace-repository.d.ts +0 -12
- package/dist/plugins/workspace/infrastructure/workspace-repository.js +0 -51
- package/dist/plugins/workspace/workspace-helpers.d.ts +0 -9
- package/dist/plugins/workspace/workspace-helpers.js +0 -67
- package/dist/plugins/workspace/worktree-review-service.d.ts +0 -16
- package/dist/plugins/workspace/worktree-review-service.js +0 -51
- package/dist/web/assets/index-BU7LCgKB.js +0 -26
- /package/dist/plugins/{workspace/domain/filesystem.js → git/types.js} +0 -0
- /package/dist/plugins/workspace/{domain/git.js → types.js} +0 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
English | <a href="./docs/README.zh-CN.md">简体中文</a> | <a href="https://doc.chatroomcp.com/">Documentation</a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
ChatRoom is a local MCP runtime that lets ChatGPT work with
|
|
11
|
+
ChatRoom is a local MCP runtime that lets ChatGPT work with projects and processes on your device. Its WebUI provides workspace browsing, Git repository management, process monitoring, ChatRoom Cloud controls, and plugin-attributed operation logs.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
package/dist/app/application.js
CHANGED
|
@@ -11,6 +11,7 @@ import { ExternalAccessRegistry } from "./external-access-registry.js";
|
|
|
11
11
|
import { ServiceRegistry } from "../plugins/types.js";
|
|
12
12
|
import { PluginManager } from "../plugins/plugin-manager.js";
|
|
13
13
|
import { createWorkspacePlugin } from "../plugins/workspace/plugin.js";
|
|
14
|
+
import { createGitPlugin } from "../plugins/git/plugin.js";
|
|
14
15
|
import { createProcessPlugin, ProcessService, } from "../plugins/process/plugin.js";
|
|
15
16
|
import { createCloudPlugin, CloudService } from "../plugins/cloud/plugin.js";
|
|
16
17
|
import { createComputerPlugin, ComputerServiceToken, } from "../plugins/computer/plugin.js";
|
|
@@ -36,6 +37,7 @@ export async function createApplication(config) {
|
|
|
36
37
|
services,
|
|
37
38
|
}, [
|
|
38
39
|
createWorkspacePlugin(),
|
|
40
|
+
createGitPlugin(),
|
|
39
41
|
createProcessPlugin(),
|
|
40
42
|
createComputerPlugin(),
|
|
41
43
|
createCloudPlugin(),
|
package/dist/app/event-bus.d.ts
CHANGED
package/dist/app/event-bus.js
CHANGED
|
@@ -4,7 +4,6 @@ export interface ExternalAccessState {
|
|
|
4
4
|
mcpBaseUrl: string | null;
|
|
5
5
|
webBaseUrl: string | null;
|
|
6
6
|
}
|
|
7
|
-
/** Combines configured self-hosted URLs with the current ChatRoom Cloud URLs. */
|
|
8
7
|
export declare class ExternalAccessRegistry {
|
|
9
8
|
private readonly config;
|
|
10
9
|
private cloud;
|
package/dist/app/lifecycle.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Implements owner authentication and the OAuth Authorization Code + PKCE flow used for remote MCP access.
|
|
3
|
-
* Raw authorization codes and access tokens are never persisted; repositories receive only their hashes.
|
|
4
|
-
*/
|
|
5
1
|
import { createHash, randomBytes, randomUUID, timingSafeEqual, } from "node:crypto";
|
|
6
2
|
import { ChatRoomError } from "../core/errors/chatroom-error.js";
|
|
7
3
|
export class AuthService {
|
package/dist/cli/run-cli.js
CHANGED
package/dist/config/types.d.ts
CHANGED
|
@@ -1,59 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistence contracts for OAuth clients, authorization codes, and access tokens.
|
|
3
|
-
*
|
|
4
|
-
* Raw authorization codes and bearer tokens must never cross this boundary. AuthService hashes
|
|
5
|
-
* those credentials first and repositories store only the resulting digest values.
|
|
6
|
-
*/
|
|
7
|
-
/** Dynamically registered OAuth public client. */
|
|
8
1
|
export interface OAuthClientRecord {
|
|
9
2
|
clientId: string;
|
|
10
3
|
name: string;
|
|
11
|
-
/** Exact redirect URI allowlist registered for the client. */
|
|
12
4
|
redirectUris: string[];
|
|
13
5
|
createdAt: string;
|
|
14
6
|
}
|
|
15
|
-
/** Hashed Authorization Code + PKCE state. */
|
|
16
7
|
export interface OAuthCodeRecord {
|
|
17
|
-
/** SHA-256 digest of the raw one-time authorization code. */
|
|
18
8
|
codeHash: string;
|
|
19
9
|
clientId: string;
|
|
20
10
|
redirectUri: string;
|
|
21
|
-
/** S256 challenge supplied during authorization. */
|
|
22
11
|
codeChallenge: string;
|
|
23
|
-
/** Granted scopes bound to this authorization code. */
|
|
24
12
|
scope: string;
|
|
25
13
|
expiresAt: string;
|
|
26
|
-
/** Consumption timestamp, or null before the code is atomically used. */
|
|
27
14
|
usedAt: string | null;
|
|
28
15
|
}
|
|
29
|
-
/** Minimal active-token metadata returned after a hashed token lookup. */
|
|
30
16
|
export interface OAuthTokenRecord {
|
|
31
17
|
clientId: string;
|
|
32
18
|
scope: string;
|
|
33
19
|
expiresAt: string;
|
|
34
20
|
}
|
|
35
|
-
/** Storage port used by AuthService for the complete OAuth state machine. */
|
|
36
21
|
export interface OAuthStateRepository {
|
|
37
22
|
createClient(client: OAuthClientRecord): void;
|
|
38
23
|
getClient(clientId: string): OAuthClientRecord | null;
|
|
39
24
|
createCode(code: OAuthCodeRecord): void;
|
|
40
|
-
/** Read an unconsumed candidate without mutating it so PKCE can be validated first. */
|
|
41
25
|
getCode(codeHash: string): OAuthCodeRecord | null;
|
|
42
|
-
/** Atomically consume a valid code; null means it was missing, expired by policy, or already used. */
|
|
43
26
|
consumeCode(codeHash: string, now: string): OAuthCodeRecord | null;
|
|
44
|
-
/** Persist only a token hash together with scope and expiry metadata. */
|
|
45
27
|
createToken(tokenHash: string, clientId: string, scope: string, expiresAt: string): void;
|
|
46
|
-
/** Persist a hashed refresh token separately from access tokens. */
|
|
47
28
|
createRefreshToken(tokenHash: string, clientId: string, scope: string, expiresAt: string): void;
|
|
48
|
-
/** Atomically rotate an active refresh token. */
|
|
49
29
|
consumeRefreshToken(tokenHash: string, clientId?: string): {
|
|
50
30
|
clientId: string;
|
|
51
31
|
scope: string;
|
|
52
32
|
} | null;
|
|
53
|
-
/** Resolve active, non-revoked token metadata from a token hash. */
|
|
54
33
|
tokenInfo(tokenHash: string): OAuthTokenRecord | null;
|
|
55
|
-
/** Mark a token hash revoked without requiring the raw bearer token to be stored. */
|
|
56
34
|
revokeToken(tokenHash: string): void;
|
|
57
|
-
/** Remove expired and terminal OAuth state that no longer participates in the protocol. */
|
|
58
35
|
prune(now: string): number;
|
|
59
36
|
}
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistence contracts for OAuth clients, authorization codes, and access tokens.
|
|
3
|
-
*
|
|
4
|
-
* Raw authorization codes and bearer tokens must never cross this boundary. AuthService hashes
|
|
5
|
-
* those credentials first and repositories store only the resulting digest values.
|
|
6
|
-
*/
|
|
7
1
|
export {};
|
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed error vocabulary exposed across ChatRoom application boundaries.
|
|
3
|
-
*
|
|
4
|
-
* Presentation adapters translate these stable codes into protocol responses. Domain/application
|
|
5
|
-
* code should prefer ChatRoomError over parsing message text or leaking implementation-specific
|
|
6
|
-
* filesystem, SQLite, process, or SDK errors to external callers.
|
|
7
|
-
*/
|
|
8
1
|
export declare const ERROR_CODES: readonly ["INVALID_INPUT", "NOT_FOUND", "FORBIDDEN", "CONFLICT", "UNSUPPORTED", "PROCESS_FAILED", "INTERNAL"];
|
|
9
2
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
10
|
-
/** Error carrying a stable machine-readable code plus optional structured diagnostic details. */
|
|
11
3
|
export declare class ChatRoomError extends Error {
|
|
12
4
|
readonly code: ErrorCode;
|
|
13
5
|
readonly details?: unknown;
|
|
14
6
|
constructor(code: ErrorCode, message: string, details?: unknown, options?: ErrorOptions);
|
|
15
7
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Normalize an arbitrary thrown value at an application or protocol boundary.
|
|
18
|
-
*
|
|
19
|
-
* Existing ChatRoomError instances retain their public code. Native Error values become INTERNAL
|
|
20
|
-
* while preserving the original error as `cause`; non-Error throws are converted as well so every
|
|
21
|
-
* external path observes the same error vocabulary.
|
|
22
|
-
*/
|
|
23
8
|
export declare function asChatRoomError(error: unknown): ChatRoomError;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed error vocabulary exposed across ChatRoom application boundaries.
|
|
3
|
-
*
|
|
4
|
-
* Presentation adapters translate these stable codes into protocol responses. Domain/application
|
|
5
|
-
* code should prefer ChatRoomError over parsing message text or leaking implementation-specific
|
|
6
|
-
* filesystem, SQLite, process, or SDK errors to external callers.
|
|
7
|
-
*/
|
|
8
1
|
export const ERROR_CODES = [
|
|
9
2
|
"INVALID_INPUT",
|
|
10
3
|
"NOT_FOUND",
|
|
@@ -14,7 +7,6 @@ export const ERROR_CODES = [
|
|
|
14
7
|
"PROCESS_FAILED",
|
|
15
8
|
"INTERNAL",
|
|
16
9
|
];
|
|
17
|
-
/** Error carrying a stable machine-readable code plus optional structured diagnostic details. */
|
|
18
10
|
export class ChatRoomError extends Error {
|
|
19
11
|
code;
|
|
20
12
|
details;
|
|
@@ -25,13 +17,6 @@ export class ChatRoomError extends Error {
|
|
|
25
17
|
this.details = details;
|
|
26
18
|
}
|
|
27
19
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Normalize an arbitrary thrown value at an application or protocol boundary.
|
|
30
|
-
*
|
|
31
|
-
* Existing ChatRoomError instances retain their public code. Native Error values become INTERNAL
|
|
32
|
-
* while preserving the original error as `cause`; non-Error throws are converted as well so every
|
|
33
|
-
* external path observes the same error vocabulary.
|
|
34
|
-
*/
|
|
35
20
|
export function asChatRoomError(error) {
|
|
36
21
|
if (error instanceof ChatRoomError)
|
|
37
22
|
return error;
|
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
export function boundJson(value, maxBytes) {
|
|
2
|
-
// Preserve both the beginning and end of oversized strings because failures commonly appear at the tail.
|
|
3
2
|
if (value === undefined)
|
|
4
3
|
return { value: null, truncated: false };
|
|
5
4
|
const json = safeStringify(value);
|
|
6
5
|
const size = Buffer.byteLength(json);
|
|
7
6
|
if (size <= maxBytes)
|
|
8
7
|
return { value: JSON.parse(json), truncated: false };
|
|
9
|
-
const marker = "\n…[truncated]…\n";
|
|
10
|
-
const budget = Math.max(0, maxBytes - Buffer.byteLength(marker));
|
|
11
|
-
const headBudget = Math.floor(budget / 2);
|
|
12
|
-
const tailBudget = budget - headBudget;
|
|
13
8
|
const encoded = Buffer.from(json);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const marker = "\n…[truncated]…\n";
|
|
10
|
+
let low = 0;
|
|
11
|
+
let high = encoded.length;
|
|
12
|
+
let best = truncatedValue(encoded, marker, size, 0);
|
|
13
|
+
while (low <= high) {
|
|
14
|
+
const retainedBytes = Math.floor((low + high) / 2);
|
|
15
|
+
const candidate = truncatedValue(encoded, marker, size, retainedBytes);
|
|
16
|
+
if (Buffer.byteLength(JSON.stringify(candidate)) <= maxBytes) {
|
|
17
|
+
best = candidate;
|
|
18
|
+
low = retainedBytes + 1;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
high = retainedBytes - 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { value: best, truncated: true };
|
|
25
|
+
}
|
|
26
|
+
function truncatedValue(encoded, marker, originalBytes, retainedBytes) {
|
|
27
|
+
const headBytes = Math.floor(retainedBytes / 2);
|
|
28
|
+
const tailBytes = retainedBytes - headBytes;
|
|
29
|
+
const head = encoded.subarray(0, headBytes).toString("utf8");
|
|
30
|
+
const tail = encoded
|
|
31
|
+
.subarray(Math.max(0, encoded.length - tailBytes))
|
|
32
|
+
.toString("utf8");
|
|
17
33
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
truncatedJson: `${head}${marker}${tail}`,
|
|
35
|
+
originalBytes,
|
|
20
36
|
};
|
|
21
37
|
}
|
|
22
38
|
function safeStringify(value) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Redacts common credential shapes before values cross the durable operation-log boundary. */
|
|
2
1
|
const SECRET_KEY = new RegExp("(^|[_-])" +
|
|
3
2
|
"(authorization|cookie|password|passwd|token|access[_-]?token|" +
|
|
4
3
|
"refresh[_-]?token|api[_-]?key|apikey|secret|private[_-]?key|" +
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
/** Persistent plugin operation lifecycle recorded by ChatRoom. */
|
|
2
1
|
export declare const OPERATION_STATUSES: readonly ["running", "success", "error", "cancelled"];
|
|
3
2
|
export type OperationStatus = (typeof OPERATION_STATUSES)[number];
|
|
4
|
-
/** Where an operation was initiated; pluginId identifies which plugin owns the operation. */
|
|
5
3
|
export declare const OPERATION_SOURCES: readonly ["mcp", "gui", "system", "cli"];
|
|
6
4
|
export type OperationSource = (typeof OPERATION_SOURCES)[number];
|
|
7
5
|
export interface Operation {
|
|
@@ -10,7 +8,6 @@ export interface Operation {
|
|
|
10
8
|
source: OperationSource;
|
|
11
9
|
action: string;
|
|
12
10
|
status: OperationStatus;
|
|
13
|
-
workspaceId: string | null;
|
|
14
11
|
processId: string | null;
|
|
15
12
|
input: unknown;
|
|
16
13
|
output: unknown;
|
|
@@ -26,6 +23,5 @@ export interface OperationStart {
|
|
|
26
23
|
source: OperationSource;
|
|
27
24
|
action: string;
|
|
28
25
|
input?: unknown;
|
|
29
|
-
workspaceId?: string | null;
|
|
30
26
|
processId?: string | null;
|
|
31
27
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
/** Persistent plugin operation lifecycle recorded by ChatRoom. */
|
|
2
1
|
export const OPERATION_STATUSES = [
|
|
3
2
|
"running",
|
|
4
3
|
"success",
|
|
5
4
|
"error",
|
|
6
5
|
"cancelled",
|
|
7
6
|
];
|
|
8
|
-
/** Where an operation was initiated; pluginId identifies which plugin owns the operation. */
|
|
9
7
|
export const OPERATION_SOURCES = ["mcp", "gui", "system", "cli"];
|
|
@@ -1,109 +1,16 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
id TEXT PRIMARY KEY,
|
|
7
|
-
root TEXT NOT NULL UNIQUE,
|
|
8
|
-
source_root TEXT NOT NULL,
|
|
9
|
-
mode TEXT NOT NULL CHECK(mode IN ('checkout','worktree')),
|
|
10
|
-
created_at TEXT NOT NULL,
|
|
11
|
-
last_used_at TEXT NOT NULL
|
|
12
|
-
);
|
|
13
|
-
CREATE INDEX IF NOT EXISTS workspaces_last_used_idx ON workspaces(last_used_at DESC);
|
|
14
|
-
|
|
15
|
-
CREATE TABLE IF NOT EXISTS operations (
|
|
16
|
-
operation_id TEXT PRIMARY KEY,
|
|
17
|
-
plugin_id TEXT NOT NULL,
|
|
18
|
-
source TEXT NOT NULL,
|
|
19
|
-
action TEXT NOT NULL,
|
|
20
|
-
status TEXT NOT NULL,
|
|
21
|
-
workspace_id TEXT,
|
|
22
|
-
process_id TEXT,
|
|
23
|
-
input_json TEXT NOT NULL,
|
|
24
|
-
output_json TEXT NOT NULL,
|
|
25
|
-
error_json TEXT NOT NULL,
|
|
26
|
-
input_truncated INTEGER NOT NULL DEFAULT 0,
|
|
27
|
-
output_truncated INTEGER NOT NULL DEFAULT 0,
|
|
28
|
-
started_at TEXT NOT NULL,
|
|
29
|
-
finished_at TEXT,
|
|
30
|
-
duration_ms INTEGER
|
|
31
|
-
);
|
|
32
|
-
CREATE INDEX IF NOT EXISTS operations_started_idx ON operations(started_at DESC);
|
|
33
|
-
CREATE INDEX IF NOT EXISTS operations_plugin_idx ON operations(plugin_id, started_at DESC);
|
|
34
|
-
CREATE INDEX IF NOT EXISTS operations_workspace_idx ON operations(workspace_id, started_at DESC);
|
|
35
|
-
CREATE INDEX IF NOT EXISTS operations_status_idx ON operations(status, started_at DESC);
|
|
36
|
-
|
|
37
|
-
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
38
|
-
client_id TEXT PRIMARY KEY,
|
|
39
|
-
name TEXT NOT NULL,
|
|
40
|
-
redirect_uris_json TEXT NOT NULL,
|
|
41
|
-
created_at TEXT NOT NULL
|
|
42
|
-
);
|
|
43
|
-
CREATE TABLE IF NOT EXISTS oauth_codes (
|
|
44
|
-
code_hash TEXT PRIMARY KEY,
|
|
45
|
-
client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
46
|
-
redirect_uri TEXT NOT NULL,
|
|
47
|
-
code_challenge TEXT NOT NULL,
|
|
48
|
-
scope TEXT NOT NULL,
|
|
49
|
-
expires_at TEXT NOT NULL,
|
|
50
|
-
used_at TEXT
|
|
51
|
-
);
|
|
52
|
-
CREATE INDEX IF NOT EXISTS oauth_code_expiry_idx ON oauth_codes(expires_at);
|
|
53
|
-
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
|
54
|
-
token_hash TEXT PRIMARY KEY,
|
|
55
|
-
client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
56
|
-
scope TEXT NOT NULL,
|
|
57
|
-
expires_at TEXT NOT NULL,
|
|
58
|
-
revoked_at TEXT,
|
|
59
|
-
created_at TEXT NOT NULL
|
|
60
|
-
);
|
|
61
|
-
CREATE INDEX IF NOT EXISTS oauth_token_expiry_idx ON oauth_tokens(expires_at);
|
|
62
|
-
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
63
|
-
token_hash TEXT PRIMARY KEY,
|
|
64
|
-
client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
65
|
-
scope TEXT NOT NULL,
|
|
66
|
-
expires_at TEXT NOT NULL,
|
|
67
|
-
revoked_at TEXT,
|
|
68
|
-
created_at TEXT NOT NULL
|
|
69
|
-
);
|
|
70
|
-
CREATE INDEX IF NOT EXISTS oauth_refresh_expiry_idx ON oauth_refresh_tokens(expires_at);
|
|
71
|
-
|
|
72
|
-
CREATE TABLE IF NOT EXISTS web_sessions (
|
|
73
|
-
token_hash TEXT PRIMARY KEY,
|
|
74
|
-
expires_at TEXT NOT NULL,
|
|
75
|
-
created_at TEXT NOT NULL
|
|
76
|
-
);
|
|
77
|
-
CREATE INDEX IF NOT EXISTS web_session_expiry_idx ON web_sessions(expires_at);
|
|
78
|
-
|
|
79
|
-
CREATE TABLE IF NOT EXISTS passkeys (
|
|
80
|
-
credential_id TEXT PRIMARY KEY,
|
|
81
|
-
public_key BLOB NOT NULL,
|
|
82
|
-
counter INTEGER NOT NULL,
|
|
83
|
-
transports_json TEXT NOT NULL,
|
|
84
|
-
device_type TEXT NOT NULL CHECK(device_type IN ('singleDevice','multiDevice')),
|
|
85
|
-
backed_up INTEGER NOT NULL,
|
|
86
|
-
name TEXT NOT NULL,
|
|
87
|
-
created_at TEXT NOT NULL,
|
|
88
|
-
last_used_at TEXT NOT NULL
|
|
89
|
-
);
|
|
90
|
-
CREATE INDEX IF NOT EXISTS passkeys_last_used_idx ON passkeys(last_used_at DESC);
|
|
91
|
-
|
|
92
|
-
CREATE TABLE IF NOT EXISTS computer_settings (
|
|
93
|
-
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
94
|
-
enabled INTEGER NOT NULL DEFAULT 0,
|
|
95
|
-
remote_access INTEGER NOT NULL DEFAULT 1,
|
|
96
|
-
updated_at TEXT NOT NULL
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
`;
|
|
4
|
+
import { migrateDatabase } from "./migrations.js";
|
|
5
|
+
import { DATABASE_SCHEMA } from "./schema.js";
|
|
100
6
|
export class AppDatabase {
|
|
101
7
|
raw;
|
|
102
8
|
constructor(databasePath) {
|
|
103
9
|
mkdirSync(path.dirname(databasePath), { recursive: true, mode: 0o700 });
|
|
104
10
|
this.raw = new DatabaseSync(databasePath);
|
|
105
11
|
this.raw.exec("PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;");
|
|
106
|
-
this.raw
|
|
12
|
+
migrateDatabase(this.raw);
|
|
13
|
+
this.raw.exec(DATABASE_SCHEMA);
|
|
107
14
|
}
|
|
108
15
|
close() {
|
|
109
16
|
this.raw.close();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DATABASE_SCHEMA_VERSION } from "./schema.js";
|
|
2
|
+
export function migrateDatabase(db) {
|
|
3
|
+
let version = userVersion(db);
|
|
4
|
+
if (version > DATABASE_SCHEMA_VERSION)
|
|
5
|
+
throw new Error(`Database schema ${version} is newer than this ChatRoom build (${DATABASE_SCHEMA_VERSION})`);
|
|
6
|
+
while (version < DATABASE_SCHEMA_VERSION) {
|
|
7
|
+
switch (version) {
|
|
8
|
+
case 0:
|
|
9
|
+
migrate0To1(db);
|
|
10
|
+
version = 1;
|
|
11
|
+
break;
|
|
12
|
+
default:
|
|
13
|
+
throw new Error(`Unsupported ChatRoom database schema: ${version}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function migrate0To1(db) {
|
|
18
|
+
db.exec("BEGIN IMMEDIATE;");
|
|
19
|
+
try {
|
|
20
|
+
db.exec(`
|
|
21
|
+
DROP TABLE IF EXISTS workspaces;
|
|
22
|
+
DROP TABLE IF EXISTS audit_events;
|
|
23
|
+
DROP TABLE IF EXISTS agent_runs;
|
|
24
|
+
DROP INDEX IF EXISTS operations_workspace_idx;
|
|
25
|
+
`);
|
|
26
|
+
const operationColumns = db
|
|
27
|
+
.prepare("PRAGMA table_info(operations)")
|
|
28
|
+
.all();
|
|
29
|
+
if (operationColumns.some((column) => column.name === "workspace_id"))
|
|
30
|
+
db.exec("ALTER TABLE operations DROP COLUMN workspace_id;");
|
|
31
|
+
db.exec("PRAGMA user_version = 1; COMMIT;");
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
db.exec("ROLLBACK;");
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function userVersion(db) {
|
|
39
|
+
const row = db.prepare("PRAGMA user_version").get();
|
|
40
|
+
const version = Number(row?.user_version ?? 0);
|
|
41
|
+
if (!Number.isInteger(version) || version < 0)
|
|
42
|
+
throw new Error(`Invalid ChatRoom database schema version: ${version}`);
|
|
43
|
+
return version;
|
|
44
|
+
}
|
|
@@ -7,10 +7,10 @@ export class OperationRepository {
|
|
|
7
7
|
this.database.raw
|
|
8
8
|
.prepare(`
|
|
9
9
|
INSERT INTO operations(
|
|
10
|
-
operation_id, plugin_id, source, action, status,
|
|
10
|
+
operation_id, plugin_id, source, action, status, process_id,
|
|
11
11
|
input_json, output_json, error_json, input_truncated, output_truncated,
|
|
12
12
|
started_at, finished_at, duration_ms
|
|
13
|
-
) VALUES(
|
|
13
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
14
14
|
`)
|
|
15
15
|
.run(...toRowValues(operation));
|
|
16
16
|
}
|
|
@@ -18,7 +18,7 @@ export class OperationRepository {
|
|
|
18
18
|
this.database.raw
|
|
19
19
|
.prepare(`
|
|
20
20
|
UPDATE operations SET
|
|
21
|
-
plugin_id=?, source=?, action=?, status=?,
|
|
21
|
+
plugin_id=?, source=?, action=?, status=?, process_id=?,
|
|
22
22
|
input_json=?, output_json=?, error_json=?, input_truncated=?, output_truncated=?,
|
|
23
23
|
started_at=?, finished_at=?, duration_ms=?
|
|
24
24
|
WHERE operation_id=?
|
|
@@ -42,10 +42,6 @@ export class OperationRepository {
|
|
|
42
42
|
where.push("status=?");
|
|
43
43
|
args.push(query.status);
|
|
44
44
|
}
|
|
45
|
-
if (query.workspaceId) {
|
|
46
|
-
where.push("workspace_id=?");
|
|
47
|
-
args.push(query.workspaceId);
|
|
48
|
-
}
|
|
49
45
|
const limit = Math.min(Math.max(query.limit ?? 100, 1), 500);
|
|
50
46
|
const offset = Math.max(query.offset ?? 0, 0);
|
|
51
47
|
const filter = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
@@ -88,7 +84,6 @@ function toRowValues(operation) {
|
|
|
88
84
|
operation.source,
|
|
89
85
|
operation.action,
|
|
90
86
|
operation.status,
|
|
91
|
-
operation.workspaceId,
|
|
92
87
|
operation.processId,
|
|
93
88
|
JSON.stringify(operation.input),
|
|
94
89
|
JSON.stringify(operation.output),
|
|
@@ -107,7 +102,6 @@ function fromRow(row) {
|
|
|
107
102
|
source: row.source,
|
|
108
103
|
action: row.action,
|
|
109
104
|
status: row.status,
|
|
110
|
-
workspaceId: row.workspace_id,
|
|
111
105
|
processId: row.process_id,
|
|
112
106
|
input: parse(row.input_json),
|
|
113
107
|
output: parse(row.output_json),
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const DATABASE_SCHEMA_VERSION = 1;
|
|
2
|
+
export declare const DATABASE_SCHEMA = "\n CREATE TABLE IF NOT EXISTS operations (\n operation_id TEXT PRIMARY KEY,\n plugin_id TEXT NOT NULL,\n source TEXT NOT NULL,\n action TEXT NOT NULL,\n status TEXT NOT NULL,\n process_id TEXT,\n input_json TEXT NOT NULL,\n output_json TEXT NOT NULL,\n error_json TEXT NOT NULL,\n input_truncated INTEGER NOT NULL DEFAULT 0,\n output_truncated INTEGER NOT NULL DEFAULT 0,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n duration_ms INTEGER\n );\n CREATE INDEX IF NOT EXISTS operations_started_idx ON operations(started_at DESC);\n CREATE INDEX IF NOT EXISTS operations_plugin_idx ON operations(plugin_id, started_at DESC);\n CREATE INDEX IF NOT EXISTS operations_status_idx ON operations(status, started_at DESC);\n\n CREATE TABLE IF NOT EXISTS oauth_clients (\n client_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n redirect_uris_json TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS oauth_codes (\n code_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n used_at TEXT\n );\n CREATE INDEX IF NOT EXISTS oauth_code_expiry_idx ON oauth_codes(expires_at);\n CREATE TABLE IF NOT EXISTS oauth_tokens (\n token_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS oauth_token_expiry_idx ON oauth_tokens(expires_at);\n CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (\n token_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS oauth_refresh_expiry_idx ON oauth_refresh_tokens(expires_at);\n\n CREATE TABLE IF NOT EXISTS web_sessions (\n token_hash TEXT PRIMARY KEY,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS web_session_expiry_idx ON web_sessions(expires_at);\n\n CREATE TABLE IF NOT EXISTS passkeys (\n credential_id TEXT PRIMARY KEY,\n public_key BLOB NOT NULL,\n counter INTEGER NOT NULL,\n transports_json TEXT NOT NULL,\n device_type TEXT NOT NULL CHECK(device_type IN ('singleDevice','multiDevice')),\n backed_up INTEGER NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n last_used_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS passkeys_last_used_idx ON passkeys(last_used_at DESC);\n\n CREATE TABLE IF NOT EXISTS computer_settings (\n id INTEGER PRIMARY KEY CHECK(id = 1),\n enabled INTEGER NOT NULL DEFAULT 0,\n remote_access INTEGER NOT NULL DEFAULT 1,\n updated_at TEXT NOT NULL\n );\n\n";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const DATABASE_SCHEMA_VERSION = 1;
|
|
2
|
+
export const DATABASE_SCHEMA = `
|
|
3
|
+
CREATE TABLE IF NOT EXISTS operations (
|
|
4
|
+
operation_id TEXT PRIMARY KEY,
|
|
5
|
+
plugin_id TEXT NOT NULL,
|
|
6
|
+
source TEXT NOT NULL,
|
|
7
|
+
action TEXT NOT NULL,
|
|
8
|
+
status TEXT NOT NULL,
|
|
9
|
+
process_id TEXT,
|
|
10
|
+
input_json TEXT NOT NULL,
|
|
11
|
+
output_json TEXT NOT NULL,
|
|
12
|
+
error_json TEXT NOT NULL,
|
|
13
|
+
input_truncated INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
output_truncated INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
started_at TEXT NOT NULL,
|
|
16
|
+
finished_at TEXT,
|
|
17
|
+
duration_ms INTEGER
|
|
18
|
+
);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS operations_started_idx ON operations(started_at DESC);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS operations_plugin_idx ON operations(plugin_id, started_at DESC);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS operations_status_idx ON operations(status, started_at DESC);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
24
|
+
client_id TEXT PRIMARY KEY,
|
|
25
|
+
name TEXT NOT NULL,
|
|
26
|
+
redirect_uris_json TEXT NOT NULL,
|
|
27
|
+
created_at TEXT NOT NULL
|
|
28
|
+
);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS oauth_codes (
|
|
30
|
+
code_hash TEXT PRIMARY KEY,
|
|
31
|
+
client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
32
|
+
redirect_uri TEXT NOT NULL,
|
|
33
|
+
code_challenge TEXT NOT NULL,
|
|
34
|
+
scope TEXT NOT NULL,
|
|
35
|
+
expires_at TEXT NOT NULL,
|
|
36
|
+
used_at TEXT
|
|
37
|
+
);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS oauth_code_expiry_idx ON oauth_codes(expires_at);
|
|
39
|
+
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
|
40
|
+
token_hash TEXT PRIMARY KEY,
|
|
41
|
+
client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
42
|
+
scope TEXT NOT NULL,
|
|
43
|
+
expires_at TEXT NOT NULL,
|
|
44
|
+
revoked_at TEXT,
|
|
45
|
+
created_at TEXT NOT NULL
|
|
46
|
+
);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS oauth_token_expiry_idx ON oauth_tokens(expires_at);
|
|
48
|
+
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
49
|
+
token_hash TEXT PRIMARY KEY,
|
|
50
|
+
client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
51
|
+
scope TEXT NOT NULL,
|
|
52
|
+
expires_at TEXT NOT NULL,
|
|
53
|
+
revoked_at TEXT,
|
|
54
|
+
created_at TEXT NOT NULL
|
|
55
|
+
);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS oauth_refresh_expiry_idx ON oauth_refresh_tokens(expires_at);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS web_sessions (
|
|
59
|
+
token_hash TEXT PRIMARY KEY,
|
|
60
|
+
expires_at TEXT NOT NULL,
|
|
61
|
+
created_at TEXT NOT NULL
|
|
62
|
+
);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS web_session_expiry_idx ON web_sessions(expires_at);
|
|
64
|
+
|
|
65
|
+
CREATE TABLE IF NOT EXISTS passkeys (
|
|
66
|
+
credential_id TEXT PRIMARY KEY,
|
|
67
|
+
public_key BLOB NOT NULL,
|
|
68
|
+
counter INTEGER NOT NULL,
|
|
69
|
+
transports_json TEXT NOT NULL,
|
|
70
|
+
device_type TEXT NOT NULL CHECK(device_type IN ('singleDevice','multiDevice')),
|
|
71
|
+
backed_up INTEGER NOT NULL,
|
|
72
|
+
name TEXT NOT NULL,
|
|
73
|
+
created_at TEXT NOT NULL,
|
|
74
|
+
last_used_at TEXT NOT NULL
|
|
75
|
+
);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS passkeys_last_used_idx ON passkeys(last_used_at DESC);
|
|
77
|
+
|
|
78
|
+
CREATE TABLE IF NOT EXISTS computer_settings (
|
|
79
|
+
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
80
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
remote_access INTEGER NOT NULL DEFAULT 1,
|
|
82
|
+
updated_at TEXT NOT NULL
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
`;
|