@zhanguym/chatroom 1.0.0 → 1.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/dist/app/application.d.ts +0 -1
  2. package/dist/app/application.js +8 -7
  3. package/dist/app/event-bus.d.ts +13 -10
  4. package/dist/app/external-access-registry.d.ts +1 -0
  5. package/dist/app/external-access-registry.js +3 -0
  6. package/dist/app/git-access.d.ts +50 -0
  7. package/dist/app/git-access.js +2 -0
  8. package/dist/app/service-registry.d.ts +11 -0
  9. package/dist/app/service-registry.js +17 -0
  10. package/dist/app/workspace-access.d.ts +24 -0
  11. package/dist/app/workspace-access.js +2 -0
  12. package/dist/auth/ingress-policy.d.ts +2 -0
  13. package/dist/auth/ingress-policy.js +14 -2
  14. package/dist/auth/passkey-service.js +1 -1
  15. package/dist/cli/run-cli.js +1 -1
  16. package/dist/core/fs/safe-directory.d.ts +9 -0
  17. package/dist/core/fs/safe-directory.js +129 -0
  18. package/dist/core/operations/repository.d.ts +2 -0
  19. package/dist/core/runtime/line-process.d.ts +19 -0
  20. package/dist/core/runtime/line-process.js +51 -0
  21. package/dist/infrastructure/database/operation-repository.js +8 -2
  22. package/dist/infrastructure/database/schema.d.ts +1 -1
  23. package/dist/infrastructure/database/schema.js +62 -0
  24. package/dist/infrastructure/http/http-server.js +1 -1
  25. package/dist/infrastructure/logging/system-log.js +2 -1
  26. package/dist/native/linux/chatroom-computer-helper +0 -0
  27. package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
  28. package/dist/native/windows/chatroom-computer-helper.exe +0 -0
  29. package/dist/operations/operation-log.js +16 -4
  30. package/dist/plugins/agent/async-queue.d.ts +10 -0
  31. package/dist/plugins/agent/async-queue.js +49 -0
  32. package/dist/plugins/agent/mcp.d.ts +3 -0
  33. package/dist/plugins/agent/mcp.js +502 -0
  34. package/dist/plugins/agent/plugin.d.ts +4 -0
  35. package/dist/plugins/agent/plugin.js +35 -0
  36. package/dist/plugins/agent/provider.d.ts +132 -0
  37. package/dist/plugins/agent/providers/codex/mapping.d.ts +56 -0
  38. package/dist/plugins/agent/providers/codex/mapping.js +427 -0
  39. package/dist/plugins/agent/providers/codex/provider.d.ts +84 -0
  40. package/dist/plugins/agent/providers/codex/provider.js +828 -0
  41. package/dist/plugins/agent/providers/codex/rpc.d.ts +40 -0
  42. package/dist/plugins/agent/providers/codex/rpc.js +227 -0
  43. package/dist/plugins/agent/providers/common.d.ts +11 -0
  44. package/dist/plugins/agent/providers/common.js +75 -0
  45. package/dist/plugins/agent/providers/index.d.ts +3 -0
  46. package/dist/plugins/agent/providers/index.js +4 -0
  47. package/dist/plugins/agent/repository.d.ts +21 -0
  48. package/dist/plugins/agent/repository.js +226 -0
  49. package/dist/plugins/agent/runtime-config.d.ts +5 -0
  50. package/dist/plugins/agent/runtime-config.js +60 -0
  51. package/dist/plugins/agent/runtime-files.d.ts +8 -0
  52. package/dist/plugins/agent/runtime-files.js +170 -0
  53. package/dist/plugins/agent/runtime-state.d.ts +17 -0
  54. package/dist/plugins/agent/runtime-state.js +133 -0
  55. package/dist/plugins/agent/runtime.d.ts +60 -0
  56. package/dist/plugins/agent/runtime.js +928 -0
  57. package/dist/plugins/agent/store.d.ts +17 -0
  58. package/dist/plugins/agent/store.js +1 -0
  59. package/dist/plugins/agent/types.d.ts +320 -0
  60. package/dist/plugins/agent/types.js +13 -0
  61. package/dist/plugins/cloud/plugin.d.ts +1 -1
  62. package/dist/plugins/cloud/plugin.js +3 -3
  63. package/dist/plugins/cloud/tunnel-client.js +6 -7
  64. package/dist/plugins/computer/computer-native-backend.js +9 -1
  65. package/dist/plugins/computer/computer-native-host.js +21 -5
  66. package/dist/plugins/computer/computer-schemas.d.ts +7 -7
  67. package/dist/plugins/computer/computer-service.d.ts +3 -0
  68. package/dist/plugins/computer/computer-service.js +33 -1
  69. package/dist/plugins/computer/plugin.d.ts +1 -1
  70. package/dist/plugins/computer/plugin.js +1 -1
  71. package/dist/plugins/computer/types.d.ts +1 -0
  72. package/dist/plugins/git/git-service.d.ts +3 -2
  73. package/dist/plugins/git/git-service.js +37 -1
  74. package/dist/plugins/git/plugin.d.ts +0 -2
  75. package/dist/plugins/git/plugin.js +3 -3
  76. package/dist/plugins/process/plugin.d.ts +1 -1
  77. package/dist/plugins/process/plugin.js +3 -3
  78. package/dist/plugins/process/process-supervisor.d.ts +1 -0
  79. package/dist/plugins/process/process-supervisor.js +15 -4
  80. package/dist/plugins/types.d.ts +1 -11
  81. package/dist/plugins/types.js +1 -17
  82. package/dist/plugins/web/api-types.d.ts +20 -5
  83. package/dist/plugins/web/http/agent-api-router.d.ts +3 -0
  84. package/dist/plugins/web/http/agent-api-router.js +254 -0
  85. package/dist/plugins/web/http/api-router.js +9 -0
  86. package/dist/plugins/web/http/computer-api-router.js +21 -6
  87. package/dist/plugins/web/http/process-api-router.js +3 -0
  88. package/dist/plugins/web/http/workspace-api-router.js +198 -8
  89. package/dist/plugins/web/plugin.d.ts +1 -1
  90. package/dist/plugins/web/plugin.js +5 -4
  91. package/dist/plugins/web/runtime.d.ts +6 -3
  92. package/dist/plugins/web/runtime.js +6 -1
  93. package/dist/plugins/workspace/mcp.d.ts +1 -1
  94. package/dist/plugins/workspace/mcp.js +125 -1
  95. package/dist/plugins/workspace/metadata.d.ts +1 -1
  96. package/dist/plugins/workspace/plugin.d.ts +1 -1
  97. package/dist/plugins/workspace/plugin.js +9 -3
  98. package/dist/plugins/workspace/types.d.ts +1 -18
  99. package/dist/plugins/workspace/workspace-fs.d.ts +19 -3
  100. package/dist/plugins/workspace/workspace-fs.js +112 -120
  101. package/dist/plugins/workspace/workspace-root-policy.d.ts +9 -0
  102. package/dist/plugins/workspace/workspace-root-policy.js +56 -0
  103. package/dist/plugins/workspace/workspace-service.d.ts +13 -6
  104. package/dist/plugins/workspace/workspace-service.js +57 -49
  105. package/dist/web/assets/AgentView-BGLIBoxF.css +1 -0
  106. package/dist/web/assets/AgentView-BwL3S7sb.js +4 -0
  107. package/dist/web/assets/CloudView-DH4sOXJr.js +1 -0
  108. package/dist/web/assets/{CloudView-CHjVbp-5.css → CloudView-DVVvwuWW.css} +1 -1
  109. package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-BaC1eGzG.js +3 -0
  110. package/dist/web/assets/ComputerView-BJSjCxFr.js +1 -0
  111. package/dist/web/assets/ComputerView-DWzfSYJ7.css +1 -0
  112. package/dist/web/assets/MarkdownContent-AdAmmDn5.js +16 -0
  113. package/dist/web/assets/MarkdownContent-B_5zTG8c.css +1 -0
  114. package/dist/web/assets/McpToolsView-CPaWUG9h.js +1 -0
  115. package/dist/web/assets/McpToolsView-DjxPbTHj.css +1 -0
  116. package/dist/web/assets/OperationsView-BjTY2lIh.css +1 -0
  117. package/dist/web/assets/OperationsView-D2FtLkHn.js +4 -0
  118. package/dist/web/assets/ProcessesView-BHOsaM2H.js +4 -0
  119. package/dist/web/assets/ProcessesView-Ce6iVwjA.css +1 -0
  120. package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-iW1SYlX_.js +1 -0
  121. package/dist/web/assets/SystemLogsView-By5ijObd.js +1 -0
  122. package/dist/web/assets/SystemLogsView-CofP4Jvi.css +1 -0
  123. package/dist/web/assets/VSelect-CLnwhyWJ.js +1 -0
  124. package/dist/web/assets/VSpacer-dDRRKhUn.js +1 -0
  125. package/dist/web/assets/VSwitch-CGzxwZuj.js +1 -0
  126. package/dist/web/assets/VTabs-7aSwjrKs.js +1 -0
  127. package/dist/web/assets/VTextarea-D1_2FwMe.js +1 -0
  128. package/dist/web/assets/VTextarea-xiMOHEu-.css +1 -0
  129. package/dist/web/assets/WorkspaceDownloadPreview-BjuqUdB9.js +1 -0
  130. package/dist/web/assets/WorkspaceDownloadPreview-DnxhH5Q7.css +1 -0
  131. package/dist/web/assets/WorkspaceMediaPreview-D-igEnka.css +1 -0
  132. package/dist/web/assets/WorkspaceMediaPreview-LwXeVOY2.js +3 -0
  133. package/dist/web/assets/WorkspacesView-ClA6xcjn.css +1 -0
  134. package/dist/web/assets/WorkspacesView-DHl28eWf.js +2 -0
  135. package/dist/web/assets/entity-refresh-queue-wQii8Dl5.js +1 -0
  136. package/dist/web/assets/index-BzRlodie.js +19 -0
  137. package/dist/web/assets/index-CZ-4tJlF.css +1 -0
  138. package/dist/web/assets/{syntax-highlight-B5ArnyrC.js → syntax-highlight-C_fuMcwC.js} +1 -1
  139. package/dist/web/assets/useMasterDetailFocus-BQAutDO9.js +1 -0
  140. package/dist/web/assets/useOperationLabels-C-zgM_ET.js +1 -0
  141. package/dist/web/index.html +2 -2
  142. package/package.json +4 -2
  143. package/dist/plugins/git/types.d.ts +0 -32
  144. package/dist/web/assets/CloudView-CvX0dkgu.js +0 -1
  145. package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-B5TxGkUW.js +0 -3
  146. package/dist/web/assets/ComputerView-91Z9Cefw.css +0 -1
  147. package/dist/web/assets/ComputerView-OOyEqWml.js +0 -1
  148. package/dist/web/assets/McpToolsView-BTnolyS6.css +0 -1
  149. package/dist/web/assets/McpToolsView-DjXWTKrZ.js +0 -1
  150. package/dist/web/assets/OperationsView-DqTY51Cr.js +0 -4
  151. package/dist/web/assets/ProcessesView-C13BykIa.js +0 -4
  152. package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-DyOhJmR6.js +0 -1
  153. package/dist/web/assets/SystemLogsView-Ci0u4BTb.js +0 -1
  154. package/dist/web/assets/VSelect-DrRpKyYI.js +0 -1
  155. package/dist/web/assets/VSpacer-C3wAY3F0.js +0 -1
  156. package/dist/web/assets/VSwitch-CxusR4iO.js +0 -1
  157. package/dist/web/assets/VTabs-qBcuuXh3.js +0 -1
  158. package/dist/web/assets/WorkspacesView-BzQ0aH74.css +0 -1
  159. package/dist/web/assets/WorkspacesView-CijIvfsp.js +0 -2
  160. package/dist/web/assets/index-BegKQ3Yd.js +0 -19
  161. package/dist/web/assets/index-CkjqXRSj.css +0 -1
  162. package/dist/web/assets/useOperationLabels-BDjzGDkT.js +0 -1
  163. /package/dist/{plugins/process → core/runtime}/head-tail-buffer.d.ts +0 -0
  164. /package/dist/{plugins/process → core/runtime}/head-tail-buffer.js +0 -0
  165. /package/dist/plugins/{git/types.js → agent/provider.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { mkdtemp, open, rm, stat } from "node:fs/promises";
1
+ import { lstat, mkdtemp, open, rm, stat } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { ChatRoomError } from "#core/errors/chatroom-error";
@@ -21,6 +21,42 @@ export class GitService {
21
21
  ]);
