@chatroomcp/chatroom 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +50 -0
  3. package/assets/chatroom.svg +15 -0
  4. package/chatroom.config.example.json +24 -0
  5. package/dist/app/application.d.ts +18 -0
  6. package/dist/app/application.js +63 -0
  7. package/dist/app/event-bus.d.ts +23 -0
  8. package/dist/app/event-bus.js +12 -0
  9. package/dist/app/external-access-registry.d.ts +18 -0
  10. package/dist/app/external-access-registry.js +48 -0
  11. package/dist/app/lifecycle.d.ts +12 -0
  12. package/dist/app/lifecycle.js +41 -0
  13. package/dist/auth/auth-service.d.ts +88 -0
  14. package/dist/auth/auth-service.js +222 -0
  15. package/dist/auth/ingress-policy.d.ts +21 -0
  16. package/dist/auth/ingress-policy.js +57 -0
  17. package/dist/auth/passkey-service.d.ts +39 -0
  18. package/dist/auth/passkey-service.js +145 -0
  19. package/dist/cli/index.d.ts +2 -0
  20. package/dist/cli/index.js +10 -0
  21. package/dist/cli/run-cli.d.ts +1 -0
  22. package/dist/cli/run-cli.js +42 -0
  23. package/dist/config/load-config.d.ts +7 -0
  24. package/dist/config/load-config.js +182 -0
  25. package/dist/config/platform-paths.d.ts +6 -0
  26. package/dist/config/platform-paths.js +34 -0
  27. package/dist/config/types.d.ts +25 -0
  28. package/dist/config/types.js +1 -0
  29. package/dist/core/auth/passkey-repository.d.ts +18 -0
  30. package/dist/core/auth/passkey-repository.js +1 -0
  31. package/dist/core/auth/repository.d.ts +59 -0
  32. package/dist/core/auth/repository.js +7 -0
  33. package/dist/core/auth/session-repository.d.ts +6 -0
  34. package/dist/core/auth/session-repository.js +1 -0
  35. package/dist/core/errors/chatroom-error.d.ts +23 -0
  36. package/dist/core/errors/chatroom-error.js +46 -0
  37. package/dist/core/operations/bounded-value.d.ts +6 -0
  38. package/dist/core/operations/bounded-value.js +41 -0
  39. package/dist/core/operations/redactor.d.ts +4 -0
  40. package/dist/core/operations/redactor.js +65 -0
  41. package/dist/core/operations/repository.d.ts +19 -0
  42. package/dist/core/operations/repository.js +1 -0
  43. package/dist/core/operations/types.d.ts +31 -0
  44. package/dist/core/operations/types.js +9 -0
  45. package/dist/core/runtime/child-environment.d.ts +1 -0
  46. package/dist/core/runtime/child-environment.js +39 -0
  47. package/dist/core/runtime/command-runner.d.ts +15 -0
  48. package/dist/core/runtime/command-runner.js +39 -0
  49. package/dist/core/runtime/identity.d.ts +2 -0
  50. package/dist/core/runtime/identity.js +9 -0
  51. package/dist/infrastructure/database/app-database.d.ts +6 -0
  52. package/dist/infrastructure/database/app-database.js +104 -0
  53. package/dist/infrastructure/database/oauth-repository.d.ts +20 -0
  54. package/dist/infrastructure/database/oauth-repository.js +140 -0
  55. package/dist/infrastructure/database/operation-repository.d.ts +16 -0
  56. package/dist/infrastructure/database/operation-repository.js +129 -0
  57. package/dist/infrastructure/database/passkey-repository.d.ts +11 -0
  58. package/dist/infrastructure/database/passkey-repository.js +57 -0
  59. package/dist/infrastructure/database/web-session-repository.d.ts +10 -0
  60. package/dist/infrastructure/database/web-session-repository.js +26 -0
  61. package/dist/infrastructure/http/http-server.d.ts +26 -0
  62. package/dist/infrastructure/http/http-server.js +166 -0
  63. package/dist/mcp/server/create-mcp-server.d.ts +3 -0
  64. package/dist/mcp/server/create-mcp-server.js +16 -0
  65. package/dist/mcp/server/tool-support.d.ts +105 -0
  66. package/dist/mcp/server/tool-support.js +124 -0
  67. package/dist/operations/operation-log.d.ts +23 -0
  68. package/dist/operations/operation-log.js +103 -0
  69. package/dist/plugins/cloud/api-client.d.ts +56 -0
  70. package/dist/plugins/cloud/api-client.js +122 -0
  71. package/dist/plugins/cloud/controller.d.ts +48 -0
  72. package/dist/plugins/cloud/controller.js +282 -0
  73. package/dist/plugins/cloud/device-proof.d.ts +9 -0
  74. package/dist/plugins/cloud/device-proof.js +17 -0
  75. package/dist/plugins/cloud/plugin.d.ts +4 -0
  76. package/dist/plugins/cloud/plugin.js +17 -0
  77. package/dist/plugins/cloud/state-store.d.ts +7 -0
  78. package/dist/plugins/cloud/state-store.js +74 -0
  79. package/dist/plugins/cloud/tunnel-client.d.ts +36 -0
  80. package/dist/plugins/cloud/tunnel-client.js +293 -0
  81. package/dist/plugins/cloud/types.d.ts +70 -0
  82. package/dist/plugins/cloud/types.js +32 -0
  83. package/dist/plugins/plugin-manager.d.ts +11 -0
  84. package/dist/plugins/plugin-manager.js +24 -0
  85. package/dist/plugins/process/backend.d.ts +19 -0
  86. package/dist/plugins/process/backend.js +1 -0
  87. package/dist/plugins/process/head-tail-buffer.d.ts +18 -0
  88. package/dist/plugins/process/head-tail-buffer.js +49 -0
  89. package/dist/plugins/process/infrastructure/pipe-process-backend.d.ts +8 -0
  90. package/dist/plugins/process/infrastructure/pipe-process-backend.js +61 -0
  91. package/dist/plugins/process/infrastructure/pty-process-backend.d.ts +8 -0
  92. package/dist/plugins/process/infrastructure/pty-process-backend.js +50 -0
  93. package/dist/plugins/process/mcp.d.ts +4 -0
  94. package/dist/plugins/process/mcp.js +69 -0
  95. package/dist/plugins/process/plugin.d.ts +4 -0
  96. package/dist/plugins/process/plugin.js +28 -0
  97. package/dist/plugins/process/process-supervisor.d.ts +36 -0
  98. package/dist/plugins/process/process-supervisor.js +232 -0
  99. package/dist/plugins/process/types.d.ts +34 -0
  100. package/dist/plugins/process/types.js +1 -0
  101. package/dist/plugins/types.d.ts +32 -0
  102. package/dist/plugins/types.js +17 -0
  103. package/dist/plugins/web/api-types.d.ts +10 -0
  104. package/dist/plugins/web/api-types.js +1 -0
  105. package/dist/plugins/web/http/api-router.d.ts +13 -0
  106. package/dist/plugins/web/http/api-router.js +146 -0
  107. package/dist/plugins/web/http/cloud-api-router.d.ts +4 -0
  108. package/dist/plugins/web/http/cloud-api-router.js +41 -0
  109. package/dist/plugins/web/http/process-api-router.d.ts +3 -0
  110. package/dist/plugins/web/http/process-api-router.js +16 -0
  111. package/dist/plugins/web/http/workspace-api-router.d.ts +3 -0
  112. package/dist/plugins/web/http/workspace-api-router.js +82 -0
  113. package/dist/plugins/web/plugin.d.ts +8 -0
  114. package/dist/plugins/web/plugin.js +17 -0
  115. package/dist/plugins/web/runtime.d.ts +36 -0
  116. package/dist/plugins/web/runtime.js +66 -0
  117. package/dist/plugins/workspace/domain/filesystem.d.ts +25 -0
  118. package/dist/plugins/workspace/domain/filesystem.js +1 -0
  119. package/dist/plugins/workspace/domain/git.d.ts +7 -0
  120. package/dist/plugins/workspace/domain/git.js +1 -0
  121. package/dist/plugins/workspace/domain/repository.d.ts +16 -0
  122. package/dist/plugins/workspace/domain/repository.js +1 -0
  123. package/dist/plugins/workspace/domain/review.d.ts +28 -0
  124. package/dist/plugins/workspace/domain/review.js +1 -0
  125. package/dist/plugins/workspace/domain/workspace.d.ts +23 -0
  126. package/dist/plugins/workspace/domain/workspace.js +1 -0
  127. package/dist/plugins/workspace/git/git-command-runner.d.ts +6 -0
  128. package/dist/plugins/workspace/git/git-command-runner.js +15 -0
  129. package/dist/plugins/workspace/git/git-service.d.ts +14 -0
  130. package/dist/plugins/workspace/git/git-service.js +107 -0
  131. package/dist/plugins/workspace/git/git-snapshot-service.d.ts +33 -0
  132. package/dist/plugins/workspace/git/git-snapshot-service.js +352 -0
  133. package/dist/plugins/workspace/infrastructure/workspace-fs.d.ts +38 -0
  134. package/dist/plugins/workspace/infrastructure/workspace-fs.js +340 -0
  135. package/dist/plugins/workspace/infrastructure/workspace-repository.d.ts +12 -0
  136. package/dist/plugins/workspace/infrastructure/workspace-repository.js +51 -0
  137. package/dist/plugins/workspace/mcp.d.ts +4 -0
  138. package/dist/plugins/workspace/mcp.js +168 -0
  139. package/dist/plugins/workspace/plugin.d.ts +10 -0
  140. package/dist/plugins/workspace/plugin.js +37 -0
  141. package/dist/plugins/workspace/workspace-helpers.d.ts +9 -0
  142. package/dist/plugins/workspace/workspace-helpers.js +67 -0
  143. package/dist/plugins/workspace/workspace-service.d.ts +46 -0
  144. package/dist/plugins/workspace/workspace-service.js +339 -0
  145. package/dist/plugins/workspace/worktree-review-service.d.ts +16 -0
  146. package/dist/plugins/workspace/worktree-review-service.js +51 -0
  147. package/dist/presentation/http/http-utils.d.ts +7 -0
  148. package/dist/presentation/http/http-utils.js +47 -0
  149. package/dist/presentation/http/oauth-router.d.ts +5 -0
  150. package/dist/presentation/http/oauth-router.js +163 -0
  151. package/dist/web/assets/chatroom-B5p-5n3y.svg +15 -0
  152. package/dist/web/assets/index-BF_mWl1D.js +26 -0
  153. package/dist/web/assets/index-JPZut9tc.css +1 -0
  154. package/dist/web/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  155. package/dist/web/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  156. package/dist/web/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  157. package/dist/web/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  158. package/dist/web/index.html +19 -0
  159. package/package.json +77 -0
