@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,166 @@
1
+ /** Express/HTTP transport host for REST, OAuth, MCP, plugins, and the built WebUI. */
2
+ import { createServer } from "node:http";
3
+ import path from "node:path";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import express from "express";
7
+ import { toNodeHandler } from "@modelcontextprotocol/node";
8
+ import { createApiRouter } from "../../plugins/web/http/api-router.js";
9
+ import { createOAuthRouter } from "../../presentation/http/oauth-router.js";
10
+ import { errorMiddleware } from "../../presentation/http/http-utils.js";
11
+ import { IngressPolicy } from "../../auth/ingress-policy.js";
12
+ import { CHATROOM_VERSION } from "../../core/runtime/identity.js";
13
+ const WEB_UI_RESERVED_PREFIXES = [
14
+ "/api",
15
+ "/mcp",
16
+ "/oauth",
17
+ "/.well-known",
18
+ "/assets",
19
+ ];
20
+ export class HttpServer {
21
+ config;
22
+ application;
23
+ eventBus;
24
+ auth;
25
+ passkeys;
26
+ mcp;
27
+ cloud;
28
+ server = null;
29
+ ingress;
30
+ startedAt = Date.now();
31
+ mcpRequestCount = 0;
32
+ constructor(config, application, eventBus, auth, passkeys, mcp, externalAccess, cloud) {
33
+ this.config = config;
34
+ this.application = application;
35
+ this.eventBus = eventBus;
36
+ this.auth = auth;
37
+ this.passkeys = passkeys;
38
+ this.mcp = mcp;
39
+ this.cloud = cloud;
40
+ this.ingress = new IngressPolicy(config, externalAccess);
41
+ }
42
+ async start() {
43
+ const app = express();
44
+ app.disable("x-powered-by");
45
+ app.set("trust proxy", false);
46
+ app.use(hostValidation(this.ingress));
47
+ app.use(express.json({ limit: "2mb" }));
48
+ app.use(express.urlencoded({ extended: false, limit: "64kb" }));
49
+ app.use(createOAuthRouter(this.auth, this.ingress));
50
+ app.use("/api", webMutationOrigin(this.ingress));
51
+ app.use("/api", createApiRouter(this.application, this.eventBus, this.auth, this.passkeys, this.ingress, this.cloud, () => ({
52
+ version: CHATROOM_VERSION,
53
+ mcpRequests: this.mcpRequestCount,
54
+ uptimeMinutes: Math.floor((Date.now() - this.startedAt) / 60000),
55
+ })));
56
+ const nodeMcp = toNodeHandler(this.mcp, {
57
+ onerror: (error) => console.error("[mcp]", error),
58
+ });
59
+ app.all("/mcp", (_req, _res, next) => {
60
+ this.mcpRequestCount += 1;
61
+ next();
62
+ }, mcpAuthentication(this.auth, this.ingress), (req, res) => {
63
+ void nodeMcp(req, res, req.body);
64
+ });
65
+ const webRoot = fileURLToPath(new URL("../../web/", import.meta.url));
66
+ const webIndexPath = path.join(webRoot, "index.html");
67
+ if (existsSync(webIndexPath)) {
68
+ const webIndex = readFileSync(webIndexPath, "utf8");
69
+ app.use("/assets", express.static(path.join(webRoot, "assets"), {
70
+ index: false,
71
+ maxAge: "1y",
72
+ immutable: true,
73
+ }));
74
+ app.use((req, res, next) => {
75
+ const isReserved = WEB_UI_RESERVED_PREFIXES.some((prefix) => req.path === prefix || req.path.startsWith(`${prefix}/`));
76
+ if (req.method !== "GET" || isReserved || !req.accepts("html")) {
77
+ next();
78
+ return;
79
+ }
80
+ res.setHeader("Cache-Control", "no-cache");
81
+ res.status(200).type("html").send(webIndex);
82
+ });
83
+ }
84
+ app.use(errorMiddleware);
85
+ this.server = createServer(app);
86
+ await new Promise((resolve, reject) => {
87
+ this.server.once("error", reject);
88
+ this.server.listen(this.config.server.port, this.config.server.host, () => {
89
+ this.server.off("error", reject);
90
+ resolve();
91
+ });
92
+ });
93
+ }
94
+ address() {
95
+ const value = this.server?.address();
96
+ return value && typeof value === "object" ? value : null;
97
+ }
98
+ async close() {
99
+ const server = this.server;
100
+ if (!server)
101
+ return;
102
+ const closed = new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
103
+ await this.mcp.close();
104
+ server.closeAllConnections();
105
+ await closed;
106
+ this.server = null;
107
+ }
108
+ }
109
+ function webMutationOrigin(ingress) {
110
+ return (req, res, next) => {
111
+ if (req.method === "GET" ||
112
+ req.method === "HEAD" ||
113
+ req.method === "OPTIONS") {
114
+ next();
115
+ return;
116
+ }
117
+ const expectedOrigin = ingress.expectedWebOrigin(req);
118
+ if (!expectedOrigin) {
119
+ next();
120
+ return;
121
+ }
122
+ if (req.headers.origin !== expectedOrigin) {
123
+ res.status(403).json({
124
+ error: {
125
+ code: "FORBIDDEN",
126
+ message: "Request origin is not allowed",
127
+ },
128
+ });
129
+ return;
130
+ }
131
+ next();
132
+ };
133
+ }
134
+ function hostValidation(ingress) {
135
+ return (req, res, next) => {
136
+ const hostname = req.hostname;
137
+ if (!hostname || !ingress.allowedHosts().has(hostname)) {
138
+ res.status(403).json({
139
+ error: { code: "FORBIDDEN", message: "Host header is not allowed" },
140
+ });
141
+ return;
142
+ }
143
+ next();
144
+ };
145
+ }
146
+ function mcpAuthentication(auth, ingress) {
147
+ return (req, res, next) => {
148
+ if (!ingress.requiresMcpAuth(req)) {
149
+ next();
150
+ return;
151
+ }
152
+ const header = req.headers.authorization;
153
+ const token = typeof header === "string" && header.startsWith("Bearer ")
154
+ ? header.slice(7)
155
+ : null;
156
+ const info = token ? auth.verifyMcpToken(token) : null;
157
+ if (!info) {
158
+ const base = ingress.mcpBaseUrl(req);
159
+ res.setHeader("WWW-Authenticate", `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource/mcp"`);
160
+ res.status(401).json({ error: "invalid_token" });
161
+ return;
162
+ }
163
+ req.auth = info;
164
+ next();
165
+ };
166
+ }
@@ -0,0 +1,3 @@
1
+ import { type McpHttpHandler } from "@modelcontextprotocol/server";
2
+ import type { PluginManager } from "../../plugins/plugin-manager.js";
3
+ export declare function createChatRoomMcpHandler(plugins: PluginManager): McpHttpHandler;
@@ -0,0 +1,16 @@
1
+ import { createMcpHandler, McpServer, } from "@modelcontextprotocol/server";
2
+ import { CHATROOM_NAME, CHATROOM_VERSION, } from "../../core/runtime/identity.js";
3
+ export function createChatRoomMcpHandler(plugins) {
4
+ return createMcpHandler(() => createServer(plugins), {
5
+ legacy: "stateless",
6
+ responseMode: "auto",
7
+ });
8
+ }
9
+ function createServer(plugins) {
10
+ const server = new McpServer({ name: CHATROOM_NAME, version: CHATROOM_VERSION }, {
11
+ instructions: "Workspace lifecycle is independent from MCP connections. " +
12
+ "Use WorkspaceId only with workspace tools and ProcessId only with process tools.",
13
+ });
14
+ plugins.registerMcp(server);
15
+ return server;
16
+ }
@@ -0,0 +1,105 @@
1
+ import { type CallToolResult } from "@modelcontextprotocol/server";
2
+ import { z } from "zod";
3
+ export declare const closedRead: {
4
+ readOnlyHint: true;
5
+ destructiveHint: false;
6
+ idempotentHint: true;
7
+ openWorldHint: false;
8
+ };
9
+ export declare const localWrite: {
10
+ readOnlyHint: false;
11
+ destructiveHint: true;
12
+ idempotentHint: true;
13
+ openWorldHint: false;
14
+ };
15
+ export declare const localMutation: {
16
+ readOnlyHint: false;
17
+ destructiveHint: false;
18
+ idempotentHint: false;
19
+ openWorldHint: false;
20
+ };
21
+ export declare const destructiveLocalMutation: {
22
+ readOnlyHint: false;
23
+ destructiveHint: true;
24
+ idempotentHint: false;
25
+ openWorldHint: false;
26
+ };
27
+ export declare const openWorldMutation: {
28
+ readOnlyHint: false;
29
+ destructiveHint: true;
30
+ idempotentHint: false;
31
+ openWorldHint: true;
32
+ };
33
+ export declare const workspaceOutputSchema: z.ZodObject<{
34
+ id: z.ZodString;
35
+ root: z.ZodString;
36
+ sourceRoot: z.ZodString;
37
+ mode: z.ZodEnum<{
38
+ worktree: "worktree";
39
+ checkout: "checkout";
40
+ }>;
41
+ createdAt: z.ZodString;
42
+ lastUsedAt: z.ZodString;
43
+ instructions: z.ZodArray<z.ZodString>;
44
+ skills: z.ZodArray<z.ZodString>;
45
+ capabilities: z.ZodObject<{
46
+ filesystem: z.ZodLiteral<"read-write">;
47
+ git: z.ZodBoolean;
48
+ skills: z.ZodBoolean;
49
+ }, z.core.$strip>;
50
+ }, z.core.$strip>;
51
+ export declare const fileReadOutputSchema: z.ZodObject<{
52
+ content: z.ZodString;
53
+ bytes: z.ZodNumber;
54
+ truncated: z.ZodBoolean;
55
+ }, z.core.$strip>;
56
+ export declare const fileWriteOutputSchema: z.ZodObject<{
57
+ path: z.ZodString;
58
+ bytes: z.ZodNumber;
59
+ }, z.core.$strip>;
60
+ export declare const fileInfoSchema: z.ZodObject<{
61
+ path: z.ZodString;
62
+ type: z.ZodEnum<{
63
+ file: "file";
64
+ directory: "directory";
65
+ symlink: "symlink";
66
+ }>;
67
+ size: z.ZodNumber;
68
+ modifiedAt: z.ZodString;
69
+ }, z.core.$strip>;
70
+ export declare const searchMatchSchema: z.ZodObject<{
71
+ path: z.ZodString;
72
+ line: z.ZodNumber;
73
+ column: z.ZodNumber;
74
+ preview: z.ZodString;
75
+ }, z.core.$strip>;
76
+ export declare const changeSetSchema: z.ZodObject<{
77
+ files: z.ZodArray<z.ZodString>;
78
+ replacements: z.ZodNumber;
79
+ bytesBefore: z.ZodNumber;
80
+ bytesAfter: z.ZodNumber;
81
+ }, z.core.$strip>;
82
+ export declare const processSnapshotSchema: z.ZodObject<{
83
+ processId: z.ZodString;
84
+ command: z.ZodString;
85
+ args: z.ZodArray<z.ZodString>;
86
+ cwd: z.ZodString;
87
+ pid: z.ZodNullable<z.ZodNumber>;
88
+ state: z.ZodEnum<{
89
+ running: "running";
90
+ exited: "exited";
91
+ killed: "killed";
92
+ failed: "failed";
93
+ }>;
94
+ startedAt: z.ZodString;
95
+ finishedAt: z.ZodNullable<z.ZodString>;
96
+ durationMs: z.ZodNumber;
97
+ exitCode: z.ZodNullable<z.ZodNumber>;
98
+ signal: z.ZodNullable<z.ZodString>;
99
+ stdout: z.ZodString;
100
+ stderr: z.ZodString;
101
+ outputTruncated: z.ZodBoolean;
102
+ timedOut: z.ZodBoolean;
103
+ operationId: z.ZodString;
104
+ }, z.core.$strip>;
105
+ export declare function mcpTool<T>(operation: (input: T) => Promise<unknown> | unknown): (input: T) => Promise<CallToolResult>;
@@ -0,0 +1,124 @@
1
+ import { z } from "zod";
2
+ import { asChatRoomError } from "../../core/errors/chatroom-error.js";
3
+ export const closedRead = {
4
+ readOnlyHint: true,
5
+ destructiveHint: false,
6
+ idempotentHint: true,
7
+ openWorldHint: false,
8
+ };
9
+ export const localWrite = {
10
+ readOnlyHint: false,
11
+ destructiveHint: true,
12
+ idempotentHint: true,
13
+ openWorldHint: false,
14
+ };
15
+ export const localMutation = {
16
+ readOnlyHint: false,
17
+ destructiveHint: false,
18
+ idempotentHint: false,
19
+ openWorldHint: false,
20
+ };
21
+ export const destructiveLocalMutation = {
22
+ readOnlyHint: false,
23
+ destructiveHint: true,
24
+ idempotentHint: false,
25
+ openWorldHint: false,
26
+ };
27
+ export const openWorldMutation = {
28
+ readOnlyHint: false,
29
+ destructiveHint: true,
30
+ idempotentHint: false,
31
+ openWorldHint: true,
32
+ };
33
+ export const workspaceOutputSchema = z.object({
34
+ id: z.string(),
35
+ root: z.string(),
36
+ sourceRoot: z.string(),
37
+ mode: z.enum(["checkout", "worktree"]),
38
+ createdAt: z.string(),
39
+ lastUsedAt: z.string(),
40
+ instructions: z.array(z.string()),
41
+ skills: z.array(z.string()),
42
+ capabilities: z.object({
43
+ filesystem: z.literal("read-write"),
44
+ git: z.boolean(),
45
+ skills: z.boolean(),
46
+ }),
47
+ });
48
+ export const fileReadOutputSchema = z.object({
49
+ content: z.string(),
50
+ bytes: z.number().int().nonnegative(),
51
+ truncated: z.boolean(),
52
+ });
53
+ export const fileWriteOutputSchema = z.object({
54
+ path: z.string(),
55
+ bytes: z.number().int().nonnegative(),
56
+ });
57
+ export const fileInfoSchema = z.object({
58
+ path: z.string(),
59
+ type: z.enum(["file", "directory", "symlink"]),
60
+ size: z.number().int().nonnegative(),
61
+ modifiedAt: z.string(),
62
+ });
63
+ export const searchMatchSchema = z.object({
64
+ path: z.string(),
65
+ line: z.number().int().positive(),
66
+ column: z.number().int().positive(),
67
+ preview: z.string(),
68
+ });
69
+ export const changeSetSchema = z.object({
70
+ files: z.array(z.string()),
71
+ replacements: z.number().int().nonnegative(),
72
+ bytesBefore: z.number().int().nonnegative(),
73
+ bytesAfter: z.number().int().nonnegative(),
74
+ });
75
+ export const processSnapshotSchema = z.object({
76
+ processId: z.string(),
77
+ command: z.string(),
78
+ args: z.array(z.string()),
79
+ cwd: z.string(),
80
+ pid: z.number().int().nullable(),
81
+ state: z.enum(["running", "exited", "killed", "failed"]),
82
+ startedAt: z.string(),
83
+ finishedAt: z.string().nullable(),
84
+ durationMs: z.number().nonnegative(),
85
+ exitCode: z.number().int().nullable(),
86
+ signal: z.string().nullable(),
87
+ stdout: z.string(),
88
+ stderr: z.string(),
89
+ outputTruncated: z.boolean(),
90
+ timedOut: z.boolean(),
91
+ operationId: z.string(),
92
+ });
93
+ export function mcpTool(operation) {
94
+ return async (input) => {
95
+ try {
96
+ const value = await operation(input);
97
+ return {
98
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
99
+ structuredContent: asRecord(value),
100
+ };
101
+ }
102
+ catch (error) {
103
+ const normalized = asChatRoomError(error);
104
+ return {
105
+ isError: true,
106
+ content: [
107
+ {
108
+ type: "text",
109
+ text: JSON.stringify({
110
+ code: normalized.code,
111
+ message: normalized.message,
112
+ details: normalized.details ?? null,
113
+ }),
114
+ },
115
+ ],
116
+ };
117
+ }
118
+ };
119
+ }
120
+ function asRecord(value) {
121
+ return value && typeof value === "object" && !Array.isArray(value)
122
+ ? value
123
+ : { value };
124
+ }
@@ -0,0 +1,23 @@
1
+ import type { Operation, OperationStart, OperationStatus } from "../core/operations/types.js";
2
+ import type { OperationQuery, OperationRepository } from "../core/operations/repository.js";
3
+ import type { RuntimeEventBus } from "../app/event-bus.js";
4
+ /** Persists redacted, bounded operations produced by ChatRoom plugins. */
5
+ export declare class OperationLog {
6
+ private readonly repository;
7
+ private readonly eventBus;
8
+ private readonly maxPayloadBytes;
9
+ private readonly redactor;
10
+ constructor(repository: OperationRepository, eventBus: RuntimeEventBus, maxPayloadBytes: number);
11
+ start(request: OperationStart): Operation;
12
+ finish(operationId: string, status: Exclude<OperationStatus, "running">, output?: unknown, error?: unknown): Operation;
13
+ updateRunning(operationId: string, output: unknown): Operation;
14
+ run<T>(request: OperationStart, action: () => Promise<T>): Promise<T>;
15
+ list(query?: OperationQuery): Operation[];
16
+ get(operationId: string): Operation | null;
17
+ clearHistory(): {
18
+ deleted: number;
19
+ preserved: number;
20
+ };
21
+ reconcileInterrupted(): number;
22
+ private require;
23
+ }
@@ -0,0 +1,103 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { boundJson } from "../core/operations/bounded-value.js";
3
+ import { SecretRedactor } from "../core/operations/redactor.js";
4
+ import { asChatRoomError, ChatRoomError, } from "../core/errors/chatroom-error.js";
5
+ /** Persists redacted, bounded operations produced by ChatRoom plugins. */
6
+ export class OperationLog {
7
+ repository;
8
+ eventBus;
9
+ maxPayloadBytes;
10
+ redactor = new SecretRedactor();
11
+ constructor(repository, eventBus, maxPayloadBytes) {
12
+ this.repository = repository;
13
+ this.eventBus = eventBus;
14
+ this.maxPayloadBytes = maxPayloadBytes;
15
+ }
16
+ start(request) {
17
+ const input = boundJson(this.redactor.redact(request.input ?? null), this.maxPayloadBytes);
18
+ const operation = {
19
+ operationId: randomUUID(),
20
+ pluginId: request.pluginId,
21
+ source: request.source,
22
+ action: request.action,
23
+ status: "running",
24
+ workspaceId: request.workspaceId ?? null,
25
+ processId: request.processId ?? null,
26
+ input: input.value,
27
+ output: null,
28
+ error: null,
29
+ inputTruncated: input.truncated,
30
+ outputTruncated: false,
31
+ startedAt: new Date().toISOString(),
32
+ finishedAt: null,
33
+ durationMs: null,
34
+ };
35
+ this.repository.insert(operation);
36
+ this.eventBus.emit({ type: "operation", operation });
37
+ return operation;
38
+ }
39
+ finish(operationId, status, output, error) {
40
+ const operation = this.require(operationId);
41
+ const finishedAt = new Date();
42
+ const bounded = boundJson(this.redactor.redact(output ?? null), this.maxPayloadBytes);
43
+ operation.status = status;
44
+ operation.output = bounded.value;
45
+ operation.outputTruncated = bounded.truncated;
46
+ operation.error = error
47
+ ? boundJson(this.redactor.redact(error), this.maxPayloadBytes).value
48
+ : null;
49
+ operation.finishedAt = finishedAt.toISOString();
50
+ operation.durationMs = Math.max(0, finishedAt.getTime() - new Date(operation.startedAt).getTime());
51
+ this.repository.update(operation);
52
+ this.eventBus.emit({ type: "operation", operation });
53
+ return operation;
54
+ }
55
+ updateRunning(operationId, output) {
56
+ const operation = this.require(operationId);
57
+ if (operation.status !== "running")
58
+ return operation;
59
+ const bounded = boundJson(this.redactor.redact(output), this.maxPayloadBytes);
60
+ operation.output = bounded.value;
61
+ operation.outputTruncated = bounded.truncated;
62
+ this.repository.update(operation);
63
+ this.eventBus.emit({ type: "operation", operation });
64
+ return operation;
65
+ }
66
+ async run(request, action) {
67
+ const operation = this.start(request);
68
+ try {
69
+ const result = await action();
70
+ this.finish(operation.operationId, "success", result);
71
+ return result;
72
+ }
73
+ catch (error) {
74
+ const normalized = asChatRoomError(error);
75
+ this.finish(operation.operationId, "error", null, {
76
+ code: normalized.code,
77
+ message: normalized.message,
78
+ details: normalized.details,
79
+ });
80
+ throw error;
81
+ }
82
+ }
83
+ list(query) {
84
+ return this.repository.list(query);
85
+ }
86
+ get(operationId) {
87
+ return this.repository.get(operationId);
88
+ }
89
+ clearHistory() {
90
+ const result = this.repository.clearHistory();
91
+ this.eventBus.emit({ type: "operations-cleared", ...result });
92
+ return result;
93
+ }
94
+ reconcileInterrupted() {
95
+ return this.repository.reconcileRunning(new Date().toISOString());
96
+ }
97
+ require(operationId) {
98
+ const operation = this.repository.get(operationId);
99
+ if (!operation)
100
+ throw new ChatRoomError("NOT_FOUND", `Operation not found: ${operationId}`);
101
+ return operation;
102
+ }
103
+ }
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { type CloudEntitlement, type CloudLeaseState, type CloudServiceId } from "./types.js";
3
+ declare const recoveryCredentialSchema: z.ZodObject<{
4
+ recoveryKey: z.ZodString;
5
+ }, z.core.$strict>;
6
+ declare const sessionSchema: z.ZodObject<{
7
+ purchaseToken: z.ZodString;
8
+ managementUrl: z.ZodString;
9
+ expiresAt: z.ZodString;
10
+ }, z.core.$strict>;
11
+ declare const restoreSchema: z.ZodObject<{
12
+ entitlements: z.ZodArray<z.ZodObject<{
13
+ service: z.ZodEnum<{
14
+ remote_mcp: "remote_mcp";
15
+ remote_web: "remote_web";
16
+ }>;
17
+ status: z.ZodLiteral<"active">;
18
+ sourceProvider: z.ZodString;
19
+ sourceId: z.ZodString;
20
+ validUntil: z.ZodNullable<z.ZodString>;
21
+ }, z.core.$strict>>;
22
+ customer: z.ZodObject<{
23
+ id: z.ZodString;
24
+ }, z.core.$strict>;
25
+ publicPrefix: z.ZodNullable<z.ZodString>;
26
+ recoveryKey: z.ZodString;
27
+ }, z.core.$strict>;
28
+ declare const prefixSchema: z.ZodObject<{
29
+ publicPrefix: z.ZodString;
30
+ mcpUrl: z.ZodString;
31
+ webUrl: z.ZodString;
32
+ }, z.core.$strict>;
33
+ export declare class CloudApiClient {
34
+ private readonly baseUrl;
35
+ constructor(baseUrl: string);
36
+ createManagementSession(identity: DeviceIdentity): Promise<z.infer<typeof sessionSchema>>;
37
+ status(identity: DeviceIdentity, purchaseToken: string | null): Promise<{
38
+ managementSessionActive: boolean;
39
+ entitlements: CloudEntitlement[];
40
+ customer: {
41
+ id: string;
42
+ } | null;
43
+ publicPrefix: string | null;
44
+ }>;
45
+ restore(identity: DeviceIdentity, recoveryKey: string): Promise<z.infer<typeof restoreSchema>>;
46
+ replaceRecoveryKey(identity: DeviceIdentity): Promise<z.infer<typeof recoveryCredentialSchema>>;
47
+ setPrefix(identity: DeviceIdentity, prefix: string): Promise<z.infer<typeof prefixSchema>>;
48
+ lease(identity: DeviceIdentity, requestedServices: CloudServiceId[]): Promise<CloudLeaseState>;
49
+ private devicePost;
50
+ }
51
+ export interface DeviceIdentity {
52
+ installationId: string;
53
+ devicePublicKey: string;
54
+ devicePrivateKey: string;
55
+ }
56
+ export {};