22
22
  return parseStatus(result.stdout);
23
23
  }
24
+ async projectFiles(cwd, maxFiles = 5000) {
25
+ const root = await this.repositoryRoot(cwd);
26
+ if (!root)
27
+ return null;
28
+ const limit = Math.min(Math.max(Math.trunc(maxFiles), 1), 10_000);
29
+ const result = await this.run(root, [
30
+ "ls-files",
31
+ "-z",
32
+ "--cached",
33
+ "--others",
34
+ "--exclude-standard",
35
+ "--",
36
+ ".",
37
+ ":(exclude).git",
38
+ ":(exclude).git/**",
39
+ ":(exclude).chatroom",
40
+ ":(exclude).chatroom/**",
41
+ ]);
42
+ const output = [];
43
+ for (const record of result.stdout.split("\0")) {
44
+ if (!record)
45
+ continue;
46
+ if (output.length >= limit)
47
+ break;
48
+ const filePath = normalizePath(record);
49
+ try {
50
+ if ((await lstat(path.join(root, filePath))).isFile())
51
+ output.push(filePath);
52
+ }
53
+ catch (error) {
54
+ if (error.code !== "ENOENT")
55
+ throw error;
56
+ }
57
+ }
58
+ return output;
59
+ }
24
60
  async diff(cwd, filePath, maxPreviewBytes = 2 * 1024 * 1024) {
25
61
  const root = await this.requireRepository(cwd);
26
62
  await this.requireHead(root);
@@ -1,4 +1,2 @@
1
1
  import type { InternalPlugin } from "#plugins/types";
2
- import { GitService } from "./git-service.js";
3
- export declare const GitServiceToken: import("#plugins/types").ServiceToken<GitService>;
4
2
  export declare function createGitPlugin(): InternalPlugin;
@@ -1,12 +1,12 @@
1
+ import { GitAccessToken } from "#app/git-access";
1
2
  import { CommandRunner } from "#core/runtime/command-runner";
2
- import { createServiceToken } from "#plugins/types";
3
3
  import { GitService } from "./git-service.js";
4
- export const GitServiceToken = createServiceToken("git");
5
4
  export function createGitPlugin() {
6
5
  return {
7
6
  id: "git",
8
7
  activate(context) {
9
- context.services.provide(GitServiceToken, new GitService(new CommandRunner()));
8
+ const service = new GitService(new CommandRunner());
9
+ context.services.provide(GitAccessToken, service);
10
10
  },
11
11
  };
12
12
  }
@@ -1,4 +1,4 @@
1
1
  import type { InternalPlugin } from "#plugins/types";
2
2
  import { ProcessSupervisor } from "./process-supervisor.js";
3
- export declare const ProcessService: import("#plugins/types").ServiceToken<ProcessSupervisor>;
3
+ export declare const ProcessServiceToken: import("#app/service-registry").ServiceToken<ProcessSupervisor>;
4
4
  export declare function createProcessPlugin(): InternalPlugin;
@@ -1,16 +1,16 @@
1
- import { createServiceToken } from "#plugins/types";
1
+ import { createServiceToken } from "#app/service-registry";
2
2
  import { PipeProcessBackend } from "./infrastructure/pipe-process-backend.js";
3
3
  import { PtyProcessBackend } from "./infrastructure/pty-process-backend.js";
4
4
  import { registerProcessTools } from "./mcp.js";
5
5
  import { ProcessSupervisor } from "./process-supervisor.js";
6
- export const ProcessService = createServiceToken("process");
6
+ export const ProcessServiceToken = createServiceToken("process");
7
7
  export function createProcessPlugin() {
8
8
  let service = null;
9
9
  return {
10
10
  id: "process",
11
11
  activate(context) {
12
12
  service = new ProcessSupervisor({ pipe: new PipeProcessBackend(), pty: new PtyProcessBackend() }, context.operations, context.events, context.config.process.maxOutputBytes, context.config.process.defaultTimeoutMs, context.config.process.maxCompletedProcesses);
13
- context.services.provide(ProcessService, service);
13
+ context.services.provide(ProcessServiceToken, service);
14
14
  },
15
15
  registerMcp(mcp) {
16
16
  if (!service)
@@ -23,6 +23,7 @@ export declare class ProcessSupervisor {
23
23
  }, operations: OperationLog, eventBus: RuntimeEventBus, maxOutputBytes: number, defaultTimeoutMs: number, maxCompletedProcesses: number);
24
24
  start(request: ProcessStartRequest, operationContext: ProcessOperationContext): Promise<ProcessSnapshot>;
25
25
  read(processId: ProcessId): ProcessSnapshot;
26
+ summary(processId: ProcessId): ProcessSummary;
26
27
  list(): ProcessSnapshot[];
27
28
  summaries(): ProcessSummary[];
28
29
  write(processId: ProcessId, data: string): ProcessSnapshot;
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { ChatRoomError } from "#core/errors/chatroom-error";
3
3
  import { childEnvironment } from "#core/runtime/child-environment";
4
- import { HeadTailBuffer } from "./head-tail-buffer.js";
4
+ import { HeadTailBuffer } from "#core/runtime/head-tail-buffer";
5
5
  export class ProcessSupervisor {
6
6
  backends;
7
7
  operations;
@@ -54,6 +54,12 @@ export class ProcessSupervisor {
54
54
  this.operations.finish(operationId, "error", null, error);
55
55
  throw new ChatRoomError("PROCESS_FAILED", `Failed to start process: ${request.command}`, undefined, { cause: error });
56
56
  }
57
+ if (this.shuttingDown) {
58
+ backend.kill("SIGKILL");
59
+ if (!adoptedOperation)
60
+ this.operations.finish(operationId, "cancelled", null, new Error("Process supervisor shut down during process startup"));
61
+ throw new ChatRoomError("CONFLICT", "Process supervisor is shutting down");
62
+ }
57
63
  let resolveSettled;
58
64
  const settled = new Promise((resolve) => {
59
65
  resolveSettled = resolve;
@@ -97,13 +103,17 @@ export class ProcessSupervisor {
97
103
  }, 2000).unref();
98
104
  }, timeoutMs).unref();
99
105
  const snapshot = this.snapshotOf(processId, managed);
100
- this.eventBus.emit({ type: "process", process: snapshot });
106
+ this.eventBus.emit({ type: "process", processId });
101
107
  return snapshot;
102
108
  }
103
109
  read(processId) {
104
110
  const item = this.require(processId);
105
111
  return this.snapshotOf(processId, item);
106
112
  }
113
+ summary(processId) {
114
+ const item = this.require(processId);
115
+ return this.summaryOf(processId, item);
116
+ }
107
117
  list() {
108
118
  return [...this.processes.entries()]
109
119
  .map(([id, item]) => this.snapshotOf(id, item))
@@ -155,7 +165,7 @@ export class ProcessSupervisor {
155
165
  item.outputEventTimer = setTimeout(() => {
156
166
  item.outputEventTimer = null;
157
167
  if (item.state === "running")
158
- this.eventBus.emit({ type: "process-output", processId });
168
+ this.eventBus.emit({ type: "process", processId, output: true });
159
169
  }, 500).unref();
160
170
  }
161
171
  }
@@ -185,7 +195,7 @@ export class ProcessSupervisor {
185
195
  }, exitCode === 0 && !signal
186
196
  ? undefined
187
197
  : { exitCode, signal, timedOut: snapshot.timedOut });
188
- this.eventBus.emit({ type: "process", process: snapshot });
198
+ this.eventBus.emit({ type: "process", processId });
189
199
  item.resolveSettled(snapshot);
190
200
  this.trimCompletedProcesses();
191
201
  }