@@ -0,0 +1,39 @@
1
+ import { spawn } from "node:child_process";
2
+ import { ChatRoomError } from "../errors/chatroom-error.js";
3
+ import { childEnvironment } from "./child-environment.js";
4
+ export class CommandRunner {
5
+ run(request) {
6
+ return new Promise((resolve, reject) => {
7
+ const child = spawn(request.command, request.args ?? [], {
8
+ cwd: request.cwd,
9
+ env: childEnvironment(request.env),
10
+ stdio: ["ignore", "pipe", "pipe"],
11
+ });
12
+ let stdout = "";
13
+ let stderr = "";
14
+ const timeout = request.timeoutMs
15
+ ? setTimeout(() => child.kill("SIGKILL"), request.timeoutMs)
16
+ : null;
17
+ timeout?.unref();
18
+ child.stdout.setEncoding("utf8");
19
+ child.stderr.setEncoding("utf8");
20
+ child.stdout.on("data", (chunk) => {
21
+ stdout += chunk;
22
+ });
23
+ child.stderr.on("data", (chunk) => {
24
+ stderr += chunk;
25
+ });
26
+ child.once("error", reject);
27
+ child.once("close", (code) => {
28
+ if (timeout)
29
+ clearTimeout(timeout);
30
+ const exitCode = code ?? -1;
31
+ if (exitCode !== 0) {
32
+ reject(new ChatRoomError("PROCESS_FAILED", `${request.command} exited with code ${exitCode}`, { stdout, stderr, exitCode }));
33
+ return;
34
+ }
35
+ resolve({ stdout, stderr, exitCode });
36
+ });
37
+ });
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ export declare const CHATROOM_NAME = "ChatRoom";
2
+ export declare const CHATROOM_VERSION: string;
@@ -0,0 +1,9 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ export const CHATROOM_NAME = "ChatRoom";
4
+ const packageJsonPath = fileURLToPath(new URL("../../../package.json", import.meta.url));
5
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
6
+ if (typeof packageJson.version !== "string" || !packageJson.version) {
7
+ throw new Error("ChatRoom package version is missing");
8
+ }
9
+ export const CHATROOM_VERSION = packageJson.version;
@@ -0,0 +1,6 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ export declare class AppDatabase {
3
+ readonly raw: DatabaseSync;
4
+ constructor(databasePath: string);
5
+ close(): void;
6
+ }
@@ -0,0 +1,104 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ const SCHEMA = `
5
+ CREATE TABLE IF NOT EXISTS workspaces (
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
+ `;
93
+ export class AppDatabase {
94
+ raw;
95
+ constructor(databasePath) {
96
+ mkdirSync(path.dirname(databasePath), { recursive: true, mode: 0o700 });
97
+ this.raw = new DatabaseSync(databasePath);
98
+ this.raw.exec("PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;");
99
+ this.raw.exec(SCHEMA);
100
+ }
101
+ close() {
102
+ this.raw.close();
103
+ }
104
+ }
@@ -0,0 +1,20 @@
1
+ import type { OAuthClientRecord, OAuthCodeRecord, OAuthStateRepository, OAuthTokenRecord } from "../../core/auth/repository.js";
2
+ import type { AppDatabase } from "./app-database.js";
3
+ export declare class OAuthRepository implements OAuthStateRepository {
4
+ private readonly database;
5
+ constructor(database: AppDatabase);
6
+ createClient(client: OAuthClientRecord): void;
7
+ getClient(clientId: string): OAuthClientRecord | null;
8
+ createCode(code: OAuthCodeRecord): void;
9
+ getCode(codeHash: string): OAuthCodeRecord | null;
10
+ consumeCode(codeHash: string, now: string): OAuthCodeRecord | null;
11
+ createToken(tokenHash: string, clientId: string, scope: string, expiresAt: string): void;
12
+ createRefreshToken(tokenHash: string, clientId: string, scope: string, expiresAt: string): void;
13
+ consumeRefreshToken(tokenHash: string, clientId?: string): {
14
+ clientId: string;
15
+ scope: string;
16
+ } | null;
17
+ tokenInfo(tokenHash: string): OAuthTokenRecord | null;
18
+ revokeToken(tokenHash: string): void;
19
+ prune(now: string): number;
20
+ }
@@ -0,0 +1,140 @@
1
+ export class OAuthRepository {
2
+ database;
3
+ constructor(database) {
4
+ this.database = database;
5
+ }
6
+ createClient(client) {
7
+ this.database.raw
8
+ .prepare("INSERT INTO oauth_clients(client_id,name,redirect_uris_json,created_at) VALUES(?,?,?,?)")
9
+ .run(client.clientId, client.name, JSON.stringify(client.redirectUris), client.createdAt);
10
+ }
11
+ getClient(clientId) {
12
+ const row = this.database.raw
13
+ .prepare("SELECT * FROM oauth_clients WHERE client_id=?")
14
+ .get(clientId);
15
+ return row ? clientFromRow(row) : null;
16
+ }
17
+ createCode(code) {
18
+ this.database.raw
19
+ .prepare("INSERT INTO oauth_codes(code_hash,client_id,redirect_uri,code_challenge,scope,expires_at,used_at) VALUES(?,?,?,?,?,?,NULL)")
20
+ .run(code.codeHash, code.clientId, code.redirectUri, code.codeChallenge, code.scope, code.expiresAt);
21
+ }
22
+ getCode(codeHash) {
23
+ const row = this.database.raw
24
+ .prepare("SELECT * FROM oauth_codes WHERE code_hash=? AND used_at IS NULL")
25
+ .get(codeHash);
26
+ return row ? codeFromRow(row) : null;
27
+ }
28
+ consumeCode(codeHash, now) {
29
+ this.database.raw.exec("BEGIN IMMEDIATE");
30
+ try {
31
+ const row = this.database.raw
32
+ .prepare("SELECT * FROM oauth_codes WHERE code_hash=? AND used_at IS NULL AND expires_at > ?")
33
+ .get(codeHash, now);
34
+ if (!row) {
35
+ this.database.raw.exec("COMMIT");
36
+ return null;
37
+ }
38
+ const update = this.database.raw
39
+ .prepare("UPDATE oauth_codes SET used_at=? WHERE code_hash=? AND used_at IS NULL AND expires_at > ?")
40
+ .run(now, codeHash, now);
41
+ if (Number(update.changes) !== 1) {
42
+ this.database.raw.exec("ROLLBACK");
43
+ return null;
44
+ }
45
+ this.database.raw.exec("COMMIT");
46
+ return codeFromRow({ ...row, used_at: now });
47
+ }
48
+ catch (error) {
49
+ this.database.raw.exec("ROLLBACK");
50
+ throw error;
51
+ }
52
+ }
53
+ createToken(tokenHash, clientId, scope, expiresAt) {
54
+ this.database.raw
55
+ .prepare("INSERT INTO oauth_tokens(token_hash,client_id,scope,expires_at,revoked_at,created_at) VALUES(?,?,?,?,NULL,?)")
56
+ .run(tokenHash, clientId, scope, expiresAt, new Date().toISOString());
57
+ }
58
+ createRefreshToken(tokenHash, clientId, scope, expiresAt) {
59
+ this.database.raw
60
+ .prepare("INSERT INTO oauth_refresh_tokens(token_hash,client_id,scope,expires_at,revoked_at,created_at) VALUES(?,?,?,?,NULL,?)")
61
+ .run(tokenHash, clientId, scope, expiresAt, new Date().toISOString());
62
+ }
63
+ consumeRefreshToken(tokenHash, clientId) {
64
+ this.database.raw.exec("BEGIN IMMEDIATE");
65
+ try {
66
+ const now = new Date().toISOString();
67
+ const row = this.database.raw
68
+ .prepare(clientId
69
+ ? "SELECT client_id,scope FROM oauth_refresh_tokens WHERE token_hash=? AND client_id=? AND revoked_at IS NULL AND expires_at > ?"
70
+ : "SELECT client_id,scope FROM oauth_refresh_tokens WHERE token_hash=? AND revoked_at IS NULL AND expires_at > ?")
71
+ .get(...(clientId ? [tokenHash, clientId, now] : [tokenHash, now]));
72
+ if (!row) {
73
+ this.database.raw.exec("COMMIT");
74
+ return null;
75
+ }
76
+ const update = this.database.raw
77
+ .prepare("UPDATE oauth_refresh_tokens SET revoked_at=? WHERE token_hash=? AND revoked_at IS NULL")
78
+ .run(now, tokenHash);
79
+ if (Number(update.changes) !== 1) {
80
+ this.database.raw.exec("ROLLBACK");
81
+ return null;
82
+ }
83
+ this.database.raw.exec("COMMIT");
84
+ return { clientId: row.client_id, scope: row.scope };
85
+ }
86
+ catch (error) {
87
+ this.database.raw.exec("ROLLBACK");
88
+ throw error;
89
+ }
90
+ }
91
+ tokenInfo(tokenHash) {
92
+ const row = this.database.raw
93
+ .prepare("SELECT client_id,scope,expires_at FROM oauth_tokens WHERE token_hash=? AND revoked_at IS NULL AND expires_at > ?")
94
+ .get(tokenHash, new Date().toISOString());
95
+ return row
96
+ ? { clientId: row.client_id, scope: row.scope, expiresAt: row.expires_at }
97
+ : null;
98
+ }
99
+ revokeToken(tokenHash) {
100
+ const now = new Date().toISOString();
101
+ this.database.raw
102
+ .prepare("UPDATE oauth_tokens SET revoked_at=? WHERE token_hash=?")
103
+ .run(now, tokenHash);
104
+ this.database.raw
105
+ .prepare("UPDATE oauth_refresh_tokens SET revoked_at=? WHERE token_hash=?")
106
+ .run(now, tokenHash);
107
+ }
108
+ prune(now) {
109
+ let deleted = 0;
110
+ deleted += Number(this.database.raw
111
+ .prepare("DELETE FROM oauth_codes WHERE expires_at <= ? OR used_at IS NOT NULL")
112
+ .run(now).changes);
113
+ deleted += Number(this.database.raw
114
+ .prepare("DELETE FROM oauth_tokens WHERE expires_at <= ? OR revoked_at IS NOT NULL")
115
+ .run(now).changes);
116
+ deleted += Number(this.database.raw
117
+ .prepare("DELETE FROM oauth_refresh_tokens WHERE expires_at <= ? OR revoked_at IS NOT NULL")
118
+ .run(now).changes);
119
+ return deleted;
120
+ }
121
+ }
122
+ function clientFromRow(row) {
123
+ return {
124
+ clientId: row.client_id,
125
+ name: row.name,
126
+ redirectUris: JSON.parse(row.redirect_uris_json),
127
+ createdAt: row.created_at,
128
+ };
129
+ }
130
+ function codeFromRow(row) {
131
+ return {
132
+ codeHash: row.code_hash,
133
+ clientId: row.client_id,
134
+ redirectUri: row.redirect_uri,
135
+ codeChallenge: row.code_challenge,
136
+ scope: row.scope,
137
+ expiresAt: row.expires_at,
138
+ usedAt: row.used_at,
139
+ };
140
+ }
@@ -0,0 +1,16 @@
1
+ import type { Operation } from "../../core/operations/types.js";
2
+ import type { OperationQuery, OperationRepository as OperationRepositoryPort } from "../../core/operations/repository.js";
3
+ import type { AppDatabase } from "./app-database.js";
4
+ export declare class OperationRepository implements OperationRepositoryPort {
5
+ private readonly database;
6
+ constructor(database: AppDatabase);
7
+ insert(operation: Operation): void;
8
+ update(operation: Operation): void;
9
+ get(operationId: string): Operation | null;
10
+ list(query?: OperationQuery): Operation[];
11
+ reconcileRunning(finishedAt: string): number;
12
+ clearHistory(): {
13
+ deleted: number;
14
+ preserved: number;
15
+ };
16
+ }
@@ -0,0 +1,129 @@
1
+ export class OperationRepository {
2
+ database;
3
+ constructor(database) {
4
+ this.database = database;
5
+ }
6
+ insert(operation) {
7
+ this.database.raw
8
+ .prepare(`
9
+ INSERT INTO operations(
10
+ operation_id, plugin_id, source, action, status, workspace_id, process_id,
11
+ input_json, output_json, error_json, input_truncated, output_truncated,
12
+ started_at, finished_at, duration_ms
13
+ ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
14
+ `)
15
+ .run(...toRowValues(operation));
16
+ }
17
+ update(operation) {
18
+ this.database.raw
19
+ .prepare(`
20
+ UPDATE operations SET
21
+ plugin_id=?, source=?, action=?, status=?, workspace_id=?, process_id=?,
22
+ input_json=?, output_json=?, error_json=?, input_truncated=?, output_truncated=?,
23
+ started_at=?, finished_at=?, duration_ms=?
24
+ WHERE operation_id=?
25
+ `)
26
+ .run(...toRowValues(operation).slice(1), operation.operationId);
27
+ }
28
+ get(operationId) {
29
+ const row = this.database.raw
30
+ .prepare("SELECT * FROM operations WHERE operation_id=?")
31
+ .get(operationId);
32
+ return row ? fromRow(row) : null;
33
+ }
34
+ list(query = {}) {
35
+ const where = [];
36
+ const args = [];
37
+ if (query.pluginId) {
38
+ where.push("plugin_id=?");
39
+ args.push(query.pluginId);
40
+ }
41
+ if (query.status) {
42
+ where.push("status=?");
43
+ args.push(query.status);
44
+ }
45
+ if (query.workspaceId) {
46
+ where.push("workspace_id=?");
47
+ args.push(query.workspaceId);
48
+ }
49
+ const limit = Math.min(Math.max(query.limit ?? 100, 1), 500);
50
+ const offset = Math.max(query.offset ?? 0, 0);
51
+ const filter = where.length ? `WHERE ${where.join(" AND ")}` : "";
52
+ return this.database.raw
53
+ .prepare(`SELECT * FROM operations ${filter} ORDER BY started_at DESC LIMIT ? OFFSET ?`)
54
+ .all(...args, limit, offset).map(fromRow);
55
+ }
56
+ reconcileRunning(finishedAt) {
57
+ const result = this.database.raw
58
+ .prepare(`
59
+ UPDATE operations
60
+ SET status='cancelled', finished_at=?,
61
+ duration_ms=MAX(0, CAST((julianday(?) - julianday(started_at)) * 86400000 AS INTEGER)),
62
+ error_json=?
63
+ WHERE status='running'
64
+ `)
65
+ .run(finishedAt, finishedAt, JSON.stringify({
66
+ code: "INTERRUPTED",
67
+ message: "Runtime stopped before this operation completed",
68
+ }));
69
+ return Number(result.changes);
70
+ }
71
+ clearHistory() {
72
+ const before = this.database.raw
73
+ .prepare("SELECT COUNT(*) AS count FROM operations")
74
+ .get().count;
75
+ this.database.raw
76
+ .prepare("DELETE FROM operations WHERE status != 'running'")
77
+ .run();
78
+ const preserved = this.database.raw
79
+ .prepare("SELECT COUNT(*) AS count FROM operations")
80
+ .get().count;
81
+ return { deleted: before - preserved, preserved };
82
+ }
83
+ }
84
+ function toRowValues(operation) {
85
+ return [
86
+ operation.operationId,
87
+ operation.pluginId,
88
+ operation.source,
89
+ operation.action,
90
+ operation.status,
91
+ operation.workspaceId,
92
+ operation.processId,
93
+ JSON.stringify(operation.input),
94
+ JSON.stringify(operation.output),
95
+ JSON.stringify(operation.error),
96
+ operation.inputTruncated ? 1 : 0,
97
+ operation.outputTruncated ? 1 : 0,
98
+ operation.startedAt,
99
+ operation.finishedAt,
100
+ operation.durationMs,
101
+ ];
102
+ }
103
+ function fromRow(row) {
104
+ return {
105
+ operationId: row.operation_id,
106
+ pluginId: row.plugin_id,
107
+ source: row.source,
108
+ action: row.action,
109
+ status: row.status,
110
+ workspaceId: row.workspace_id,
111
+ processId: row.process_id,
112
+ input: parse(row.input_json),
113
+ output: parse(row.output_json),
114
+ error: parse(row.error_json),
115
+ inputTruncated: Boolean(row.input_truncated),
116
+ outputTruncated: Boolean(row.output_truncated),
117
+ startedAt: row.started_at,
118
+ finishedAt: row.finished_at,
119
+ durationMs: row.duration_ms,
120
+ };
121
+ }
122
+ function parse(value) {
123
+ try {
124
+ return JSON.parse(value);
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
@@ -0,0 +1,11 @@
1
+ import type { PasskeyRecord, PasskeyRepository as PasskeyRepositoryPort } from "../../core/auth/passkey-repository.js";
2
+ import type { AppDatabase } from "./app-database.js";
3
+ export declare class PasskeyRepository implements PasskeyRepositoryPort {
4
+ private readonly database;
5
+ constructor(database: AppDatabase);
6
+ list(): PasskeyRecord[];
7
+ get(credentialId: string): PasskeyRecord | null;
8
+ upsert(record: PasskeyRecord): void;
9
+ remove(credentialId: string): void;
10
+ updateCounter(credentialId: string, counter: number): void;
11
+ }
@@ -0,0 +1,57 @@
1
+ export class PasskeyRepository {
2
+ database;
3
+ constructor(database) {
4
+ this.database = database;
5
+ }
6
+ list() {
7
+ return this.database.raw
8
+ .prepare("SELECT * FROM passkeys ORDER BY last_used_at DESC, created_at DESC")
9
+ .all()
10
+ .map((row) => mapPasskey(row));
11
+ }
12
+ get(credentialId) {
13
+ const row = this.database.raw
14
+ .prepare("SELECT * FROM passkeys WHERE credential_id=?")
15
+ .get(credentialId);
16
+ return row ? mapPasskey(row) : null;
17
+ }
18
+ upsert(record) {
19
+ this.database.raw
20
+ .prepare(`INSERT INTO passkeys(
21
+ credential_id, public_key, counter, transports_json, device_type, backed_up,
22
+ name, created_at, last_used_at
23
+ ) VALUES(?,?,?,?,?,?,?,?,?)
24
+ ON CONFLICT(credential_id) DO UPDATE SET
25
+ public_key=excluded.public_key,
26
+ counter=excluded.counter,
27
+ transports_json=excluded.transports_json,
28
+ device_type=excluded.device_type,
29
+ backed_up=excluded.backed_up,
30
+ name=excluded.name,
31
+ last_used_at=excluded.last_used_at`)
32
+ .run(record.credentialId, Buffer.from(record.publicKey), record.counter, JSON.stringify(record.transports), record.deviceType, record.backedUp ? 1 : 0, record.name, record.createdAt, record.lastUsedAt);
33
+ }
34
+ remove(credentialId) {
35
+ this.database.raw
36
+ .prepare("DELETE FROM passkeys WHERE credential_id=?")
37
+ .run(credentialId);
38
+ }
39
+ updateCounter(credentialId, counter) {
40
+ this.database.raw
41
+ .prepare("UPDATE passkeys SET counter=?, last_used_at=? WHERE credential_id=?")
42
+ .run(counter, new Date().toISOString(), credentialId);
43
+ }
44
+ }
45
+ function mapPasskey(row) {
46
+ return {
47
+ credentialId: row.credential_id,
48
+ publicKey: new Uint8Array(row.public_key),
49
+ counter: row.counter,
50
+ transports: JSON.parse(row.transports_json),
51
+ deviceType: row.device_type,
52
+ backedUp: Boolean(row.backed_up),
53
+ name: row.name,
54
+ createdAt: row.created_at,
55
+ lastUsedAt: row.last_used_at,
56
+ };
57
+ }
@@ -0,0 +1,10 @@
1
+ import type { WebSessionRepository as WebSessionRepositoryPort } from "../../core/auth/session-repository.js";
2
+ import type { AppDatabase } from "./app-database.js";
3
+ export declare class WebSessionRepository implements WebSessionRepositoryPort {
4
+ private readonly database;
5
+ constructor(database: AppDatabase);
6
+ create(tokenHash: string, expiresAt: string): void;
7
+ valid(tokenHash: string, now: string): boolean;
8
+ revoke(tokenHash: string): void;
9
+ prune(now: string): number;
10
+ }
@@ -0,0 +1,26 @@
1
+ export class WebSessionRepository {
2
+ database;
3
+ constructor(database) {
4
+ this.database = database;
5
+ }
6
+ create(tokenHash, expiresAt) {
7
+ this.database.raw
8
+ .prepare("INSERT INTO web_sessions(token_hash,expires_at,created_at) VALUES(?,?,?)")
9
+ .run(tokenHash, expiresAt, new Date().toISOString());
10
+ }
11
+ valid(tokenHash, now) {
12
+ return Boolean(this.database.raw
13
+ .prepare("SELECT 1 AS ok FROM web_sessions WHERE token_hash=? AND expires_at > ?")
14
+ .get(tokenHash, now));
15
+ }
16
+ revoke(tokenHash) {
17
+ this.database.raw
18
+ .prepare("DELETE FROM web_sessions WHERE token_hash=?")
19
+ .run(tokenHash);
20
+ }
21
+ prune(now) {
22
+ return Number(this.database.raw
23
+ .prepare("DELETE FROM web_sessions WHERE expires_at <= ?")
24
+ .run(now).changes);
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ import type { AddressInfo } from "node:net";
2
+ import type { McpHttpHandler } from "@modelcontextprotocol/server";
3
+ import type { ChatRoomConfig } from "../../config/types.js";
4
+ import type { WebRuntime } from "../../plugins/web/runtime.js";
5
+ import type { RuntimeEventBus } from "../../app/event-bus.js";
6
+ import type { ExternalAccessRegistry } from "../../app/external-access-registry.js";
7
+ import type { AuthService } from "../../auth/auth-service.js";
8
+ import type { PasskeyService } from "../../auth/passkey-service.js";
9
+ import type { CloudController } from "../../plugins/cloud/controller.js";
10
+ export declare class HttpServer {
11
+ private readonly config;
12
+ private readonly application;
13
+ private readonly eventBus;
14
+ private readonly auth;
15
+ private readonly passkeys;
16
+ private readonly mcp;
17
+ private readonly cloud;
18
+ private server;
19
+ private readonly ingress;
20
+ private readonly startedAt;
21
+ private mcpRequestCount;
22
+ constructor(config: ChatRoomConfig, application: WebRuntime, eventBus: RuntimeEventBus, auth: AuthService, passkeys: PasskeyService, mcp: McpHttpHandler, externalAccess: ExternalAccessRegistry, cloud: CloudController);
23
+ start(): Promise<void>;
24
+ address(): AddressInfo | null;
25
+ close(): Promise<void>;
26
+ }