@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,7 +1,7 @@
1
- import { randomUUID } from "node:crypto";
2
- import { lstat, mkdir, open, readdir, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
1
+ import { lstat, open, readdir, realpath, stat } from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import { ChatRoomError } from "#core/errors/chatroom-error";
4
+ import { normalizeRelativePath, resolveReadableWithin, resolveWritableWithin, writeBytesAtomicWithin, } from "#core/fs/safe-directory";
5
5
  export class WorkspaceFs {
6
6
  root;
7
7
  constructor(root) {
@@ -16,30 +16,92 @@ export class WorkspaceFs {
16
16
  throw new ChatRoomError("INVALID_INPUT", `Workspace root is not a directory: ${root}`);
17
17
  return new WorkspaceFs(canonical);
18
18
  }
19
+ async fileInfo(relativePath) {
20
+ const target = await resolveReadableWithin(this.root, relativePath);
21
+ const info = await stat(target);
22
+ if (!info.isFile())
23
+ throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
24
+ return { bytes: info.size, modifiedAt: info.mtime.toISOString() };
25
+ }
19
26
  async read(relativePath, options = {}) {
20
27
  const result = await this.readBytes(relativePath, options);
21
28
  return {
22
29
  content: result.data.toString("utf8"),
23
30
  bytes: result.bytes,
24
31
  truncated: result.truncated,
32
+ modifiedAt: result.modifiedAt,
25
33
  };
26
34
  }
27
35
  async readBytes(relativePath, options = {}) {
28
- const target = await this.resolveReadable(relativePath);
29
- const info = await stat(target);
30
- if (!info.isFile())
31
- throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
36
+ const target = await resolveReadableWithin(this.root, relativePath);
32
37
  const maxBytes = options.maxBytes ?? 1024 * 1024;
38
+ for (let attempt = 0; attempt < 2; attempt += 1) {
39
+ const handle = await open(target, "r");
40
+ try {
41
+ const before = await handle.stat();
42
+ if (!before.isFile())
43
+ throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
44
+ const readSize = Math.min(before.size, maxBytes);
45
+ const buffer = Buffer.alloc(readSize);
46
+ let bytesRead = 0;
47
+ while (bytesRead < readSize) {
48
+ const result = await handle.read(buffer, bytesRead, readSize - bytesRead, bytesRead);
49
+ if (result.bytesRead === 0)
50
+ break;
51
+ bytesRead += result.bytesRead;
52
+ }
53
+ const after = await handle.stat();
54
+ const stable = before.size === after.size &&
55
+ before.mtimeMs === after.mtimeMs &&
56
+ before.ctimeMs === after.ctimeMs;
57
+ if (stable)
58
+ return {
59
+ data: buffer.subarray(0, bytesRead),
60
+ bytes: after.size,
61
+ truncated: after.size > maxBytes,
62
+ modifiedAt: after.mtime.toISOString(),
63
+ };
64
+ }
65
+ finally {
66
+ await handle.close();
67
+ }
68
+ }
69
+ throw new ChatRoomError("CONFLICT", `File changed while being read: ${relativePath}`);
70
+ }
71
+ async openReadable(relativePath) {
72
+ const target = await resolveReadableWithin(this.root, relativePath);
33
73
  const handle = await open(target, "r");
34
74
  try {
35
- const readSize = Math.min(info.size, maxBytes);
36
- const buffer = Buffer.alloc(readSize);
37
- const result = await handle.read(buffer, 0, readSize, 0);
38
- return {
39
- data: buffer.subarray(0, result.bytesRead),
40
- bytes: info.size,
41
- truncated: info.size > maxBytes,
42
- };
75
+ const info = await handle.stat();
76
+ if (!info.isFile())
77
+ throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
78
+ return { handle, bytes: info.size, modifiedAt: info.mtime.toISOString() };
79
+ }
80
+ catch (error) {
81
+ await handle.close();
82
+ throw error;
83
+ }
84
+ }
85
+ async readChunk(relativePath, offset, maxBytes) {
86
+ const { handle, bytes, modifiedAt } = await this.openReadable(relativePath);
87
+ try {
88
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > bytes)
89
+ throw new ChatRoomError("INVALID_INPUT", "Invalid file offset");
90
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 262144)
91
+ throw new ChatRoomError("INVALID_INPUT", "Invalid file chunk size");
92
+ const length = Math.min(maxBytes, bytes - offset);
93
+ const buffer = Buffer.alloc(length);
94
+ let position = 0;
95
+ while (position < length) {
96
+ const { bytesRead } = await handle.read(buffer, position, length - position, offset + position);
97
+ if (bytesRead === 0)
98
+ break;
99
+ position += bytesRead;
100
+ }
101
+ const after = await handle.stat();
102
+ if (after.size !== bytes || after.mtime.toISOString() !== modifiedAt)
103
+ throw new ChatRoomError("CONFLICT", "File changed while reading");
104
+ return { data: buffer.subarray(0, position), bytes, modifiedAt };
43
105
  }
44
106
  finally {
45
107
  await handle.close();
@@ -48,10 +110,10 @@ export class WorkspaceFs {
48
110
  async write(relativePath, content) {
49
111
  if (typeof content !== "string")
50
112
  throw new ChatRoomError("INVALID_INPUT", "File content must be a string");
51
- const normalized = normalizeRelative(relativePath);
113
+ const normalized = normalizeRelativePath(relativePath);
52
114
  if (normalized === ".")
53
115
  throw new ChatRoomError("INVALID_INPUT", "File path is required");
54
- const target = await this.resolveWritable(normalized);
116
+ const target = await resolveWritableWithin(this.root, normalized);
55
117
  let mode = 0o644;
56
118
  try {
57
119
  const info = await lstat(target);
@@ -65,34 +127,41 @@ export class WorkspaceFs {
65
127
  if (error.code !== "ENOENT")
66
128
  throw error;
67
129
  }
68
- const temporary = `${target}.chatroom-${randomUUID()}.tmp`;
69
- try {
70
- await writeFile(temporary, content, {
71
- encoding: "utf8",
72
- flag: "wx",
73
- mode,
74
- });
75
- await rename(temporary, target);
76
- }
77
- finally {
78
- await rm(temporary, { force: true }).catch(() => undefined);
79
- }
130
+ await writeBytesAtomicWithin(this.root, normalized, Buffer.from(content, "utf8"), mode);
80
131
  return {
81
132
  path: normalized,
82
133
  type: "file",
83
134
  size: Buffer.byteLength(content, "utf8"),
84
135
  };
85
136
  }
137
+ async writeBytes(relativePath, content) {
138
+ const normalized = normalizeRelativePath(relativePath);
139
+ if (normalized === ".")
140
+ throw new ChatRoomError("INVALID_INPUT", "File path is required");
141
+ await writeBytesAtomicWithin(this.root, normalized, content);
142
+ return {
143
+ path: normalized,
144
+ type: "file",
145
+ size: content.byteLength,
146
+ };
147
+ }
86
148
  async list(relativePath = ".", options = {}) {
87
- const start = await this.resolveReadable(relativePath);
149
+ const start = await resolveReadableWithin(this.root, relativePath);
88
150
  if (!(await stat(start)).isDirectory())
89
151
  throw new ChatRoomError("INVALID_INPUT", `Not a directory: ${relativePath}`);
90
152
  const maxEntries = Math.min(options.maxEntries ?? 1000, 10000);
153
+ const offset = Math.max(0, options.offset ?? 0);
154
+ const excluded = new Set(options.excludeDirectoryNames ?? []);
91
155
  const output = [];
156
+ let seen = 0;
92
157
  const walk = async (directory) => {
93
- for (const entry of await readdir(directory, { withFileTypes: true })) {
158
+ const entries = await readdir(directory, { withFileTypes: true });
159
+ entries.sort((left, right) => left.name.localeCompare(right.name));
160
+ for (const entry of entries) {
94
161
  if (output.length >= maxEntries)
95
162
  return;
163
+ if (entry.isDirectory() && excluded.has(entry.name))
164
+ continue;
96
165
  const absolute = path.join(directory, entry.name);
97
166
  const info = await lstat(absolute);
98
167
  const type = info.isSymbolicLink()
@@ -100,11 +169,19 @@ export class WorkspaceFs {
100
169
  : info.isDirectory()
101
170
  ? "directory"
102
171
  : "file";
103
- output.push({
104
- path: path.relative(this.root, absolute).split(path.sep).join("/"),
105
- type,
106
- size: info.size,
107
- });
172
+ const relative = path
173
+ .relative(this.root, absolute)
174
+ .split(path.sep)
175
+ .join("/");
176
+ if (seen >= offset) {
177
+ output.push({
178
+ path: relative,
179
+ type,
180
+ size: info.size,
181
+ modifiedAt: info.mtime.toISOString(),
182
+ });
183
+ }
184
+ seen += 1;
108
185
  if (options.recursive && entry.isDirectory() && !entry.isSymbolicLink())
109
186
  await walk(absolute);
110
187
  }
@@ -112,89 +189,4 @@ export class WorkspaceFs {
112
189
  await walk(start);
113
190
  return output;
114
191
  }
115
- lexical(relativePath) {
116
- const normalized = normalizeRelative(relativePath);
117
- const absolute = path.resolve(this.root, ...normalized.split("/"));
118
- if (!inside(this.root, absolute))
119
- throw new ChatRoomError("FORBIDDEN", "Path escapes workspace");
120
- return absolute;
121
- }
122
- async resolveWritable(relativePath) {
123
- const target = this.lexical(relativePath);
124
- const relative = path.relative(this.root, target);
125
- const parts = relative.split(path.sep).filter(Boolean);
126
- let current = this.root;
127
- for (let index = 0; index < parts.length - 1; index++) {
128
- current = path.join(current, parts[index]);
129
- let info;
130
- try {
131
- info = await lstat(current);
132
- }
133
- catch (error) {
134
- if (error.code !== "ENOENT")
135
- throw error;
136
- try {
137
- await mkdir(current, { recursive: false, mode: 0o700 });
138
- }
139
- catch (mkdirError) {
140
- if (mkdirError.code !== "EEXIST")
141
- throw mkdirError;
142
- }
143
- info = await lstat(current);
144
- }
145
- if (info.isSymbolicLink())
146
- throw new ChatRoomError("FORBIDDEN", `Writes through symlinked directories are not allowed: ${relativePath}`);
147
- if (!info.isDirectory())
148
- throw new ChatRoomError("INVALID_INPUT", `Parent is not a directory: ${parts[index]}`);
149
- const canonical = await realpath(current);
150
- if (!inside(this.root, canonical))
151
- throw new ChatRoomError("FORBIDDEN", "Write path escapes workspace");
152
- }
153
- try {
154
- const targetInfo = await lstat(target);
155
- if (targetInfo.isSymbolicLink())
156
- throw new ChatRoomError("FORBIDDEN", `Writes through symlinks are not allowed: ${relativePath}`);
157
- if (!targetInfo.isFile())
158
- throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
159
- }
160
- catch (error) {
161
- if (error instanceof ChatRoomError)
162
- throw error;
163
- if (error.code !== "ENOENT")
164
- throw error;
165
- }
166
- return target;
167
- }
168
- async resolveReadable(relativePath) {
169
- const lexical = this.lexical(relativePath);
170
- const canonical = await realpath(lexical).catch((error) => {
171
- if (error.code === "ENOENT")
172
- throw new ChatRoomError("NOT_FOUND", `Path does not exist: ${relativePath}`);
173
- throw error;
174
- });
175
- if (!inside(this.root, canonical))
176
- throw new ChatRoomError("FORBIDDEN", "Symlink escapes workspace");
177
- return canonical;
178
- }
179
- }
180
- function normalizeRelative(input) {
181
- if (typeof input !== "string" || input.includes("\0"))
182
- throw new ChatRoomError("INVALID_INPUT", "Path must be a valid string");
183
- if (path.isAbsolute(input) ||
184
- path.win32.isAbsolute(input) ||
185
- /^\\\\/.test(input))
186
- throw new ChatRoomError("FORBIDDEN", "Absolute paths are not allowed");
187
- const parts = input
188
- .split(/[\\/]+/)
189
- .filter((part) => part !== "" && part !== ".");
190
- if (parts.some((part) => part === ".."))
191
- throw new ChatRoomError("FORBIDDEN", "Path traversal is not allowed");
192
- return parts.join("/") || ".";
193
- }
194
- function inside(root, candidate) {
195
- const relative = path.relative(root, candidate);
196
- return (relative === "" ||
197
- (!relative.startsWith(`..${path.sep}`) &&
198
- relative !== ".." &&
199
- !path.isAbsolute(relative)));
200
192
  }
@@ -0,0 +1,9 @@
1
+ export declare class WorkspaceRootPolicy {
2
+ private readonly allowedRoots;
3
+ private constructor();
4
+ static create(allowedRoots: string[]): Promise<WorkspaceRootPolicy>;
5
+ roots(): string[];
6
+ isWorkspaceRoot(candidate: string): boolean;
7
+ resolveWorkspace(input: string): Promise<string>;
8
+ resolveAllowedRoot(input: string): Promise<string>;
9
+ }
@@ -0,0 +1,56 @@
1
+ import { realpath, stat } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { ChatRoomError } from "#core/errors/chatroom-error";
5
+ export class WorkspaceRootPolicy {
6
+ allowedRoots;
7
+ constructor(allowedRoots) {
8
+ this.allowedRoots = allowedRoots;
9
+ }
10
+ static async create(allowedRoots) {
11
+ const canonicalRoots = await Promise.all(allowedRoots.map(async (root) => {
12
+ const canonical = await realpath(expandHome(root)).catch(() => {
13
+ throw new ChatRoomError("NOT_FOUND", `Allowed root does not exist: ${root}`);
14
+ });
15
+ if (!(await stat(canonical)).isDirectory())
16
+ throw new ChatRoomError("INVALID_INPUT", `Allowed root is not a directory: ${root}`);
17
+ return canonical;
18
+ }));
19
+ return new WorkspaceRootPolicy([...new Set(canonicalRoots)]);
20
+ }
21
+ roots() {
22
+ return [...this.allowedRoots];
23
+ }
24
+ isWorkspaceRoot(candidate) {
25
+ return this.allowedRoots.some((root) => path.dirname(candidate) === root);
26
+ }
27
+ async resolveWorkspace(input) {
28
+ if (typeof input !== "string" || !input.trim())
29
+ throw new ChatRoomError("INVALID_INPUT", "Workspace root is required");
30
+ const canonical = await realpath(expandHome(input)).catch(() => {
31
+ throw new ChatRoomError("NOT_FOUND", `Workspace root does not exist: ${input}`);
32
+ });
33
+ if (!(await stat(canonical)).isDirectory())
34
+ throw new ChatRoomError("INVALID_INPUT", `Workspace root is not a directory: ${input}`);
35
+ if (!this.isWorkspaceRoot(canonical))
36
+ throw new ChatRoomError("FORBIDDEN", "Workspace must be a direct child of a configured allowed root", { root: canonical });
37
+ return canonical;
38
+ }
39
+ async resolveAllowedRoot(input) {
40
+ if (typeof input !== "string" || !input.trim())
41
+ throw new ChatRoomError("INVALID_INPUT", "Allowed root is required");
42
+ const canonical = await realpath(expandHome(input)).catch(() => {
43
+ throw new ChatRoomError("NOT_FOUND", `Allowed root does not exist: ${input}`);
44
+ });
45
+ if (!this.allowedRoots.includes(canonical))
46
+ throw new ChatRoomError("FORBIDDEN", "Project can only be created directly under a configured allowed root");
47
+ return canonical;
48
+ }
49
+ }
50
+ function expandHome(input) {
51
+ if (input === "~")
52
+ return os.homedir();
53
+ if (input.startsWith("~/") || input.startsWith("~\\"))
54
+ return path.join(os.homedir(), input.slice(2));
55
+ return path.resolve(input);
56
+ }
@@ -1,15 +1,22 @@
1
- import type { WorkspaceEntry, WorkspaceInfo } from "./types.js";
1
+ import type { GitAccess } from "#app/git-access";
2
+ import type { WorkspaceAccess, WorkspaceEntry, WorkspaceInfo } from "#app/workspace-access";
2
3
  import { WorkspaceFs } from "./workspace-fs.js";
3
- export declare class WorkspaceService {
4
- private readonly allowedRoots;
4
+ export declare class WorkspaceService implements WorkspaceAccess {
5
+ private readonly rootsPolicy;
6
+ private readonly git;
5
7
  private constructor();
6
- static create(allowedRoots: string[]): Promise<WorkspaceService>;
8
+ static create(allowedRoots: string[], git: Pick<GitAccess, "projectFiles">): Promise<WorkspaceService>;
7
9
  roots(): string[];
8
10
  list(): Promise<WorkspaceEntry[]>;
9
11
  resolve(input: string): Promise<string>;
12
+ resolveAlias(alias: string): Promise<string>;
13
+ fsByAlias(alias: string): Promise<WorkspaceFs>;
10
14
  createProject(parentInput: string, nameInput: string): Promise<WorkspaceEntry>;
15
+ projectFiles(input: string, maxFiles?: number): Promise<string[]>;
16
+ projectFilesResult(input: string, maxFiles?: number): Promise<{
17
+ paths: string[];
18
+ truncated: boolean;
19
+ }>;
11
20
  info(input: string): Promise<WorkspaceInfo>;
12
21
  fs(input: string): Promise<WorkspaceFs>;
13
- private isWorkspaceRoot;
14
- private resolveAllowedRoot;
15
22
  }
@@ -1,31 +1,25 @@
1
- import { mkdir, readdir, realpath, stat } from "node:fs/promises";
2
- import os from "node:os";
1
+ import { mkdir, readdir, realpath } from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import { ChatRoomError } from "#core/errors/chatroom-error";
4
+ import { WorkspaceRootPolicy } from "./workspace-root-policy.js";
5
5
  import { readInstructions, readPresetPrompt, readSkills, readSummary, } from "./metadata.js";
6
6
  import { WorkspaceFs } from "./workspace-fs.js";
7
7
  export class WorkspaceService {
8
- allowedRoots;
9
- constructor(allowedRoots) {
10
- this.allowedRoots = allowedRoots;
8
+ rootsPolicy;
9
+ git;
10
+ constructor(rootsPolicy, git) {
11
+ this.rootsPolicy = rootsPolicy;
12
+ this.git = git;
11
13
  }
12
- static async create(allowedRoots) {
13
- const canonicalRoots = await Promise.all(allowedRoots.map(async (root) => {
14
- const canonical = await realpath(expandHome(root)).catch(() => {
15
- throw new ChatRoomError("NOT_FOUND", `Allowed root does not exist: ${root}`);
16
- });
17
- if (!(await stat(canonical)).isDirectory())
18
- throw new ChatRoomError("INVALID_INPUT", `Allowed root is not a directory: ${root}`);
19
- return canonical;
20
- }));
21
- return new WorkspaceService([...new Set(canonicalRoots)]);
14
+ static async create(allowedRoots, git) {
15
+ return new WorkspaceService(await WorkspaceRootPolicy.create(allowedRoots), git);
22
16
  }
23
17
  roots() {
24
- return [...this.allowedRoots];
18
+ return this.rootsPolicy.roots();
25
19
  }
26
20
  async list() {
27
21
  const roots = new Set();
28
- for (const allowedRoot of this.allowedRoots) {
22
+ for (const allowedRoot of this.rootsPolicy.roots()) {
29
23
  let entries;
30
24
  try {
31
25
  entries = await readdir(allowedRoot, { withFileTypes: true });
@@ -39,7 +33,7 @@ export class WorkspaceService {
39
33
  entry.name.startsWith("."))
40
34
  continue;
41
35
  const candidate = await realpath(path.join(allowedRoot, entry.name)).catch(() => null);
42
- if (candidate && this.isWorkspaceRoot(candidate))
36
+ if (candidate && this.rootsPolicy.isWorkspaceRoot(candidate))
43
37
  roots.add(candidate);
44
38
  }
45
39
  }
@@ -55,19 +49,28 @@ export class WorkspaceService {
55
49
  }));
56
50
  }
57
51
  async resolve(input) {
58
- if (typeof input !== "string" || !input.trim())
59
- throw new ChatRoomError("INVALID_INPUT", "Workspace root is required");
60
- const canonical = await realpath(expandHome(input)).catch(() => {
61
- throw new ChatRoomError("NOT_FOUND", `Workspace root does not exist: ${input}`);
62
- });
63
- if (!(await stat(canonical)).isDirectory())
64
- throw new ChatRoomError("INVALID_INPUT", `Workspace root is not a directory: ${input}`);
65
- if (!this.isWorkspaceRoot(canonical))
66
- throw new ChatRoomError("FORBIDDEN", "Workspace must be a direct child of a configured allowed root", { root: canonical });
67
- return canonical;
52
+ return this.rootsPolicy.resolveWorkspace(input);
53
+ }
54
+ async resolveAlias(alias) {
55
+ const name = validateProjectName(alias);
56
+ const matches = [];
57
+ for (const parent of this.rootsPolicy.roots()) {
58
+ const candidate = path.join(parent, name);
59
+ const canonical = await realpath(candidate).catch(() => null);
60
+ if (canonical === candidate)
61
+ matches.push(candidate);
62
+ }
63
+ if (matches.length === 0)
64
+ throw new ChatRoomError("NOT_FOUND", "Workspace not found");
65
+ if (matches.length > 1)
66
+ throw new ChatRoomError("CONFLICT", "Workspace alias is ambiguous");
67
+ return this.resolve(matches[0]);
68
+ }
69
+ async fsByAlias(alias) {
70
+ return WorkspaceFs.create(await this.resolveAlias(alias));
68
71
  }
69
72
  async createProject(parentInput, nameInput) {
70
- const parent = await this.resolveAllowedRoot(parentInput);
73
+ const parent = await this.rootsPolicy.resolveAllowedRoot(parentInput);
71
74
  const name = validateProjectName(nameInput);
72
75
  const target = path.join(parent, name);
73
76
  try {
@@ -82,6 +85,31 @@ export class WorkspaceService {
82
85
  const root = await realpath(target);
83
86
  return { root, name: path.basename(root), summary: null };
84
87
  }
88
+ async projectFiles(input, maxFiles = 5000) {
89
+ return (await this.projectFilesResult(input, maxFiles)).paths;
90
+ }
91
+ async projectFilesResult(input, maxFiles = 5000) {
92
+ const fs = await this.fs(input);
93
+ const limit = Math.min(Math.max(Math.trunc(maxFiles), 1), 9999);
94
+ const gitFiles = await this.git.projectFiles(fs.root, limit + 1);
95
+ if (gitFiles)
96
+ return {
97
+ paths: gitFiles.slice(0, limit),
98
+ truncated: gitFiles.length > limit,
99
+ };
100
+ const entries = await fs.list(".", {
101
+ recursive: true,
102
+ maxEntries: 10_000,
103
+ excludeDirectoryNames: [".chatroom", ".git"],
104
+ });
105
+ const files = entries
106
+ .filter((entry) => entry.type === "file")
107
+ .map((entry) => entry.path);
108
+ return {
109
+ paths: files.slice(0, limit),
110
+ truncated: entries.length >= 10_000 || files.length > limit,
111
+ };
112
+ }
85
113
  async info(input) {
86
114
  const root = await this.resolve(input);
87
115
  const fs = await WorkspaceFs.create(root);
@@ -103,19 +131,6 @@ export class WorkspaceService {
103
131
  async fs(input) {
104
132
  return WorkspaceFs.create(await this.resolve(input));
105
133
  }
106
- isWorkspaceRoot(candidate) {
107
- return this.allowedRoots.some((root) => path.dirname(candidate) === root);
108
- }
109
- async resolveAllowedRoot(input) {
110
- if (typeof input !== "string" || !input.trim())
111
- throw new ChatRoomError("INVALID_INPUT", "Allowed root is required");
112
- const canonical = await realpath(expandHome(input)).catch(() => {
113
- throw new ChatRoomError("NOT_FOUND", `Allowed root does not exist: ${input}`);
114
- });
115
- if (!this.allowedRoots.includes(canonical))
116
- throw new ChatRoomError("FORBIDDEN", "Project can only be created directly under a configured allowed root");
117
- return canonical;
118
- }
119
134
  }
120
135
  function validateProjectName(input) {
121
136
  if (typeof input !== "string")
@@ -131,10 +146,3 @@ function validateProjectName(input) {
131
146
  throw new ChatRoomError("INVALID_INPUT", "Invalid project name");
132
147
  return name;
133
148
  }
134
- function expandHome(input) {
135
- if (input === "~")
136
- return os.homedir();
137
- if (input.startsWith("~/") || input.startsWith("~\\"))
138
- return path.join(os.homedir(), input.slice(2));
139
- return path.resolve(input);
140
- }
@@ -0,0 +1 @@
1
+ .agent-compose-tool-button[data-v-024d9876]{padding:0;color:rgba(var(--v-theme-on-surface),.72)}.agent-workspace-reference-button[data-v-024d9876]{font-size:16px;font-weight:600;line-height:1;text-transform:none}.agent-file-picker[data-v-024d9876]{inline-size:max-content;max-inline-size:90vw;padding:10px;background:rgb(var(--v-theme-surface));color:rgb(var(--v-theme-on-surface))}.agent-file-picker-caption[data-v-024d9876],.agent-tree-path[data-v-024d9876]{color:rgba(var(--v-theme-on-surface),.62)}.agent-file-picker-caption[data-v-024d9876]{margin-bottom:9px;font-size:.75rem}.agent-file-picker-list[data-v-024d9876]{max-height:45dvh;overflow-y:auto;margin-top:8px}.agent-tree-row[data-v-024d9876]{width:100%;min-height:32px;display:grid;grid-template-columns:18px minmax(0,1fr) auto;align-items:center;gap:5px;padding-block:4px;padding-inline-end:8px;border:0;border-radius:6px;background:transparent;color:rgb(var(--v-theme-on-surface));text-align:left;cursor:pointer}.agent-tree-row[data-v-024d9876]:hover,.agent-tree-row[data-selected=true][data-v-024d9876]{background:rgba(var(--v-theme-primary),.12)}.agent-tree-row[data-type=directory][data-v-024d9876]{font-weight:600}.agent-tree-chevron[data-v-024d9876]{transition:transform .12s ease}.agent-tree-chevron-open[data-v-024d9876]{transform:rotate(90deg)}.agent-tree-spacer[data-v-024d9876]{width:16px}.agent-tree-name[data-v-024d9876]{min-width:0;overflow:hidden;font-size:.8rem;text-overflow:ellipsis;white-space:nowrap}.agent-tree-path[data-v-024d9876]{max-width:45%;overflow:hidden;font-family:var(--font-mono, monospace);font-size:.68rem;text-overflow:ellipsis;white-space:nowrap}.agent-file-picker-empty[data-v-024d9876]{padding:22px 10px;color:rgba(var(--v-theme-on-surface),.62);font-size:.8rem;text-align:center}.agent-project-files-truncated[data-v-024d9876]{margin-top:8px}.agent-composer[data-v-c20ba904]{flex:0 1 auto;display:flex;flex-direction:column;min-width:0;min-height:0;gap:8px;padding:12px}.agent-composer-caption[data-v-c20ba904],.agent-file-suggestion-path[data-v-c20ba904]{color:rgba(var(--v-theme-on-surface),.62)}.agent-composer-caption[data-v-c20ba904]{font-size:.75rem}.agent-inline-error[data-v-c20ba904]{color:rgb(var(--v-theme-error))}.agent-attachment-error[data-v-c20ba904]{padding:0;font-size:.8rem}.agent-selected-attachments[data-v-c20ba904]{display:flex;flex-wrap:wrap;gap:6px;min-height:24px;overflow:auto}.agent-compose-row[data-v-c20ba904]{justify-content:flex-end;display:flex;align-items:flex-end;gap:8px}.agent-compose-tools[data-v-c20ba904]{flex:0 0 auto;align-self:flex-end;display:flex;flex-direction:column;gap:5px;margin-bottom:2px}.agent-compose-tool-button[data-v-c20ba904]{padding:0;color:rgba(var(--v-theme-on-surface),.72)}.agent-attachment-button[data-v-c20ba904] .v-icon{font-size:18px}.agent-compose-input-wrap[data-v-c20ba904]{position:relative;min-width:0;flex:1 1 auto}.agent-compose-input-wrap[data-v-c20ba904] .v-textarea{min-width:0}.agent-compose-actions[data-v-c20ba904]{flex:0 0 auto;align-self:flex-end;display:flex;flex-direction:column;align-items:center;gap:4px;margin-bottom:1px}.agent-compose-actions[data-v-c20ba904]>.v-btn{flex:0 0 auto}.agent-hidden-file-input[data-v-c20ba904]{display:none}.agent-mention-popup[data-v-c20ba904]{position:absolute;background:rgb(var(--v-theme-surface));color:rgb(var(--v-theme-on-surface));left:0;right:0;bottom:calc(100% + 8px);z-index:20;max-height:40dvh;overflow-y:auto;padding:5px;border:1px solid rgba(var(--v-border-color),var(--v-border-opacity));border-radius:8px;box-shadow:0 8px 24px #0003}.agent-file-suggestion[data-v-c20ba904]{width:100%;display:grid;gap:1px;padding:8px 10px;border:0;border-radius:6px;background:transparent;color:rgb(var(--v-theme-on-surface));text-align:left;cursor:pointer}.agent-file-suggestion[data-v-c20ba904]:hover{background:rgba(var(--v-theme-primary),.12)}.agent-file-suggestion-name[data-v-c20ba904]{overflow:hidden;font-size:.82rem;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.agent-file-suggestion-path[data-v-c20ba904]{overflow:hidden;font-family:var(--font-mono, monospace);font-size:.68rem;text-overflow:ellipsis;white-space:nowrap}.agent-composer[data-v-c20ba904]>*{flex-shrink:0}.agent-composer--waiting .agent-compose-actions[data-v-c20ba904]{flex-direction:row}.agent-artifact-group[data-v-e957f066]{min-width:0;align-self:stretch}.agent-artifact-group-heading[data-v-e957f066]{display:flex;align-items:center;gap:7px;margin-bottom:7px;color:rgba(var(--v-theme-on-surface),.62);font-size:.76rem;font-weight:600}.agent-artifact-grid[data-v-e957f066]{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr));gap:10px}.agent-artifact-grid[data-count="1"][data-v-e957f066]{grid-template-columns:minmax(0,1fr)}.agent-artifact-tile[data-v-e957f066]{min-width:0;overflow:hidden;border:1px solid rgba(var(--v-border-color),.2);border-radius:10px;background:rgba(var(--v-theme-surface-variant),.06)}.agent-artifact-media[data-v-e957f066]{position:relative;aspect-ratio:16 / 10;overflow:hidden;background:rgba(var(--v-theme-on-surface),.025)}.agent-artifact-image-button[data-v-e957f066]{width:100%;height:100%;display:block;padding:0;border:0;background:transparent;cursor:zoom-in}.agent-artifact-image-button[data-v-e957f066]:disabled{cursor:default}.agent-artifact-image[data-v-e957f066]{width:100%;height:100%;display:block;object-fit:contain;background:linear-gradient(45deg,rgba(var(--v-theme-on-surface),.025) 25%,transparent 25%,transparent 75%,rgba(var(--v-theme-on-surface),.025) 75%),linear-gradient(45deg,rgba(var(--v-theme-on-surface),.025) 25%,transparent 25%,transparent 75%,rgba(var(--v-theme-on-surface),.025) 75%);background-position:0 0,8px 8px;background-size:16px 16px}.agent-artifact-placeholder[data-v-e957f066]{display:grid;place-items:center;align-content:center;gap:8px;padding:20px;color:rgba(var(--v-theme-on-surface),.56);text-align:center;font-size:.78rem}.agent-artifact-overlay[data-v-e957f066]{position:absolute;inset:0;pointer-events:none;background:rgb(var(--v-theme-surface))}.agent-artifact-overlay.error[data-v-e957f066]{pointer-events:auto}.agent-artifact-placeholder.error[data-v-e957f066]{color:rgb(var(--v-theme-error))}.agent-artifact-error-detail[data-v-e957f066]{max-width:100%;overflow:hidden;color:rgba(var(--v-theme-on-surface),.5);font-family:var(--font-mono, monospace);font-size:.68rem;text-overflow:ellipsis;white-space:nowrap}.agent-artifact-footer[data-v-e957f066]{display:flex;align-items:center;gap:6px;padding:4px 6px 4px 10px;border-top:1px solid rgba(var(--v-border-color),.15)}.agent-artifact-name[data-v-e957f066]{min-width:0;flex:1 1 auto;overflow:hidden;color:rgba(var(--v-theme-on-surface),.62);font-family:var(--font-mono, monospace);font-size:.68rem;text-overflow:ellipsis;white-space:nowrap}.agent-artifact-preview-dialog[data-v-e957f066]{overflow:hidden}.agent-artifact-preview-header[data-v-e957f066]{display:flex;align-items:center;gap:8px;padding:5px 7px 5px 14px;font-size:.82rem}.agent-artifact-preview-header[data-v-e957f066]>:first-child{min-width:0;flex:1 1 auto}.agent-artifact-preview-body[data-v-e957f066]{display:grid;place-items:center;max-height:75dvh;overflow:auto;background:rgba(var(--v-theme-on-surface),.025)}.agent-artifact-preview-body img[data-v-e957f066]{max-width:100%;max-height:75dvh;object-fit:contain}@media(max-width:960px){.agent-artifact-grid[data-v-e957f066]{grid-template-columns:1fr}}.agent-command[data-v-7c2e41e4]{width:100%;display:block;flex:0 0 auto;min-inline-size:0;border-inline-start:2px solid rgba(var(--v-border-color),.3);background:rgba(var(--v-theme-surface-variant),.12)}.agent-command-main[data-v-7c2e41e4]{cursor:pointer;list-style:none;padding:7px 9px 8px}.agent-command-main[data-v-7c2e41e4]::-webkit-details-marker{display:none}.agent-command-main[data-v-7c2e41e4]:focus-visible{outline:2px solid rgb(var(--v-theme-primary))}.agent-command-label[data-v-7c2e41e4]{display:flex;align-items:center;gap:6px;color:rgba(var(--v-theme-on-surface),.62);font-size:.68rem;font-weight:600}.agent-command-status[data-v-7c2e41e4]{margin-inline-start:auto;display:inline-flex;align-items:center;gap:5px;font-weight:500;white-space:nowrap}.agent-command-status-dot[data-v-7c2e41e4]{width:6px;height:6px;border-radius:50%;background:rgba(var(--v-theme-on-surface),.42)}.agent-command[data-state=running] .agent-command-status-dot[data-v-7c2e41e4]{background:rgb(var(--v-theme-warning))}.agent-command[data-state=success] .agent-command-status-dot[data-v-7c2e41e4]{background:rgb(var(--v-theme-success))}.agent-command[data-state=error][data-v-7c2e41e4]{border-color:rgba(var(--v-theme-error),.36)}.agent-command[data-state=error] .agent-command-status-dot[data-v-7c2e41e4]{background:rgb(var(--v-theme-error))}.agent-command-line[data-v-7c2e41e4]{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:2px;margin-top:2px}.agent-command-line code[data-v-7c2e41e4]{min-width:0;display:block;overflow:hidden;color:rgba(var(--v-theme-on-surface),.92);font-size:.76rem;line-height:1.5;text-overflow:ellipsis;white-space:nowrap}.agent-command-copy[data-v-7c2e41e4],.agent-command-toggle[data-v-7c2e41e4]{opacity:.66}.agent-command-copy[data-v-7c2e41e4]:hover,.agent-command-toggle[data-v-7c2e41e4]:hover{opacity:1}.agent-command-toggle[data-v-7c2e41e4]{transition:transform .12s ease}.agent-command-toggle-open[data-v-7c2e41e4]{transform:rotate(180deg)}.agent-command-copy-error[data-v-7c2e41e4]{padding:6px 10px;color:rgb(var(--v-theme-error));font-size:.8rem}.agent-command-details[data-v-7c2e41e4]{border-top:1px solid rgba(var(--v-border-color),.18)}.agent-command-full[data-v-7c2e41e4],.agent-command-code[data-v-7c2e41e4]{background:rgba(var(--v-theme-on-surface),.025)}.agent-command-full[data-v-7c2e41e4] .code-block,.agent-command-code[data-v-7c2e41e4] .code-block{padding:9px 10px 11px;font-size:11.5px;line-height:1.55}.agent-command-meta[data-v-7c2e41e4]{display:flex;align-items:center;gap:10px;padding:5px 10px;border-top:1px solid rgba(var(--v-border-color),.12);color:rgba(var(--v-theme-on-surface),.5);font-size:.68rem}.agent-command-cwd[data-v-7c2e41e4]{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-command-exit[data-v-7c2e41e4]{margin-inline-start:auto;white-space:nowrap}.agent-command-output-label[data-v-7c2e41e4]{padding:5px 10px;border-top:1px solid rgba(var(--v-border-color),.12);color:rgba(var(--v-theme-on-surface),.56);font-size:.68rem;font-weight:600}.agent-command-code.output[data-v-7c2e41e4] .code-block{color:rgba(var(--v-theme-on-surface),.8)}@media(max-width:960px){.agent-command-status[data-v-7c2e41e4]{font-size:0}.agent-command-status-dot[data-v-7c2e41e4]{margin-inline-end:1px}}.agent-command-output-plain[data-v-7c2e41e4]{max-height:45dvh;margin:0;padding:10px 12px;overflow:auto;font-size:.75rem;line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere}@layer vuetify-components{.v-combobox--chips.v-input--density-compact .v-field--variant-solo .v-label.v-field-label--floating,.v-combobox--chips.v-input--density-compact .v-field--variant-solo-inverted .v-label.v-field-label--floating,.v-combobox--chips.v-input--density-compact .v-field--variant-filled .v-label.v-field-label--floating,.v-combobox--chips.v-input--density-compact .v-field--variant-solo-filled .v-label.v-field-label--floating{top:0}.v-combobox .v-field .v-text-field__prefix,.v-combobox .v-field .v-text-field__suffix,.v-combobox .v-field .v-field__input,.v-combobox .v-field .v-field__input>input,.v-combobox .v-field.v-field{cursor:text}.v-combobox .v-field .v-field__input>input{flex:1 1}.v-combobox .v-field input{min-width:64px}.v-combobox .v-field:not(.v-field--focused) input{min-width:0}.v-combobox .v-field--dirty .v-combobox__selection{margin-inline-end:2px}.v-combobox .v-combobox__selection-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.v-combobox__content{overflow:hidden}.v-combobox__content{box-shadow:0 1px 2px rgba(var(--v-shadow-color),var(--v-shadow-key-opacity, .3)),0 2px 6px 2px rgba(var(--v-shadow-color),var(--v-shadow-ambient-opacity, .15));--v-elevation-overlay: color-mix(in srgb, var(--v-elevation-overlay-color) 4%, transparent)}.v-menu>.v-overlay__content.v-combobox__content{border-radius:4px}.v-combobox__content>.v-sheet{display:flex;flex-direction:column}.v-combobox__mask{background:rgb(var(--v-theme-surface-light))}.v-combobox__selection{display:inline-flex;align-items:center;height:1.5rem;letter-spacing:inherit;line-height:inherit;max-width:calc(100% - 4px)}.v-combobox__selection:first-child{margin-inline-start:0}.v-combobox--selecting-index .v-combobox__selection{opacity:var(--v-medium-emphasis-opacity)}.v-combobox--selecting-index .v-combobox__selection--selected{opacity:1}.v-combobox--selecting-index .v-field__input>input{caret-color:transparent}.v-combobox--single:not(.v-combobox--selection-slot).v-text-field input{flex:1 1;position:absolute;left:0;right:0;width:100%;padding-inline:inherit}.v-combobox--single:not(.v-combobox--selection-slot) .v-field--active input{transition:none}.v-combobox--single:not(.v-combobox--selection-slot) .v-field--dirty:not(.v-field--focused) input{opacity:0}.v-combobox--single:not(.v-combobox--selection-slot) .v-field--focused .v-combobox__selection{opacity:0}.v-combobox__menu-icon{margin-inline-start:4px;transition:.2s cubic-bezier(.4,0,.2,1)}.v-combobox--active-menu .v-combobox__menu-icon{transform:rotate(180deg)}}.agent-interaction-card[data-v-45060adf]{display:grid;grid-template-rows:auto minmax(0,1fr) auto;min-inline-size:0;max-inline-size:100%;min-block-size:min-content;border-block:1px solid rgba(var(--v-border-color),var(--v-border-opacity));flex:1 1 0;overflow-wrap:anywhere}.agent-interaction-content[data-v-45060adf]{min-inline-size:0;min-block-size:0;flex:1 1 auto;padding:10px 12px;overflow:auto;overscroll-behavior:contain}.agent-interaction-footer[data-v-45060adf]{flex:0 0 auto;display:flex;flex-wrap:wrap;gap:8px;padding:8px 12px;border-block-start:1px solid rgba(var(--v-border-color),var(--v-border-opacity))}.agent-interaction-footer[data-v-45060adf] .v-btn{max-inline-size:100%;height:auto;padding:.5em .75em;white-space:normal}.agent-interaction-footer[data-v-45060adf] .v-btn__content{white-space:normal;overflow-wrap:anywhere}.agent-interaction-card--pinned .agent-interaction-content[data-v-45060adf]{contain:size}.agent-interaction-card--pinned[data-v-45060adf]{background:rgb(var(--v-theme-surface));border-color:rgba(var(--v-theme-warning),.42)}.agent-interaction-heading[data-v-45060adf]{display:flex;flex:0 0 auto;align-items:center;gap:8px;padding:8px 12px;font-size:.85rem;font-weight:600}.agent-interaction-title[data-v-45060adf]{overflow-wrap:anywhere;font-weight:600;margin-block-end:6px}.agent-interaction-description[data-v-45060adf]{margin:0 0 10px;white-space:pre-wrap;overflow-wrap:anywhere;font-size:.8rem}.agent-interaction-actions[data-v-45060adf]{display:flex;flex-wrap:wrap;gap:8px;inline-size:100%}.agent-interaction-actions[data-v-45060adf]>.v-btn:not(.v-btn--icon){flex:1 1 auto}.agent-approval-more-menu[data-v-45060adf]{max-inline-size:90vw}.agent-approval-more-menu[data-v-45060adf] .v-list-item-subtitle{overflow-wrap:anywhere}.agent-interaction-actions[data-v-45060adf] .v-btn__content{min-inline-size:0;line-height:1.4}.agent-input-questions[data-v-45060adf],.agent-question[data-v-45060adf]{display:grid}.agent-input-questions[data-v-45060adf]{gap:14px}.agent-question[data-v-45060adf]{min-width:0;gap:6px}.agent-question-label[data-v-45060adf]{font-weight:600}.agent-question-text[data-v-45060adf],.agent-interaction-resolved[data-v-45060adf]{color:rgba(var(--v-theme-on-surface),.62)}.agent-question-text[data-v-45060adf]{font-size:.875rem}.agent-interaction-resolved[data-v-45060adf]{white-space:pre-wrap}.agent-provider-icon[data-v-9209bf7a]{display:block;flex:0 0 auto}.agent-activity[data-v-63e4d047]{min-inline-size:0;max-inline-size:100%;border-inline-start:2px solid rgba(var(--v-border-color),.3);overflow-wrap:anywhere}.agent-activity-heading[data-v-63e4d047]{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;list-style:none;font-size:.82rem;color:rgba(var(--v-theme-on-surface),.74)}.agent-activity-heading[data-v-63e4d047]::-webkit-details-marker{display:none}.agent-activity-heading[data-v-63e4d047]:focus-visible{outline:2px solid rgb(var(--v-theme-primary));outline-offset:-2px}.agent-activity-heading[data-v-63e4d047]:hover{background:rgba(var(--v-theme-on-surface),.04)}.agent-activity-title[data-v-63e4d047]{flex:1;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-activity-chevron[data-v-63e4d047]{flex-shrink:0}.agent-activity[open] .agent-activity-chevron[data-v-63e4d047]{transform:rotate(90deg)}.agent-activity-body[data-v-63e4d047]{min-inline-size:0;padding:0 10px 8px}.agent-tool-progress[data-v-63e4d047],.agent-inline-error[data-v-63e4d047]{padding-block:6px;white-space:pre-wrap}.agent-inline-error[data-v-63e4d047]{color:rgb(var(--v-theme-error))}.agent-file-change+.agent-file-change[data-v-63e4d047]{border-top:1px solid rgba(var(--v-border-color),var(--v-border-opacity))}.agent-file-change-name[data-v-63e4d047]{display:flex;flex-wrap:wrap;align-items:center;gap:6px;padding-block:8px}.agent-file-change-name span[data-v-63e4d047]{min-inline-size:0;overflow-wrap:anywhere}.agent-plan-steps[data-v-63e4d047]{padding-inline-start:20px}.agent-plan-steps li[data-v-63e4d047]{padding-block:4px}.agent-plan-step-status[data-v-63e4d047]{margin-inline-start:8px;font-size:.75rem;color:rgba(var(--v-theme-on-surface),.62)}.agent-plan-steps li[data-status=completed] .agent-plan-step-status[data-v-63e4d047]{color:rgb(var(--v-theme-success))}.agent-plan-steps li[data-status=inProgress] .agent-plan-step-status[data-v-63e4d047]{color:rgb(var(--v-theme-primary))}.agent-activity[data-v-63e4d047] .code-viewer,.agent-activity[data-v-63e4d047] .code-viewer-plain{min-inline-size:0;max-inline-size:100%}.agent-activity[data-v-63e4d047] .code-block{max-block-size:50dvh}.agent-turn[data-v-d9c9ba96]{min-width:0;display:flex;flex-direction:column;gap:10px;content-visibility:auto;contain-intrinsic-size:auto 160px}.agent-message-row[data-v-d9c9ba96]{min-width:0;display:flex}.agent-message-row.user[data-v-d9c9ba96]{justify-content:flex-end}.agent-message[data-v-d9c9ba96]{min-width:0;max-inline-size:100%;border-radius:8px;padding:11px 14px}.user-message[data-v-d9c9ba96]{position:relative;background:rgb(var(--v-theme-primary));color:rgb(var(--v-theme-on-primary))}.agent-turn-provider[data-v-d9c9ba96]{min-height:18px;display:inline-flex;align-items:center;align-self:flex-start;gap:5px;margin:1px 2px -4px;color:rgba(var(--v-theme-on-surface),.56);font-size:.7rem;font-weight:500;line-height:1}.agent-turn-status[data-v-d9c9ba96]{align-self:stretch;min-width:0;color:rgb(var(--v-theme-error));font-size:.76rem;white-space:pre-wrap;overflow-wrap:anywhere}.agent-response-text[data-v-d9c9ba96],.agent-response-command[data-v-d9c9ba96],.agent-reasoning-summary[data-v-d9c9ba96]{min-width:0;align-self:stretch}.agent-response-text[data-v-d9c9ba96]{padding:1px 2px;color:rgb(var(--v-theme-on-surface))}.agent-response-command[data-v-d9c9ba96]{display:block}.agent-response-text+.agent-response-text[data-v-d9c9ba96]{margin-top:-6px}.agent-response-text+.agent-response-command[data-v-d9c9ba96],.agent-response-command+.agent-response-text[data-v-d9c9ba96]{margin-top:-3px}.agent-message-source[data-v-d9c9ba96]{display:flex;align-items:center;justify-content:flex-end;gap:4px;margin-block-start:6px;color:currentColor;opacity:.72;font-size:.7rem}.agent-message-text[data-v-d9c9ba96],.agent-message-markdown[data-v-d9c9ba96],.agent-plan[data-v-d9c9ba96]{overflow-wrap:anywhere;line-height:1.55}.agent-plain-text[data-v-d9c9ba96]{white-space:pre-wrap}.agent-message-attachments[data-v-d9c9ba96]{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.agent-item-progress[data-v-d9c9ba96]{margin-top:8px}.agent-notice[data-v-d9c9ba96]{align-self:center;max-inline-size:100%;color:rgba(var(--v-theme-on-surface),.62);font-size:.82rem;text-align:center;white-space:pre-wrap;overflow-wrap:anywhere}.agent-reasoning-summary[data-v-d9c9ba96]{padding:8px 12px;border-inline-start:2px solid rgba(var(--v-border-color),.3);overflow-wrap:anywhere}.agent-summary-label[data-v-d9c9ba96]{color:rgba(var(--v-theme-on-surface),.62);font-size:.72rem;margin-bottom:4px;font-weight:600}.agent-response-text[data-phase=commentary][data-v-d9c9ba96]{padding-inline-start:10px;border-inline-start:2px solid rgba(var(--v-border-color),.18);color:rgba(var(--v-theme-on-surface),.68);font-size:.875rem}.agent-compaction[data-v-d9c9ba96]{display:flex;flex-wrap:wrap;align-items:center;gap:6px;color:rgba(var(--v-theme-on-surface),.62);font-size:.8rem}.agent-compaction[data-status=failed][data-v-d9c9ba96]{color:rgb(var(--v-theme-error))}@media(max-width:960px){.agent-message[data-v-d9c9ba96]{width:auto;max-width:94%}.agent-turn-provider[data-v-d9c9ba96],.agent-response-text[data-v-d9c9ba96],.agent-response-command[data-v-d9c9ba96],.agent-reasoning-summary[data-v-d9c9ba96]{width:100%;max-width:100%}}@media(max-width:520px){.agent-message[data-v-d9c9ba96]{max-width:96%}}.agent-resolved-interaction[data-v-d9c9ba96]{min-inline-size:0}.agent-resolved-interaction>summary[data-v-d9c9ba96]{cursor:pointer;padding-block:.5em;overflow-wrap:anywhere;color:rgba(var(--v-theme-on-surface),.72)}.agent-resolved-interaction>summary .v-icon[data-v-d9c9ba96]{margin-inline-end:.5em}.agent-conversation-shell[data-v-f2a2b510]{position:relative;flex:1 1 0;min-height:0;overflow:hidden}.agent-conversation[data-v-f2a2b510]{width:100%;height:100%;min-height:0;overflow-y:auto;padding:20px;overflow-anchor:none}.agent-conversation-content[data-v-f2a2b510]{display:flex;flex-direction:column;gap:10px}.agent-back-to-latest[data-v-f2a2b510]{position:absolute;right:18px;bottom:14px;z-index:3}@media(max-width:960px){.agent-conversation[data-v-f2a2b510]{padding:14px}}@media(max-width:520px){.agent-conversation[data-v-f2a2b510]{padding:12px 10px}}.agent-show-older-turns[data-v-f2a2b510]{align-self:center}.agent-native-granular[data-v-7d7f4fb1]{display:grid;gap:2px;padding:10px 12px;border:1px solid rgba(var(--v-border-color),var(--v-border-opacity));border-radius:8px}.agent-native-granular .agent-setting-heading[data-v-7d7f4fb1]{margin-bottom:2px}.agent-native-granular[data-v-7d7f4fb1] .v-switch{min-height:34px}.agent-setting-heading[data-v-7d7f4fb1]{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:6px;font-size:.82rem}@layer vuetify-components{.v-slider .v-slider__container input{cursor:default;padding:0;width:100%;display:none}.v-slider>.v-input__append,.v-slider>.v-input__prepend{padding:0}.v-slider__container{position:relative;min-height:inherit;width:100%;height:100%;display:flex;justify-content:center;align-items:center;cursor:pointer}.v-input--disabled .v-slider__container{opacity:var(--v-disabled-opacity)}.v-input--error:not(.v-input--disabled) .v-slider__container{color:rgb(var(--v-theme-error))}.v-slider.v-input--horizontal{align-items:center}.v-slider.v-input--horizontal>.v-input__prepend{margin-inline-end:8px}.v-slider.v-input--horizontal>.v-input__append{margin-inline-start:8px}.v-slider.v-input--horizontal>.v-input__control{margin-inline:8px 8px;min-height:32px;display:flex;align-items:center}.v-slider.v-input--vertical{justify-content:center;margin-top:12px;margin-bottom:12px}.v-slider.v-input--vertical>.v-input__control{min-height:300px}.v-slider.v-input--disabled{pointer-events:none}.v-slider--has-labels>.v-input__control{margin-bottom:4px}.v-slider__label{margin-inline-end:12px}}@layer vuetify-components{.v-slider-thumb{touch-action:none;color:rgb(var(--v-theme-surface-variant))}.v-input--error:not(.v-input--disabled) .v-slider-thumb{color:inherit}.v-slider-thumb__label{background:color-mix(in srgb,rgb(var(--v-theme-surface-variant)) 70%,transparent);color:rgb(var(--v-theme-on-surface-variant))}.v-slider-thumb__label>.v-slider-thumb__label-wedge{background:inherit}.v-slider-thumb{outline:none;position:absolute;transition:.3s cubic-bezier(.25,.8,.5,1)}.v-slider-thumb__surface{cursor:pointer;width:var(--v-slider-thumb-size);height:var(--v-slider-thumb-size);border-radius:50%;-webkit-user-select:none;user-select:none;background-color:currentColor}@media(forced-colors:active){.v-slider-thumb__surface{background-color:highlight}}.v-slider-thumb__surface:before{transition:.3s cubic-bezier(.4,0,.2,1);content:"";color:inherit;top:0;left:0;width:100%;height:100%;border-radius:50%;background:currentColor;position:absolute;pointer-events:none;opacity:0}.v-slider-thumb__surface:after{content:"";width:42px;height:42px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.v-slider-thumb__label-container{position:absolute;transition:.2s cubic-bezier(.4,0,1,1)}.v-slider-thumb__label{display:flex;align-items:center;justify-content:center;font-size:.6875rem;min-width:35px;height:25px;border-radius:4px;padding:6px;position:absolute;-webkit-user-select:none;user-select:none;transition:.2s cubic-bezier(.4,0,1,1)}.v-slider-thumb__label>.v-slider-thumb__label-wedge{width:12px;height:12px;position:absolute}.v-slider-thumb__ripple{position:absolute;left:calc(var(--v-slider-thumb-size) / -2);top:calc(var(--v-slider-thumb-size) / -2);width:calc(var(--v-slider-thumb-size) * 2);height:calc(var(--v-slider-thumb-size) * 2);background:inherit}.v-slider.v-input--horizontal .v-slider-thumb{top:50%;transform:translateY(-50%);inset-inline-start:calc(var(--v-slider-thumb-position) - var(--v-slider-thumb-size) / 2)}.v-slider.v-input--horizontal .v-slider-thumb__label-container{left:calc(var(--v-slider-thumb-size) / 2);top:0}.v-slider.v-input--horizontal .v-slider-thumb__label{bottom:calc(var(--v-slider-thumb-size) / 2)}.v-locale--is-ltr.v-slider.v-input--horizontal .v-slider-thumb__label,.v-locale--is-ltr .v-slider.v-input--horizontal .v-slider-thumb__label{transform:translate(-50%)}.v-locale--is-rtl.v-slider.v-input--horizontal .v-slider-thumb__label,.v-locale--is-rtl .v-slider.v-input--horizontal .v-slider-thumb__label{transform:translate(50%)}.v-slider.v-input--horizontal .v-slider-thumb__label>.v-slider-thumb__label-wedge{clip-path:polygon(50% 100%,0 50%,100% 50%);bottom:-5.8px}.v-slider.v-input--vertical .v-slider-thumb{top:calc(var(--v-slider-thumb-position) - var(--v-slider-thumb-size) / 2)}.v-slider.v-input--vertical .v-slider-thumb__label-container{top:calc(var(--v-slider-thumb-size) / 2);right:0}.v-slider.v-input--vertical .v-slider-thumb__label{top:-12.5px;left:calc(var(--v-slider-thumb-size) / 2)}.v-slider.v-input--vertical .v-slider-thumb__label>.v-slider-thumb__label-wedge{clip-path:polygon(0 50%,50% 0,50% 100%);left:-5.8px}.v-slider-thumb--focused .v-slider-thumb__surface:before{transform:scale(2);opacity:var(--v-focus-opacity)}.v-slider-thumb--pressed{transition:none}.v-slider-thumb--pressed .v-slider-thumb__surface:before{opacity:var(--v-pressed-opacity)}@media(hover:hover){.v-slider-thumb:hover .v-slider-thumb__surface:before{transform:scale(2)}.v-slider-thumb:hover:not(.v-slider-thumb--focused) .v-slider-thumb__surface:before{opacity:var(--v-hover-opacity)}}}@layer vuetify-components{.v-slider-track__background{background-color:rgb(var(--v-theme-surface-variant))}@media(forced-colors:active){.v-slider-track__background{background-color:highlight}}.v-slider-track__fill{background-color:rgb(var(--v-theme-surface-variant))}@media(forced-colors:active){.v-slider-track__fill{background-color:highlight}}.v-slider-track__tick{background-color:rgb(var(--v-theme-surface-variant))}.v-slider-track__tick--filled{background-color:rgb(var(--v-theme-surface-light))}.v-slider-track{border-radius:6px}@media(forced-colors:active){.v-slider-track{border:thin solid buttontext}}.v-slider-track__background,.v-slider-track__fill{position:absolute;transition:.3s cubic-bezier(.25,.8,.5,1);border-radius:inherit}.v-slider--pressed .v-slider-track__background,.v-slider--pressed .v-slider-track__fill{transition:none}.v-input--error:not(.v-input--disabled) .v-slider-track__background,.v-input--error:not(.v-input--disabled) .v-slider-track__fill{background-color:currentColor}.v-slider-track__ticks{height:100%;width:100%;position:relative}.v-slider-track__tick{position:absolute;opacity:0;transition:.2s opacity cubic-bezier(.4,0,.2,1);border-radius:2px;width:var(--v-slider-tick-size);height:var(--v-slider-tick-size);transform:translate(calc(var(--v-slider-tick-size) / -2),calc(var(--v-slider-tick-size) / -2))}.v-locale--is-ltr.v-slider-track__tick--first .v-slider-track__tick-label,.v-locale--is-ltr .v-slider-track__tick--first .v-slider-track__tick-label{transform:none}.v-locale--is-rtl.v-slider-track__tick--first .v-slider-track__tick-label,.v-locale--is-rtl .v-slider-track__tick--first .v-slider-track__tick-label{transform:translate(100%)}.v-locale--is-ltr.v-slider-track__tick--last .v-slider-track__tick-label,.v-locale--is-ltr .v-slider-track__tick--last .v-slider-track__tick-label{transform:translate(-100%)}.v-locale--is-rtl.v-slider-track__tick--last .v-slider-track__tick-label,.v-locale--is-rtl .v-slider-track__tick--last .v-slider-track__tick-label{transform:none}.v-slider-track__tick-label{position:absolute;-webkit-user-select:none;user-select:none;white-space:nowrap}.v-slider.v-input--horizontal .v-slider-track{display:flex;align-items:center;width:100%;height:calc(var(--v-slider-track-size) + 2px);touch-action:pan-y}.v-slider.v-input--horizontal .v-slider-track__background{height:var(--v-slider-track-size)}.v-slider.v-input--horizontal .v-slider-track__fill{height:inherit}.v-slider.v-input--horizontal .v-slider-track__tick{margin-top:calc(calc(var(--v-slider-track-size) + 2px) / 2)}.v-locale--is-rtl.v-slider.v-input--horizontal .v-slider-track__tick,.v-locale--is-rtl .v-slider.v-input--horizontal .v-slider-track__tick{transform:translate(calc(var(--v-slider-tick-size) / 2),calc(var(--v-slider-tick-size) / -2))}.v-slider.v-input--horizontal .v-slider-track__tick .v-slider-track__tick-label{margin-top:calc(var(--v-slider-track-size) / 2 + 8px)}.v-locale--is-ltr.v-slider.v-input--horizontal .v-slider-track__tick .v-slider-track__tick-label,.v-locale--is-ltr .v-slider.v-input--horizontal .v-slider-track__tick .v-slider-track__tick-label{transform:translate(-50%)}.v-locale--is-rtl.v-slider.v-input--horizontal .v-slider-track__tick .v-slider-track__tick-label,.v-locale--is-rtl .v-slider.v-input--horizontal .v-slider-track__tick .v-slider-track__tick-label{transform:translate(50%)}.v-slider.v-input--horizontal .v-slider-track__tick--first{margin-inline-start:calc(var(--v-slider-tick-size) + 1px)}.v-locale--is-ltr.v-slider.v-input--horizontal .v-slider-track__tick--first .v-slider-track__tick-label,.v-locale--is-ltr .v-slider.v-input--horizontal .v-slider-track__tick--first .v-slider-track__tick-label,.v-locale--is-rtl.v-slider.v-input--horizontal .v-slider-track__tick--first .v-slider-track__tick-label,.v-locale--is-rtl .v-slider.v-input--horizontal .v-slider-track__tick--first .v-slider-track__tick-label{transform:translate(0)}.v-slider.v-input--horizontal .v-slider-track__tick--last{margin-inline-start:calc(100% - var(--v-slider-tick-size) - 1px)}.v-locale--is-ltr.v-slider.v-input--horizontal .v-slider-track__tick--last .v-slider-track__tick-label,.v-locale--is-ltr .v-slider.v-input--horizontal .v-slider-track__tick--last .v-slider-track__tick-label{transform:translate(-100%)}.v-locale--is-rtl.v-slider.v-input--horizontal .v-slider-track__tick--last .v-slider-track__tick-label,.v-locale--is-rtl .v-slider.v-input--horizontal .v-slider-track__tick--last .v-slider-track__tick-label{transform:translate(100%)}.v-slider.v-input--vertical .v-slider-track{height:100%;display:flex;justify-content:center;width:calc(var(--v-slider-track-size) + 2px);touch-action:pan-x}.v-slider.v-input--vertical .v-slider-track__background{width:var(--v-slider-track-size)}.v-slider.v-input--vertical .v-slider-track__fill{width:inherit}.v-slider.v-input--vertical .v-slider-track__ticks{height:100%}.v-slider.v-input--vertical .v-slider-track__tick{margin-inline-start:calc(calc(var(--v-slider-track-size) + 2px) / 2);transform:translate(calc(var(--v-slider-tick-size) / -2),calc(var(--v-slider-tick-size) / 2))}.v-locale--is-rtl.v-slider.v-input--vertical .v-slider-track__tick,.v-locale--is-rtl .v-slider.v-input--vertical .v-slider-track__tick{transform:translate(calc(var(--v-slider-tick-size) / 2),calc(var(--v-slider-tick-size) / 2))}.v-slider.v-input--vertical .v-slider-track__tick--first{bottom:calc(0% + var(--v-slider-tick-size) + 1px)}.v-slider.v-input--vertical .v-slider-track__tick--last{bottom:calc(100% - var(--v-slider-tick-size) - 1px)}.v-slider.v-input--vertical .v-slider-track__tick .v-slider-track__tick-label{margin-inline-start:calc(var(--v-slider-track-size) / 2 + 12px);transform:translateY(-50%)}.v-slider-track__ticks--always-show .v-slider-track__tick,.v-slider--focused .v-slider-track__tick{opacity:1}.v-slider-track__background--opacity{opacity:.38}}.agent-model-settings-dialog[data-v-6560ca7a]{display:grid;gap:18px}.agent-model-select-host[data-v-6560ca7a]{min-width:0}.agent-model-select-menu{overflow-x:hidden}.agent-model-select-menu .v-list-item{min-width:0}.agent-model-select-menu .v-list-item-title,.agent-model-select-menu .v-list-item-subtitle{min-width:0;overflow-wrap:anywhere}.agent-model-select-menu .v-list-item-subtitle{white-space:normal}.agent-reasoning-slider[data-v-6560ca7a]{min-width:0}.agent-setting-heading[data-v-6560ca7a]{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:6px;font-size:.82rem}.agent-setting-heading strong[data-v-6560ca7a]{color:rgb(var(--v-theme-primary));font-weight:600;text-align:end}.agent-reasoning-slider[data-v-6560ca7a] .v-slider{margin-inline:4px}.agent-reasoning-slider[data-v-6560ca7a] .v-slider-track__background{background:rgba(var(--v-theme-on-surface),.24);opacity:1}.agent-reasoning-slider[data-v-6560ca7a] .v-slider-track__fill,.agent-reasoning-slider[data-v-6560ca7a] .v-slider-thumb__surface{background:rgb(var(--v-theme-primary));opacity:1}.agent-reasoning-slider[data-v-6560ca7a] .v-slider-thumb__surface{box-shadow:0 0 0 2px rgb(var(--v-theme-surface)),0 1px 5px #00000052}.agent-reasoning-slider[data-v-6560ca7a] .v-slider-track__tick{background:rgba(var(--v-theme-on-surface),.58);opacity:1}.agent-reasoning-slider[data-v-6560ca7a] .v-slider-track__tick--filled{background:rgb(var(--v-theme-primary))}.agent-setting-description[data-v-6560ca7a]{margin-top:2px;color:rgba(var(--v-theme-on-surface),.62);font-size:.74rem;line-height:1.4}.agent-review-fields[data-v-c8b9a40c]{display:grid;gap:14px}.agent-review-error[data-v-c8b9a40c]{color:rgb(var(--v-theme-error))}.agent-session-header[data-v-1cae8321]{position:relative;z-index:2;flex:0 0 auto;background:rgb(var(--v-theme-surface))}.agent-session-header-info[data-v-1cae8321]{flex:1 1 auto;min-width:0}.agent-session-provider-title[data-v-1cae8321]{display:flex;min-width:0;align-items:center;gap:7px}.agent-session-model-label[data-v-1cae8321]{min-width:0;overflow:hidden;color:rgba(var(--v-theme-on-surface),.62);text-overflow:ellipsis;white-space:nowrap}.agent-session-header-status[data-v-1cae8321]{flex:0 0 auto;align-self:center;display:inline-flex;min-height:30px;align-items:center;gap:7px;padding:4px 8px;border-radius:8px;color:rgba(var(--v-theme-on-surface),.74);font-size:.75rem;white-space:nowrap}.agent-session-status-dot[data-v-1cae8321]{width:8px;height:8px;border-radius:50%;background:rgba(var(--v-theme-on-surface),.4)}.agent-session-status-dot[data-status=idle][data-v-1cae8321]{background:rgb(var(--v-theme-success))}.agent-session-status-dot[data-status=running][data-v-1cae8321]{background:rgb(var(--v-theme-primary))}.agent-session-status-dot[data-status=waiting_input][data-v-1cae8321]{background:rgb(var(--v-theme-warning))}.agent-session-status-dot[data-status=waiting_input][data-attention=input][data-v-1cae8321]{background:rgb(var(--v-theme-info))}.agent-session-status-dot[data-status=error][data-v-1cae8321]{background:rgb(var(--v-theme-error))}.agent-session-header-switch[data-v-1cae8321]{flex:0 0 auto;align-self:center;min-width:0;padding-inline:9px;font-size:.75rem;text-transform:none}.agent-session-header-switch[data-v-1cae8321] .v-btn__prepend{margin-inline-end:4px}.agent-session-header-switch[data-v-1cae8321] .v-btn__append{margin-inline-start:3px}.agent-session-switch-menu[data-v-1cae8321]{padding:4px}.agent-session-switch-menu[data-v-1cae8321] .v-list-item{min-height:32px;padding-inline:7px;border-radius:7px}.agent-session-switch-menu[data-v-1cae8321] .v-list-item__prepend{width:auto;margin-inline-end:7px}.agent-session-switch-provider-icon[data-v-1cae8321]{width:21px;height:21px;display:grid;place-items:center}.agent-session-token-usage[data-v-1cae8321]{margin-top:4px;color:rgba(var(--v-theme-on-surface),.62);font-size:.72rem}@media(max-width:1100px){.agent-session-header[data-v-1cae8321]{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:start;gap:5px 8px;padding-block:10px}.agent-session-header-info[data-v-1cae8321]{grid-column:2}.agent-session-header-status[data-v-1cae8321]{grid-column:3}.agent-session-header-switch[data-v-1cae8321]{grid-column:4;margin-inline-start:0}.agent-session-provider-title[data-v-1cae8321]{flex-wrap:wrap}}@media(max-width:520px){.agent-session-header[data-v-1cae8321]{padding-inline:10px}.agent-session-header-status[data-v-1cae8321]{width:30px;min-width:30px;justify-content:center;padding-inline:0}.agent-session-header-status-text[data-v-1cae8321]{display:none}.agent-session-header-switch[data-v-1cae8321]{width:38px;min-width:38px;padding-inline:5px}.agent-session-header-switch[data-v-1cae8321] .v-btn__content{display:none}.agent-session-header-switch[data-v-1cae8321] .v-btn__prepend{margin-inline:0}.agent-session-header-switch[data-v-1cae8321] .v-btn__append{display:none}}.agent-create[data-v-8cdd509d]{display:grid;gap:10px;padding:14px}.agent-provider-strip[data-v-5b1c74e5]{flex:0 0 auto;min-height:42px;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px;padding:8px 12px}.agent-provider-pill[data-v-5b1c74e5]{display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border:1px solid rgba(var(--v-border-color),.18);border-radius:999px;background:rgba(var(--v-theme-on-surface),.045);color:rgba(var(--v-theme-on-surface),.72);font-size:.7rem;font-weight:500;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none}.agent-provider-pill[data-v-5b1c74e5]:hover,.agent-provider-pill[data-v-5b1c74e5]:focus-visible,.agent-provider-pill[data-expanded=true][data-v-5b1c74e5]{border-color:rgba(var(--v-theme-primary),.46);background:rgba(var(--v-theme-primary),.09);outline:none}.agent-provider-pill-name[data-v-5b1c74e5]{line-height:1}.agent-provider-pill-dot[data-v-5b1c74e5]{width:6px;height:6px;border-radius:50%;background:rgba(var(--v-theme-on-surface),.32)}.agent-provider-pill[data-state=ready] .agent-provider-pill-dot[data-v-5b1c74e5]{background:rgb(var(--v-theme-success))}.agent-provider-pill[data-state=warning] .agent-provider-pill-dot[data-v-5b1c74e5]{background:rgb(var(--v-theme-warning))}.agent-provider-pill[data-state=unavailable][data-v-5b1c74e5]{opacity:.55}.agent-provider-pill[data-state=unavailable] .agent-provider-pill-dot[data-v-5b1c74e5]{background:rgb(var(--v-theme-error))}.agent-provider-quota[data-v-5b1c74e5]{flex:0 0 auto;padding:8px 12px 10px;border-top:1px solid rgba(var(--v-border-color),.12)}.agent-provider-quota-header[data-v-5b1c74e5],.agent-provider-quota-extra[data-v-5b1c74e5]{display:flex;align-items:center;justify-content:space-between;gap:8px;color:rgba(var(--v-theme-on-surface),.7);font-size:.68rem;font-weight:600}.agent-provider-quota-header>span[data-v-5b1c74e5]:last-child{color:rgba(var(--v-theme-on-surface),.46);font-size:.62rem;font-weight:500;text-transform:uppercase}.agent-provider-quota-unavailable[data-v-5b1c74e5]{margin-top:7px;color:rgba(var(--v-theme-on-surface),.46);font-size:.64rem;line-height:1.4}.agent-provider-quota-row[data-v-5b1c74e5]{display:grid;grid-template-columns:max-content minmax(0,1fr) auto;align-items:center;gap:6px 8px;margin-top:7px}.agent-provider-quota-label[data-v-5b1c74e5],.agent-provider-quota-value[data-v-5b1c74e5],.agent-provider-quota-reset[data-v-5b1c74e5]{font-size:.64rem;white-space:nowrap}.agent-provider-quota-label[data-v-5b1c74e5]{color:rgba(var(--v-theme-on-surface),.64)}.agent-provider-quota-value[data-v-5b1c74e5],.agent-provider-quota-reset[data-v-5b1c74e5]{text-align:end}.agent-provider-quota-value[data-v-5b1c74e5]{color:rgba(var(--v-theme-on-surface),.72)}.agent-provider-quota-reset[data-v-5b1c74e5]{grid-column:2 / 4;margin-top:-3px;color:rgba(var(--v-theme-on-surface),.4)}.agent-provider-quota-track[data-v-5b1c74e5]{height:4px;overflow:hidden;border-radius:999px;background:rgba(var(--v-theme-on-surface),.1)}.agent-provider-quota-fill[data-v-5b1c74e5]{display:block;height:100%;border-radius:inherit;background:rgb(var(--v-theme-primary))}.agent-provider-quota-extra[data-v-5b1c74e5]{margin-top:8px;padding-top:7px;border-top:1px solid rgba(var(--v-border-color),.08);font-weight:500}.agent-sidebar[data-v-e6bcf543]{min-height:0;overflow:hidden;display:flex;flex-direction:column;color:rgb(var(--v-theme-on-surface))}.agent-error[data-v-e6bcf543]{display:flex;gap:8px;align-items:center;padding:10px 14px;color:rgb(var(--v-theme-error));font-size:.875rem;white-space:pre-wrap;overflow-wrap:anywhere}.agent-session-list[data-v-e6bcf543]{min-height:0;overflow-y:auto;flex:1 1 auto}.agent-session-provider-icon[data-v-e6bcf543]{margin-inline-end:10px;color:rgba(var(--v-theme-on-surface),.72)}.agent-status-dot[data-v-e6bcf543]{width:8px;height:8px;border-radius:50%;background:rgba(var(--v-theme-on-surface),.4)}.agent-status-dot[data-status=idle][data-v-e6bcf543]{background:rgb(var(--v-theme-success))}.agent-status-dot[data-status=running][data-v-e6bcf543]{background:rgb(var(--v-theme-primary))}.agent-status-dot[data-status=waiting_input][data-v-e6bcf543]{background:rgb(var(--v-theme-warning))}.agent-status-dot[data-status=waiting_input][data-attention=input][data-v-e6bcf543]{background:rgb(var(--v-theme-info))}.agent-status-dot[data-status=error][data-v-e6bcf543]{background:rgb(var(--v-theme-error))}@media(max-width:1100px){.agent-sidebar[data-v-e6bcf543]{min-height:0;max-height:calc(100dvh - var(--v-layout-top, 0px) - var(--app-content-block-start, 0px) - var(--app-content-block-end, 0px))}}.agent-layout[data-v-3189043d]{display:grid;grid-template-columns:minmax(0,.34fr) minmax(0,1fr);gap:16px;grid-template-rows:minmax(0,1fr);flex:1 1 0;min-block-size:0;min-inline-size:0;color:rgb(var(--v-theme-on-surface))}.agent-main[data-v-3189043d]{min-height:0;overflow:hidden;display:flex;flex-direction:column;color:rgb(var(--v-theme-on-surface))}.agent-session-controls-button[data-v-3189043d]{flex:0 0 auto}.agent-session-controls-menu[data-v-3189043d]{max-width:90vw}.agent-pending-interactions[data-v-3189043d]{padding-inline:12px;display:flex;flex-direction:column;min-block-size:min-content;gap:8px;flex:1 1 0;overflow:auto;overscroll-behavior:contain}@media(max-width:1100px){.agent-layout[data-v-3189043d]{grid-template-columns:1fr;min-height:0}}.agent-error[data-v-3189043d]{white-space:pre-wrap;overflow-wrap:anywhere}