@@ -195,6 +205,7 @@ export class ProcessSupervisor {
195
205
  .sort(([, a], [, b]) => b.startedAt.getTime() - a.startedAt.getTime());
196
206
  for (const [processId] of completed.slice(this.maxCompletedProcesses)) {
197
207
  this.processes.delete(processId);
208
+ this.eventBus.emit({ type: "process", processId, deleted: true });
198
209
  }
199
210
  }
200
211
  require(processId) {
@@ -6,17 +6,7 @@ import type { McpToolControl } from "#mcp/server/tool-control";
6
6
  import type { RuntimeEventBus } from "#app/event-bus";
7
7
  import type { ExternalAccessRegistry } from "#app/external-access-registry";
8
8
  import type { LogWriter } from "#core/logging/types";
9
- export interface ServiceToken<T> {
10
- readonly key: symbol;
11
- readonly name: string;
12
- readonly __type?: T;
13
- }
14
- export declare function createServiceToken<T>(name: string): ServiceToken<T>;
15
- export declare class ServiceRegistry {
16
- private readonly values;
17
- provide<T>(token: ServiceToken<T>, value: T): void;
18
- require<T>(token: ServiceToken<T>): T;
19
- }
9
+ import type { ServiceRegistry } from "#app/service-registry";
20
10
  export interface PluginContext {
21
11
  config: ChatRoomConfig;
22
12
  database: AppDatabase;
@@ -1,17 +1 @@
1
- export function createServiceToken(name) {
2
- return { key: Symbol(name), name };
3
- }
4
- export class ServiceRegistry {
5
- values = new Map();
6
- provide(token, value) {
7
- if (this.values.has(token.key))
8
- throw new Error(`Service already provided: ${token.name}`);
9
- this.values.set(token.key, value);
10
- }
11
- require(token) {
12
- const value = this.values.get(token.key);
13
- if (value === undefined)
14
- throw new Error(`Required service unavailable: ${token.name}`);
15
- return value;
16
- }
17
- }
1
+ export {};
@@ -1,11 +1,13 @@
1
1
  import type { Operation as DomainOperation } from "#core/operations/types";
2
- import type { GitBranch, GitChange, GitCommit, GitDiff, GitStatus } from "#plugins/git/types";
2
+ import type { GitBranch, GitChange, GitCommit, GitDiff, GitStatus } from "#app/git-access";
3
3
  import type { ProcessSnapshot, ProcessSummary } from "#plugins/process/types";
4
+ import type { AgentAttachment, AgentHistory, AgentHistoryUpdate, AgentInteractionResponse, AgentItem, AgentModel, AgentProviderId, AgentProviderDetails, AgentReviewTarget, AgentProviderStatus, AgentSession, AgentTurn, AgentTurnSource } from "#plugins/agent/types";
4
5
  import type { McpToolSummary as DomainMcpToolSummary } from "#mcp/server/tool-control";
5
6
  import type { CloudServiceId, CloudStatus as DomainCloudStatus } from "#plugins/cloud/types";
6
- import type { ComputerDisplay, ComputerPermission, ComputerSnapshot, ComputerStatus } from "#plugins/computer/types";
7
- import type { WorkspaceEntry, WorkspaceFile, WorkspaceInfo, WorkspaceSkill } from "#plugins/workspace/types";
8
- export type { ComputerDisplay, ComputerPermission, ComputerStatus, GitBranch, GitChange, GitCommit, GitDiff, GitStatus, ProcessSnapshot, ProcessSummary, WorkspaceEntry, WorkspaceFile, WorkspaceInfo, WorkspaceSkill, };
7
+ import type { ComputerDisplay, ComputerPermission, ComputerStatus } from "#plugins/computer/types";
8
+ import type { WorkspaceEntry, WorkspaceInfo, WorkspaceSkill } from "#app/workspace-access";
9
+ import type { WorkspaceFile } from "#plugins/workspace/types";
10
+ export type { AgentAttachment, AgentHistory, AgentHistoryUpdate, AgentInteractionResponse, AgentItem, AgentModel, AgentProviderId, AgentProviderDetails, AgentReviewTarget, AgentProviderStatus, AgentSession, AgentTurn, AgentTurnSource, ComputerDisplay, ComputerPermission, ComputerStatus, GitBranch, GitChange, GitCommit, GitDiff, GitStatus, ProcessSnapshot, ProcessSummary, WorkspaceEntry, WorkspaceFile, WorkspaceInfo, WorkspaceSkill, };
9
11
  export interface ComputerPreviewView {
10
12
  snapshotId: string;
11
13
  revision: number;
@@ -18,7 +20,9 @@ export interface ComputerPreviewView {
18
20
  y: number;
19
21
  } | null;
20
22
  elementCount: number;
21
- screenshot: ComputerSnapshot["screenshot"] | null;
23
+ screenshot: {
24
+ mimeType: "image/jpeg" | "image/png";
25
+ } | null;
22
26
  }
23
27
  export type Operation = DomainOperation;
24
28
  export interface AuthStatus {
@@ -46,6 +50,17 @@ export type CloudService = CloudServiceId;
46
50
  export type CloudStatus = DomainCloudStatus;
47
51
  export interface WorkspaceFileContent {
48
52
  content: string;
53
+ bytes: number;
54
+ truncated: boolean;
55
+ modifiedAt: string;
56
+ }
57
+ export interface WorkspaceFilePage {
58
+ items: WorkspaceFile[];
59
+ nextOffset: number | null;
60
+ }
61
+ export interface WorkspaceProjectFiles {
62
+ paths: string[];
63
+ truncated: boolean;
49
64
  }
50
65
  export interface CloudManagementSession {
51
66
  url: string;
@@ -0,0 +1,3 @@
1
+ import { Router } from "express";
2
+ import type { AgentRuntime } from "#plugins/agent/runtime";
3
+ export declare function createAgentApiRouter(agents: AgentRuntime): Router;
@@ -0,0 +1,254 @@
1
+ import { Router } from "express";
2
+ import { ChatRoomError } from "#core/errors/chatroom-error";
3
+ import { isAgentProviderId, isAgentPermissionMode } from "#plugins/agent/types";
4
+ import { asyncRoute, bodyRecord, requireString, } from "#presentation/http/http-utils";
5
+ export function createAgentApiRouter(agents) {
6
+ const router = Router();
7
+ router.get("/agents/providers", asyncRoute(async (_req, res) => {
8
+ res.json(await agents.providerStatuses());
9
+ }));
10
+ router.get("/agents/providers/:provider/models", asyncRoute(async (req, res) => {
11
+ res.json(await agents.models(requireProvider(requireString(req.params.provider, "provider"))));
12
+ }));
13
+ router.get("/agents/providers/:provider/details", asyncRoute(async (req, res) => {
14
+ res.json(await agents.providerDetails(requireProvider(requireString(req.params.provider, "provider"))));
15
+ }));
16
+ router.get("/agents/sessions", (_req, res) => {
17
+ res.json(agents.listSessions());
18
+ });
19
+ router.post("/agents/sessions", asyncRoute(async (req, res) => {
20
+ const body = bodyRecord(req.body);
21
+ res.json(await agents.createSession({
22
+ workspaceRoot: requireString(body.workspaceRoot, "workspaceRoot"),
23
+ provider: requireProvider(requireString(body.provider, "provider")),
24
+ model: null,
25
+ reasoningEffort: null,
26
+ reasoningSummary: null,
27
+ serviceTier: null,
28
+ approvalPolicy: null,
29
+ approvalsReviewer: null,
30
+ permissionMode: null,
31
+ ...parseConfig(body),
32
+ }));
33
+ }));
34
+ router.get("/agents/sessions/:sessionId/history", (req, res) => {
35
+ res.json(agents.history(requireString(req.params.sessionId, "sessionId")));
36
+ });
37
+ router.get("/agents/sessions/:sessionId/update", (req, res) => {
38
+ res.json(agents.historyUpdate(requireString(req.params.sessionId, "sessionId"), parseIdList(req.query.turnIds), parseIdList(req.query.itemIds)));
39
+ });
40
+ router.put("/agents/sessions/:sessionId/config", asyncRoute(async (req, res) => {
41
+ const body = bodyRecord(req.body);
42
+ res.json(await agents.configureSession(requireString(req.params.sessionId, "sessionId"), parseConfig(body)));
43
+ }));
44
+ router.post("/agents/sessions/:sessionId/turns", asyncRoute(async (req, res) => {
45
+ const body = bodyRecord(req.body);
46
+ res.json(await agents.startTurn(requireString(req.params.sessionId, "sessionId"), {
47
+ text: typeof body.text === "string" ? body.text : "",
48
+ attachments: parseAttachments(body.attachments),
49
+ outputSchema: body.outputSchema ?? null,
50
+ }, "human"));
51
+ }));
52
+ router.post("/agents/sessions/:sessionId/steer", asyncRoute(async (req, res) => {
53
+ const body = bodyRecord(req.body);
54
+ requireKeys(body, ["text", "attachments"]);
55
+ if (body.text !== undefined && typeof body.text !== "string")
56
+ throw new ChatRoomError("INVALID_INPUT", "text must be a string");
57
+ await agents.steer(requireString(req.params.sessionId, "sessionId"), {
58
+ text: typeof body.text === "string" ? body.text : "",
59
+ attachments: parseAttachments(body.attachments),
60
+ }, "human");
61
+ res.status(204).end();
62
+ }));
63
+ router.post("/agents/sessions/:sessionId/review", asyncRoute(async (req, res) => {
64
+ const body = bodyRecord(req.body);
65
+ requireKeys(body, ["target"]);
66
+ res.json(await agents.startReview(requireString(req.params.sessionId, "sessionId"), parseReviewTarget(body.target), "human"));
67
+ }));
68
+ router.post("/agents/sessions/:sessionId/interrupt", asyncRoute(async (req, res) => {
69
+ await agents.interrupt(requireString(req.params.sessionId, "sessionId"));
70
+ res.status(204).end();
71
+ }));
72
+ router.post("/agents/sessions/:sessionId/interactions/:itemId", asyncRoute(async (req, res) => {
73
+ res.json(await agents.respondInteraction(requireString(req.params.sessionId, "sessionId"), requireString(req.params.itemId, "itemId"), parseInteractionResponse(bodyRecord(req.body))));
74
+ }));
75
+ router.post("/agents/sessions/:sessionId/provider", asyncRoute(async (req, res) => {
76
+ const body = bodyRecord(req.body);
77
+ res.json(await agents.switchProvider(requireString(req.params.sessionId, "sessionId"), requireProvider(requireString(body.provider, "provider")), parseConfig(body)));
78
+ }));
79
+ router.delete("/agents/sessions/:sessionId", asyncRoute(async (req, res) => {
80
+ await agents.deleteSession(requireString(req.params.sessionId, "sessionId"));
81
+ res.status(204).end();
82
+ }));
83
+ return router;
84
+ }
85
+ function parseIdList(value) {
86
+ const values = Array.isArray(value) ? value : value == null ? [] : [value];
87
+ const output = new Set();
88
+ for (const entry of values) {
89
+ if (typeof entry !== "string")
90
+ continue;
91
+ for (const id of entry.split(",")) {
92
+ if (!id)
93
+ continue;
94
+ if (!/^[A-Za-z0-9_-]+$/.test(id))
95
+ throw new ChatRoomError("INVALID_INPUT", "Invalid Agent update id");
96
+ output.add(id);
97
+ if (output.size > 256)
98
+ throw new ChatRoomError("INVALID_INPUT", "Too many Agent update ids");
99
+ }
100
+ }
101
+ return [...output];
102
+ }
103
+ function requireProvider(value) {
104
+ if (isAgentProviderId(value))
105
+ return value;
106
+ throw new ChatRoomError("INVALID_INPUT", "Invalid Agent provider id");
107
+ }
108
+ function optionalString(value) {
109
+ if (value === null || value === undefined || value === "")
110
+ return null;
111
+ if (typeof value !== "string")
112
+ throw new ChatRoomError("INVALID_INPUT", "Expected a string");
113
+ return value;
114
+ }
115
+ function parseAttachments(value) {
116
+ if (value === undefined || value === null)
117
+ return [];
118
+ if (!Array.isArray(value))
119
+ throw new ChatRoomError("INVALID_INPUT", "attachments must be an array");
120
+ return value.map((entry, index) => {
121
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
122
+ throw new ChatRoomError("INVALID_INPUT", "Invalid attachment at index " + index);
123
+ const record = entry;
124
+ const kind = requireString(record.kind, "attachments.kind");
125
+ if (!["image", "audio", "skill", "file"].includes(kind))
126
+ throw new ChatRoomError("INVALID_INPUT", "Invalid attachment kind");
127
+ return {
128
+ kind: kind,
129
+ name: requireString(record.name, "attachments.name"),
130
+ path: requireString(record.path, "attachments.path"),
131
+ mimeType: optionalString(record.mimeType),
132
+ };
133
+ });
134
+ }
135
+ function parseInteractionResponse(body) {
136
+ const type = requireString(body.type, "type");
137
+ if (type === "approval") {
138
+ const decision = requireString(body.decision, "decision");
139
+ return { type, decision };
140
+ }
141
+ if (type === "input") {
142
+ if (!body.answers ||
143
+ typeof body.answers !== "object" ||
144
+ Array.isArray(body.answers))
145
+ throw new ChatRoomError("INVALID_INPUT", "answers must be an object");
146
+ const answers = {};
147
+ for (const [key, value] of Object.entries(body.answers)) {
148
+ if (!Array.isArray(value) ||
149
+ value.some((entry) => typeof entry !== "string"))
150
+ throw new ChatRoomError("INVALID_INPUT", "Interaction answers must be string arrays");
151
+ answers[key] = value;
152
+ }
153
+ return { type, answers };
154
+ }
155
+ throw new ChatRoomError("INVALID_INPUT", "Unsupported interaction response");
156
+ }
157
+ function parseConfig(body) {
158
+ const config = {};
159
+ for (const key of ["model", "reasoningEffort", "serviceTier"]) {
160
+ if (Object.hasOwn(body, key))
161
+ config[key] = optionalString(body[key]);
162
+ }
163
+ if (Object.hasOwn(body, "approvalPolicy"))
164
+ config.approvalPolicy = parseApprovalPolicy(body.approvalPolicy);
165
+ if (Object.hasOwn(body, "approvalsReviewer")) {
166
+ const value = optionalString(body.approvalsReviewer);
167
+ if (value !== null &&
168
+ !["user", "auto_review", "guardian_subagent"].includes(value))
169
+ throw new ChatRoomError("INVALID_INPUT", "Invalid approvalsReviewer");
170
+ config.approvalsReviewer = value;
171
+ }
172
+ if (Object.hasOwn(body, "permissionMode")) {
173
+ const value = optionalString(body.permissionMode);
174
+ if (value !== null && !isAgentPermissionMode(value))
175
+ throw new ChatRoomError("INVALID_INPUT", "Invalid permissionMode");
176
+ config.permissionMode = value;
177
+ }
178
+ if (Object.hasOwn(body, "reasoningSummary")) {
179
+ const value = optionalString(body.reasoningSummary);
180
+ if (value !== null &&
181
+ !["auto", "concise", "detailed", "none"].includes(value))
182
+ throw new ChatRoomError("INVALID_INPUT", "Invalid reasoning summary");
183
+ config.reasoningSummary = value;
184
+ }
185
+ return config;
186
+ }
187
+ function parseApprovalPolicy(value) {
188
+ if (value === null || value === undefined)
189
+ return null;
190
+ if (value === "untrusted" || value === "on-request" || value === "never")
191
+ return value;
192
+ if (!value || typeof value !== "object" || Array.isArray(value))
193
+ throw new ChatRoomError("INVALID_INPUT", "Invalid approvalPolicy");
194
+ const record = value;
195
+ requireKeys(record, ["granular"]);
196
+ const granularValue = record.granular;
197
+ if (!granularValue ||
198
+ typeof granularValue !== "object" ||
199
+ Array.isArray(granularValue))
200
+ throw new ChatRoomError("INVALID_INPUT", "Invalid granular approvalPolicy");
201
+ const granular = granularValue;
202
+ const keys = [
203
+ "sandbox_approval",
204
+ "rules",
205
+ "skill_approval",
206
+ "request_permissions",
207
+ "mcp_elicitations",
208
+ ];
209
+ requireKeys(granular, [...keys]);
210
+ if (keys.some((key) => !Object.hasOwn(granular, key) || typeof granular[key] !== "boolean"))
211
+ throw new ChatRoomError("INVALID_INPUT", "Granular approvalPolicy fields must be booleans");
212
+ return {
213
+ granular: {
214
+ sandbox_approval: granular.sandbox_approval,
215
+ rules: granular.rules,
216
+ skill_approval: granular.skill_approval,
217
+ request_permissions: granular.request_permissions,
218
+ mcp_elicitations: granular.mcp_elicitations,
219
+ },
220
+ };
221
+ }
222
+ function requireKeys(body, allowed) {
223
+ if (Object.keys(body).some((key) => !allowed.includes(key)))
224
+ throw new ChatRoomError("INVALID_INPUT", "Unexpected request field");
225
+ }
226
+ function parseReviewTarget(value) {
227
+ if (!value || typeof value !== "object" || Array.isArray(value))
228
+ throw new ChatRoomError("INVALID_INPUT", "target must be an object");
229
+ const target = value;
230
+ const type = requireString(target.type, "target.type");
231
+ switch (type) {
232
+ case "uncommittedChanges":
233
+ requireKeys(target, ["type"]);
234
+ return { type };
235
+ case "baseBranch":
236
+ requireKeys(target, ["type", "branch"]);
237
+ return { type, branch: requireString(target.branch, "target.branch") };
238
+ case "commit":
239
+ requireKeys(target, ["type", "sha", "title"]);
240
+ return {
241
+ type,
242
+ sha: requireString(target.sha, "target.sha"),
243
+ title: optionalString(target.title),
244
+ };
245
+ case "custom":
246
+ requireKeys(target, ["type", "instructions"]);
247
+ return {
248
+ type,
249
+ instructions: requireString(target.instructions, "target.instructions"),
250
+ };
251
+ default:
252
+ throw new ChatRoomError("INVALID_INPUT", "Unsupported review target");
253
+ }
254
+ }
@@ -9,6 +9,7 @@ import { createComputerApiRouter } from "./computer-api-router.js";
9
9
  import { apiAuthentication, createPrivateAuthApiRouter, createPublicAuthApiRouter, } from "./auth-api-router.js";
10
10
  import { createRuntimeApiRouter } from "./runtime-api-router.js";
11
11
  import { createLogsApiRouter } from "./logs-api-router.js";
12
+ import { createAgentApiRouter } from "./agent-api-router.js";
12
13
  export function createApiRouter(application, eventBus, auth, passkeys, ingress, cloud, logs, runtimeStatus) {
13
14
  const router = Router();
14
15
  router.use(createPublicAuthApiRouter(auth, passkeys, ingress, logs));
@@ -20,6 +21,13 @@ export function createApiRouter(application, eventBus, auth, passkeys, ingress,
20
21
  res.json(application.operations.list({
21
22
  limit: boundedIntegerQuery(req.query.limit, 100, 0, 500),
22
23
  offset: boundedIntegerQuery(req.query.offset, 0, 0, 100000),
24
+ ...(typeof req.query.beforeStartedAt === "string" &&
25
+ typeof req.query.beforeOperationId === "string"
26
+ ? {
27
+ beforeStartedAt: req.query.beforeStartedAt,
28
+ beforeOperationId: req.query.beforeOperationId,
29
+ }
30
+ : {}),
23
31
  ...(typeof req.query.pluginId === "string"
24
32
  ? { pluginId: req.query.pluginId }
25
33
  : {}),
@@ -41,6 +49,7 @@ export function createApiRouter(application, eventBus, auth, passkeys, ingress,
41
49
  router.use(createWorkspaceApiRouter(application));
42
50
  router.use(createGitApiRouter(application));
43
51
  router.use(createProcessApiRouter(application));
52
+ router.use(createAgentApiRouter(application.agents));
44
53
  router.use(createComputerApiRouter(application.computer, application.operations, ingress));
45
54
  router.use(createCloudApiRouter(cloud, application.operations));
46
55
  return router;
@@ -1,6 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import { z } from "zod";
3
- import { asyncRoute } from "#presentation/http/http-utils";
3
+ import { asyncRoute, requireString } from "#presentation/http/http-utils";
4
4
  import { ChatRoomError } from "#core/errors/chatroom-error";
5
5
  const settingsPatchSchema = z
6
6
  .object({
@@ -20,8 +20,8 @@ export function createComputerApiRouter(computer, operations, ingress) {
20
20
  res.json(await computer.requestPermission("screenRecording"));
21
21
  }));
22
22
  function assertLocalPermissionRequest(req) {
23
- if (ingress.isExternalWeb(req))
24
- throw new ChatRoomError("FORBIDDEN", "Computer permissions can be requested only from the local WebUI");
23
+ if (ingress.isRemoteWeb(req))
24
+ throw new ChatRoomError("FORBIDDEN", "Computer permissions can be requested only from the local Web UI");
25
25
  }
26
26
  router.patch("/computer/settings", asyncRoute(async (req, res) => {
27
27
  const parsed = settingsPatchSchema.parse(req.body);
@@ -39,11 +39,24 @@ export function createComputerApiRouter(computer, operations, ingress) {
39
39
  }, async () => computer.setSettings(patch)));
40
40
  }));
41
41
  router.get("/computer/preview", (req, res) => {
42
- const scope = ingress.isExternalWeb(req) ? "remote" : "local";
42
+ const scope = ingress.isRemoteWeb(req) ? "remote" : "local";
43
43
  res.json(presentPreview(computer.latestSnapshot(scope), computer.latestSnapshotTimestamp()));
44
44
  });
45
+ router.get("/computer/preview/image", (req, res) => {
46
+ const scope = ingress.isRemoteWeb(req) ? "remote" : "local";
47
+ const snapshotId = requireString(req.query.snapshotId, "snapshotId");
48
+ const screenshot = computer.screenshot(scope, snapshotId);
49
+ if (!screenshot)
50
+ throw new ChatRoomError("NOT_FOUND", "Computer screenshot not found");
51
+ const data = Buffer.from(screenshot.data, "base64");
52
+ res.setHeader("Content-Type", screenshot.mimeType);
53
+ res.setHeader("Content-Length", String(data.byteLength));
54
+ res.setHeader("Cache-Control", "private, max-age=31536000, immutable");
55
+ res.setHeader("X-Content-Type-Options", "nosniff");
56
+ res.send(data);
57
+ });
45
58
  router.post("/computer/snapshot", asyncRoute(async (req, res) => {
46
- const scope = ingress.isExternalWeb(req) ? "remote" : "local";
59
+ const scope = ingress.isRemoteWeb(req) ? "remote" : "local";
47
60
  const value = await computer.snapshot(scope, {
48
61
  includeScreenshot: true,
49
62
  includeElements: true,
@@ -64,6 +77,8 @@ function presentPreview(value, capturedAt) {
64
77
  activeWindow: value.activeWindow,
65
78
  cursor: value.cursor,
66
79
  elementCount: value.elements.length,
67
- screenshot: value.screenshot ?? null,
80
+ screenshot: value.screenshot
81
+ ? { mimeType: value.screenshot.mimeType }
82
+ : null,
68
83
  };
69
84
  }
@@ -3,6 +3,9 @@ import { asyncRoute, requireString } from "#presentation/http/http-utils";
3
3
  export function createProcessApiRouter(application) {
4
4
  const router = Router();
5
5
  router.get("/processes", (_req, res) => res.json(application.listProcesses()));
6
+ router.get("/processes/:processId/summary", (req, res) => {
7
+ res.json(application.getProcessSummary(requireString(req.params.processId, "processId")));
8
+ });
6
9
  router.get("/processes/:processId", (req, res) => {
7
10
  res.json(application.getProcess(requireString(req.params.processId, "processId")));
8
11
  });