@getpaseo/server 0.1.70 → 0.1.72

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 (197) hide show
  1. package/dist/server/client/daemon-client.d.ts +24 -4
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +41 -2
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-manager.d.ts +17 -4
  6. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  7. package/dist/server/server/agent/agent-manager.js +177 -21
  8. package/dist/server/server/agent/agent-manager.js.map +1 -1
  9. package/dist/server/server/agent/agent-metadata-generator.d.ts +2 -0
  10. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  11. package/dist/server/server/agent/agent-metadata-generator.js +16 -7
  12. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  13. package/dist/server/server/agent/agent-projections.d.ts +6 -2
  14. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  15. package/dist/server/server/agent/agent-projections.js +32 -0
  16. package/dist/server/server/agent/agent-projections.js.map +1 -1
  17. package/dist/server/server/agent/agent-prompt.d.ts +72 -0
  18. package/dist/server/server/agent/agent-prompt.d.ts.map +1 -0
  19. package/dist/server/server/agent/agent-prompt.js +169 -0
  20. package/dist/server/server/agent/agent-prompt.js.map +1 -0
  21. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  22. package/dist/server/server/agent/agent-response-loop.js +2 -1
  23. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.d.ts +12 -0
  25. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  26. package/dist/server/server/agent/create-agent-mode.d.ts +2 -0
  27. package/dist/server/server/agent/create-agent-mode.d.ts.map +1 -1
  28. package/dist/server/server/agent/create-agent-mode.js +3 -0
  29. package/dist/server/server/agent/create-agent-mode.js.map +1 -1
  30. package/dist/server/server/agent/create-agent-title.d.ts +8 -0
  31. package/dist/server/server/agent/create-agent-title.d.ts.map +1 -0
  32. package/dist/server/server/agent/create-agent-title.js +29 -0
  33. package/dist/server/server/agent/create-agent-title.js.map +1 -0
  34. package/dist/server/server/agent/import-sessions.d.ts +52 -0
  35. package/dist/server/server/agent/import-sessions.d.ts.map +1 -0
  36. package/dist/server/server/agent/import-sessions.js +208 -0
  37. package/dist/server/server/agent/import-sessions.js.map +1 -0
  38. package/dist/server/server/agent/mcp-server.d.ts +1 -1
  39. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  40. package/dist/server/server/agent/mcp-server.js +29 -6
  41. package/dist/server/server/agent/mcp-server.js.map +1 -1
  42. package/dist/server/server/agent/mcp-shared.d.ts +1 -47
  43. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  44. package/dist/server/server/agent/mcp-shared.js +0 -145
  45. package/dist/server/server/agent/mcp-shared.js.map +1 -1
  46. package/dist/server/server/agent/provider-registry.d.ts +7 -0
  47. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  48. package/dist/server/server/agent/provider-registry.js +7 -1
  49. package/dist/server/server/agent/provider-registry.js.map +1 -1
  50. package/dist/server/server/agent/providers/claude/agent.d.ts.map +1 -1
  51. package/dist/server/server/agent/providers/claude/agent.js +15 -0
  52. package/dist/server/server/agent/providers/claude/agent.js.map +1 -1
  53. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +25 -0
  54. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts.map +1 -0
  55. package/dist/server/server/agent/providers/codex/app-server-transport.js +183 -0
  56. package/dist/server/server/agent/providers/codex/app-server-transport.js.map +1 -0
  57. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +9 -22
  58. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  59. package/dist/server/server/agent/providers/codex-app-server-agent.js +39 -171
  60. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  61. package/dist/server/server/agent/providers/opencode/runtime.d.ts +27 -0
  62. package/dist/server/server/agent/providers/opencode/runtime.d.ts.map +1 -0
  63. package/dist/server/server/agent/providers/opencode/runtime.js +5 -0
  64. package/dist/server/server/agent/providers/opencode/runtime.js.map +1 -0
  65. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +55 -0
  66. package/dist/server/server/agent/providers/opencode/server-manager.d.ts.map +1 -0
  67. package/dist/server/server/agent/providers/opencode/server-manager.js +255 -0
  68. package/dist/server/server/agent/providers/opencode/server-manager.js.map +1 -0
  69. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +22 -0
  70. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts.map +1 -0
  71. package/dist/server/server/agent/providers/opencode/test-server-manager.js +28 -0
  72. package/dist/server/server/agent/providers/opencode/test-server-manager.js.map +1 -0
  73. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts +75 -0
  74. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts.map +1 -0
  75. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js +169 -0
  76. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js.map +1 -0
  77. package/dist/server/server/agent/providers/opencode-agent.d.ts +7 -36
  78. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  79. package/dist/server/server/agent/providers/opencode-agent.js +199 -261
  80. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  81. package/dist/server/server/agent/providers/pi-direct-agent.d.ts +8 -3
  82. package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -1
  83. package/dist/server/server/agent/providers/pi-direct-agent.js +44 -34
  84. package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -1
  85. package/dist/server/server/chat/chat-mentions.d.ts +24 -8
  86. package/dist/server/server/chat/chat-mentions.d.ts.map +1 -1
  87. package/dist/server/server/chat/chat-mentions.js +77 -35
  88. package/dist/server/server/chat/chat-mentions.js.map +1 -1
  89. package/dist/server/server/chat/chat-service.d.ts +4 -0
  90. package/dist/server/server/chat/chat-service.d.ts.map +1 -1
  91. package/dist/server/server/chat/chat-service.js +9 -0
  92. package/dist/server/server/chat/chat-service.js.map +1 -1
  93. package/dist/server/server/checkout/status-projection.d.ts +19 -0
  94. package/dist/server/server/checkout/status-projection.d.ts.map +1 -0
  95. package/dist/server/server/checkout/status-projection.js +98 -0
  96. package/dist/server/server/checkout/status-projection.js.map +1 -0
  97. package/dist/server/server/daemon-keypair.d.ts.map +1 -1
  98. package/dist/server/server/daemon-keypair.js +4 -2
  99. package/dist/server/server/daemon-keypair.js.map +1 -1
  100. package/dist/server/server/exports.d.ts +1 -0
  101. package/dist/server/server/exports.d.ts.map +1 -1
  102. package/dist/server/server/exports.js +1 -0
  103. package/dist/server/server/exports.js.map +1 -1
  104. package/dist/server/server/paseo-home.js +2 -2
  105. package/dist/server/server/paseo-home.js.map +1 -1
  106. package/dist/server/server/paseo-worktree-service.d.ts +2 -1
  107. package/dist/server/server/paseo-worktree-service.d.ts.map +1 -1
  108. package/dist/server/server/paseo-worktree-service.js +30 -3
  109. package/dist/server/server/paseo-worktree-service.js.map +1 -1
  110. package/dist/server/server/persisted-config.d.ts.map +1 -1
  111. package/dist/server/server/persisted-config.js +5 -4
  112. package/dist/server/server/persisted-config.js.map +1 -1
  113. package/dist/server/server/private-files.d.ts +7 -0
  114. package/dist/server/server/private-files.d.ts.map +1 -0
  115. package/dist/server/server/private-files.js +42 -0
  116. package/dist/server/server/private-files.js.map +1 -0
  117. package/dist/server/server/push/token-store.d.ts.map +1 -1
  118. package/dist/server/server/push/token-store.js +4 -6
  119. package/dist/server/server/push/token-store.js.map +1 -1
  120. package/dist/server/server/schedule/service.d.ts +1 -1
  121. package/dist/server/server/schedule/service.d.ts.map +1 -1
  122. package/dist/server/server/schedule/service.js +14 -11
  123. package/dist/server/server/schedule/service.js.map +1 -1
  124. package/dist/server/server/server-id.d.ts.map +1 -1
  125. package/dist/server/server/server-id.js +8 -3
  126. package/dist/server/server/server-id.js.map +1 -1
  127. package/dist/server/server/session.d.ts +3 -17
  128. package/dist/server/server/session.d.ts.map +1 -1
  129. package/dist/server/server/session.js +174 -254
  130. package/dist/server/server/session.js.map +1 -1
  131. package/dist/server/server/workspace-directory.d.ts +0 -2
  132. package/dist/server/server/workspace-directory.d.ts.map +1 -1
  133. package/dist/server/server/workspace-directory.js +9 -26
  134. package/dist/server/server/workspace-directory.js.map +1 -1
  135. package/dist/server/server/workspace-git-service.d.ts +2 -1
  136. package/dist/server/server/workspace-git-service.d.ts.map +1 -1
  137. package/dist/server/server/workspace-git-service.js +1 -1
  138. package/dist/server/server/workspace-git-service.js.map +1 -1
  139. package/dist/server/server/worktree-branch-name-generator.d.ts +2 -0
  140. package/dist/server/server/worktree-branch-name-generator.d.ts.map +1 -1
  141. package/dist/server/server/worktree-branch-name-generator.js +18 -11
  142. package/dist/server/server/worktree-branch-name-generator.js.map +1 -1
  143. package/dist/server/services/github-service.d.ts +13 -0
  144. package/dist/server/services/github-service.d.ts.map +1 -1
  145. package/dist/server/services/github-service.js +12 -2
  146. package/dist/server/services/github-service.js.map +1 -1
  147. package/dist/server/shared/agent-labels.d.ts +2 -0
  148. package/dist/server/shared/agent-labels.d.ts.map +1 -0
  149. package/dist/server/shared/agent-labels.js +2 -0
  150. package/dist/server/shared/agent-labels.js.map +1 -0
  151. package/dist/server/shared/agent-state-bucket.d.ts +13 -0
  152. package/dist/server/shared/agent-state-bucket.d.ts.map +1 -0
  153. package/dist/server/shared/agent-state-bucket.js +41 -0
  154. package/dist/server/shared/agent-state-bucket.js.map +1 -0
  155. package/dist/server/shared/git-remote.d.ts +16 -0
  156. package/dist/server/shared/git-remote.d.ts.map +1 -0
  157. package/dist/server/shared/git-remote.js +72 -0
  158. package/dist/server/shared/git-remote.js.map +1 -0
  159. package/dist/server/shared/importable-providers.d.ts +7 -0
  160. package/dist/server/shared/importable-providers.d.ts.map +1 -0
  161. package/dist/server/shared/importable-providers.js +7 -0
  162. package/dist/server/shared/importable-providers.js.map +1 -0
  163. package/dist/server/shared/messages.d.ts +7148 -690
  164. package/dist/server/shared/messages.d.ts.map +1 -1
  165. package/dist/server/shared/messages.js +56 -4
  166. package/dist/server/shared/messages.js.map +1 -1
  167. package/dist/server/utils/build-metadata-prompt.d.ts +14 -0
  168. package/dist/server/utils/build-metadata-prompt.d.ts.map +1 -0
  169. package/dist/server/utils/build-metadata-prompt.js +28 -0
  170. package/dist/server/utils/build-metadata-prompt.js.map +1 -0
  171. package/dist/server/utils/checkout-git.d.ts +3 -1
  172. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  173. package/dist/server/utils/checkout-git.js +3 -0
  174. package/dist/server/utils/checkout-git.js.map +1 -1
  175. package/dist/server/utils/github-remote.d.ts +3 -7
  176. package/dist/server/utils/github-remote.d.ts.map +1 -1
  177. package/dist/server/utils/github-remote.js +4 -70
  178. package/dist/server/utils/github-remote.js.map +1 -1
  179. package/dist/server/utils/paseo-config-schema.d.ts +625 -0
  180. package/dist/server/utils/paseo-config-schema.d.ts.map +1 -1
  181. package/dist/server/utils/paseo-config-schema.js +17 -0
  182. package/dist/server/utils/paseo-config-schema.js.map +1 -1
  183. package/dist/server/utils/wrap-user-instructions.d.ts +2 -0
  184. package/dist/server/utils/wrap-user-instructions.d.ts.map +1 -0
  185. package/dist/server/utils/wrap-user-instructions.js +13 -0
  186. package/dist/server/utils/wrap-user-instructions.js.map +1 -0
  187. package/dist/src/server/paseo-home.js +2 -2
  188. package/dist/src/server/paseo-home.js.map +1 -1
  189. package/dist/src/server/persisted-config.js +5 -4
  190. package/dist/src/server/persisted-config.js.map +1 -1
  191. package/dist/src/server/private-files.js +42 -0
  192. package/dist/src/server/private-files.js.map +1 -0
  193. package/dist/src/shared/messages.js +56 -4
  194. package/dist/src/shared/messages.js.map +1 -1
  195. package/dist/src/utils/paseo-config-schema.js +17 -0
  196. package/dist/src/utils/paseo-config-schema.js.map +1 -1
  197. package/package.json +4 -4
@@ -1,17 +1,18 @@
1
+ import { readdir, readFile } from "node:fs/promises";
1
2
  import { homedir } from "node:os";
2
- import { createOpencodeClient, } from "@opencode-ai/sdk/v2/client";
3
- import net from "node:net";
4
- import { z } from "zod";
5
- import { createProviderEnvSpec, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
3
+ import path from "node:path";
6
4
  import { findExecutable, isCommandAvailable } from "../../../utils/executable.js";
7
- import { terminateWithTreeKill } from "../../../utils/tree-kill.js";
5
+ import { z } from "zod";
6
+ import { createProviderEnvSpec } from "../provider-launch-config.js";
8
7
  import { withTimeout } from "../../../utils/promise-timeout.js";
9
- import { execCommand, spawnProcess } from "../../../utils/spawn.js";
8
+ import { execCommand } from "../../../utils/spawn.js";
10
9
  import { buildToolCallDisplayModel } from "../../../shared/tool-call-display.js";
11
10
  import { mapOpencodeToolCall } from "./opencode/tool-call-mapper.js";
11
+ import { OpenCodeServerManager } from "./opencode/server-manager.js";
12
12
  import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, resolveBinaryVersion, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
13
13
  import { runProviderTurn } from "./provider-runner.js";
14
14
  import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
15
+ import { createSdkOpenCodeClient, } from "./opencode/runtime.js";
15
16
  const OPENCODE_CAPABILITIES = {
16
17
  supportsStreaming: true,
17
18
  supportsSessionPersistence: true,
@@ -22,6 +23,7 @@ const OPENCODE_CAPABILITIES = {
22
23
  };
23
24
  const OPENCODE_BUILD_MODE_ID = "build";
24
25
  const OPENCODE_FULL_ACCESS_MODE_ID = "full-access";
26
+ const OPENCODE_STORAGE_SESSION_LIMIT = 200;
25
27
  const DEFAULT_MODES = [
26
28
  {
27
29
  id: OPENCODE_BUILD_MODE_ID,
@@ -39,10 +41,46 @@ const DEFAULT_MODES = [
39
41
  description: "Automatically approves all tool permission prompts for the session",
40
42
  },
41
43
  ];
44
+ const OpenCodeStoredSessionSchema = z
45
+ .object({
46
+ id: z.string().min(1),
47
+ directory: z.string().min(1),
48
+ title: z.string().nullable().optional(),
49
+ time: z
50
+ .object({
51
+ created: z.number().optional(),
52
+ updated: z.number().optional(),
53
+ })
54
+ .optional(),
55
+ })
56
+ .passthrough();
57
+ const OpenCodeStoredMessageSchema = z
58
+ .object({
59
+ id: z.string().min(1),
60
+ sessionID: z.string().min(1),
61
+ role: z.string().optional(),
62
+ time: z
63
+ .object({
64
+ created: z.number().optional(),
65
+ completed: z.number().optional(),
66
+ })
67
+ .optional(),
68
+ })
69
+ .passthrough();
70
+ const OpenCodeStoredPartSchema = z
71
+ .object({
72
+ type: z.string().optional(),
73
+ text: z.string().optional(),
74
+ time: z
75
+ .object({
76
+ start: z.number().optional(),
77
+ end: z.number().optional(),
78
+ })
79
+ .optional(),
80
+ })
81
+ .passthrough();
42
82
  const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"];
43
83
  const OPENCODE_PROVIDER_LIST_TIMEOUT_MS = 30000;
44
- const OPENCODE_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5000;
45
- const OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS = 1000;
46
84
  const OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS = [
47
85
  { name: "compact", description: "Compact the current session", argumentHint: "" },
48
86
  { name: "summarize", description: "Compact the current session", argumentHint: "" },
@@ -121,13 +159,6 @@ const OpencodeToolPartToTimelineItemSchema = OpencodeToolPartTimelineEnvelopeSch
121
159
  output: part.output,
122
160
  error: part.error,
123
161
  }));
124
- async function resolveOpenCodeBinary() {
125
- const found = await findExecutable("opencode");
126
- if (found) {
127
- return found;
128
- }
129
- throw new Error("OpenCode binary not found. Install OpenCode (https://github.com/opencode-ai/opencode) and ensure it is available in your shell PATH.");
130
- }
131
162
  function toOpenCodeMcpConfig(config) {
132
163
  if (config.type === "stdio") {
133
164
  return {
@@ -237,22 +268,6 @@ function isAlreadyPresentMcpError(error) {
237
268
  const normalized = toDiagnosticErrorMessage(error).toLowerCase();
238
269
  return MCP_ALREADY_PRESENT_ERROR_TOKENS.some((token) => normalized.includes(token));
239
270
  }
240
- async function findAvailablePort() {
241
- return new Promise((resolve, reject) => {
242
- const server = net.createServer();
243
- server.listen(0, () => {
244
- const address = server.address();
245
- if (address && typeof address === "object") {
246
- const port = address.port;
247
- server.close(() => resolve(port));
248
- }
249
- else {
250
- server.close(() => reject(new Error("Failed to get port")));
251
- }
252
- });
253
- server.on("error", reject);
254
- });
255
- }
256
271
  function resolvePartDedupeKey(part, partType) {
257
272
  if (part.id.trim().length > 0) {
258
273
  return `${partType}:${part.id}`;
@@ -485,6 +500,133 @@ function buildOpenCodePromptParts(prompt) {
485
500
  }
486
501
  return output;
487
502
  }
503
+ function resolveOpenCodeStorageRoot() {
504
+ const xdgDataHome = process.env.XDG_DATA_HOME;
505
+ const dataHome = typeof xdgDataHome === "string" && xdgDataHome.trim().length > 0
506
+ ? xdgDataHome
507
+ : path.join(homedir(), ".local", "share");
508
+ return path.join(dataHome, "opencode", "storage");
509
+ }
510
+ async function collectOpenCodePersistedAgentsFromStorage(storageRoot, options) {
511
+ const sessions = await readOpenCodeStoredSessions(path.join(storageRoot, "session"));
512
+ const limit = options?.limit ?? OPENCODE_STORAGE_SESSION_LIMIT;
513
+ const candidates = sessions
514
+ .filter((session) => !options?.cwd || session.directory === options.cwd)
515
+ .sort((left, right) => getOpenCodeSessionTimestamp(right) - getOpenCodeSessionTimestamp(left))
516
+ .slice(0, limit);
517
+ return await Promise.all(candidates.map((session) => buildOpenCodePersistedAgentDescriptor(storageRoot, session)));
518
+ }
519
+ async function readOpenCodeStoredSessions(sessionRoot) {
520
+ const files = await findJsonFiles(sessionRoot);
521
+ const sessions = [];
522
+ for (const file of files) {
523
+ const parsed = await readJsonFile(file, OpenCodeStoredSessionSchema);
524
+ if (parsed) {
525
+ sessions.push(parsed);
526
+ }
527
+ }
528
+ return sessions;
529
+ }
530
+ async function buildOpenCodePersistedAgentDescriptor(storageRoot, session) {
531
+ const timeline = await readOpenCodeSessionTimeline(storageRoot, session.id);
532
+ return {
533
+ provider: "opencode",
534
+ sessionId: session.id,
535
+ cwd: session.directory,
536
+ title: normalizeOpenCodeSessionTitle(session.title),
537
+ lastActivityAt: new Date(getOpenCodeSessionTimestamp(session)),
538
+ persistence: {
539
+ provider: "opencode",
540
+ sessionId: session.id,
541
+ nativeHandle: session.id,
542
+ metadata: {
543
+ provider: "opencode",
544
+ cwd: session.directory,
545
+ title: normalizeOpenCodeSessionTitle(session.title),
546
+ },
547
+ },
548
+ timeline,
549
+ };
550
+ }
551
+ async function readOpenCodeSessionTimeline(storageRoot, sessionId) {
552
+ const messageRoot = path.join(storageRoot, "message", sessionId);
553
+ const messageFiles = await findJsonFiles(messageRoot);
554
+ const messages = [];
555
+ for (const file of messageFiles) {
556
+ const parsed = await readJsonFile(file, OpenCodeStoredMessageSchema);
557
+ if (parsed?.sessionID === sessionId) {
558
+ messages.push(parsed);
559
+ }
560
+ }
561
+ const timeline = [];
562
+ for (const message of messages.sort((left, right) => getOpenCodeMessageTimestamp(left) - getOpenCodeMessageTimestamp(right))) {
563
+ const text = await readOpenCodeMessageText(storageRoot, message.id);
564
+ if (!text) {
565
+ continue;
566
+ }
567
+ if (message.role === "user") {
568
+ timeline.push({ type: "user_message", text, messageId: message.id });
569
+ }
570
+ else if (message.role === "assistant") {
571
+ timeline.push({ type: "assistant_message", text });
572
+ }
573
+ }
574
+ return timeline;
575
+ }
576
+ async function readOpenCodeMessageText(storageRoot, messageId) {
577
+ const partRoot = path.join(storageRoot, "part", messageId);
578
+ const partFiles = await findJsonFiles(partRoot);
579
+ const parts = [];
580
+ for (const file of partFiles) {
581
+ const parsed = await readJsonFile(file, OpenCodeStoredPartSchema);
582
+ if (parsed?.type === "text" && typeof parsed.text === "string") {
583
+ parts.push(parsed);
584
+ }
585
+ }
586
+ return parts
587
+ .sort((left, right) => getOpenCodePartTimestamp(left) - getOpenCodePartTimestamp(right))
588
+ .map((part) => part.text?.trim() ?? "")
589
+ .filter(Boolean)
590
+ .join("\n\n");
591
+ }
592
+ async function findJsonFiles(root) {
593
+ let entries;
594
+ try {
595
+ entries = await readdir(root, { withFileTypes: true });
596
+ }
597
+ catch {
598
+ return [];
599
+ }
600
+ const files = await Promise.all(entries.map(async (entry) => {
601
+ const entryPath = path.join(root, entry.name);
602
+ if (entry.isDirectory()) {
603
+ return findJsonFiles(entryPath);
604
+ }
605
+ return entry.isFile() && entry.name.endsWith(".json") ? [entryPath] : [];
606
+ }));
607
+ return files.flat();
608
+ }
609
+ async function readJsonFile(file, schema) {
610
+ try {
611
+ return schema.parse(JSON.parse(await readFile(file, "utf8")));
612
+ }
613
+ catch {
614
+ return null;
615
+ }
616
+ }
617
+ function normalizeOpenCodeSessionTitle(title) {
618
+ const normalized = title?.trim();
619
+ return normalized ? normalized : null;
620
+ }
621
+ function getOpenCodeSessionTimestamp(session) {
622
+ return session.time?.updated ?? session.time?.created ?? 0;
623
+ }
624
+ function getOpenCodeMessageTimestamp(message) {
625
+ return message.time?.created ?? message.time?.completed ?? 0;
626
+ }
627
+ function getOpenCodePartTimestamp(part) {
628
+ return part.time?.start ?? part.time?.end ?? 0;
629
+ }
488
630
  export const __openCodeInternals = {
489
631
  buildOpenCodePromptParts,
490
632
  buildOpenCodeModelContextWindowLookup,
@@ -502,243 +644,40 @@ export const __openCodeInternals = {
502
644
  return OpenCodeAgentSession;
503
645
  },
504
646
  };
505
- export class OpenCodeServerManager {
506
- constructor(logger, runtimeSettings) {
507
- this.currentServer = null;
508
- this.retiredServers = new Set();
509
- this.startPromise = null;
510
- this.forcedRefreshPromise = null;
511
- this.logger = logger;
512
- this.runtimeSettings = runtimeSettings;
513
- this.runtimeSettingsKey = JSON.stringify(runtimeSettings ?? {});
647
+ class ProductionOpenCodeRuntime {
648
+ constructor(serverManager) {
649
+ this.serverManager = serverManager;
514
650
  }
515
- static getInstance(logger, runtimeSettings) {
516
- const nextSettingsKey = JSON.stringify(runtimeSettings ?? {});
517
- if (!OpenCodeServerManager.instance) {
518
- OpenCodeServerManager.instance = new OpenCodeServerManager(logger, runtimeSettings);
519
- OpenCodeServerManager.registerExitHandler();
520
- }
521
- else if (OpenCodeServerManager.instance.runtimeSettingsKey !== nextSettingsKey) {
522
- logger.warn({
523
- existingRuntimeSettings: OpenCodeServerManager.instance.runtimeSettingsKey,
524
- requestedRuntimeSettings: nextSettingsKey,
525
- }, "OpenCode server manager already initialized with different runtime settings");
526
- }
527
- return OpenCodeServerManager.instance;
651
+ async acquireServer(options) {
652
+ return this.serverManager.acquire(options);
528
653
  }
529
- static registerExitHandler() {
530
- if (OpenCodeServerManager.exitHandlerRegistered) {
531
- return;
532
- }
533
- OpenCodeServerManager.exitHandlerRegistered = true;
534
- const cleanup = () => {
535
- const instance = OpenCodeServerManager.instance;
536
- void instance?.shutdown();
537
- };
538
- process.on("exit", cleanup);
539
- process.on("SIGTERM", cleanup);
540
- process.on("SIGINT", cleanup);
541
- }
542
- async ensureRunning() {
543
- const acquisition = await this.acquire({ force: false });
544
- acquisition.release();
545
- return acquisition.server;
546
- }
547
- async acquire(options) {
548
- const server = options.force
549
- ? await this.getForcedRefreshServer()
550
- : await this.getCurrentServer();
551
- server.refCount += 1;
552
- let released = false;
553
- return {
554
- server: { port: server.port, url: server.url },
555
- release: () => {
556
- if (released) {
557
- return;
558
- }
559
- released = true;
560
- server.refCount -= 1;
561
- this.cleanupRetiredServers();
562
- },
563
- };
654
+ async ensureServerRunning() {
655
+ return this.serverManager.ensureRunning();
564
656
  }
565
- async getForcedRefreshServer() {
566
- if (this.forcedRefreshPromise) {
567
- return this.forcedRefreshPromise;
568
- }
569
- this.forcedRefreshPromise = Promise.resolve()
570
- .then(async () => {
571
- await this.rotateCurrentServer();
572
- return this.getCurrentServer();
573
- })
574
- .finally(() => {
575
- this.forcedRefreshPromise = null;
576
- });
577
- return this.forcedRefreshPromise;
578
- }
579
- async getCurrentServer() {
580
- if (this.startPromise) {
581
- return this.startPromise;
582
- }
583
- if (this.currentServer && !this.currentServer.process.killed) {
584
- return this.currentServer;
585
- }
586
- this.startPromise = this.startServer();
587
- try {
588
- const result = await this.startPromise;
589
- if (!result.retired) {
590
- this.currentServer = result;
591
- }
592
- return result;
593
- }
594
- finally {
595
- this.startPromise = null;
596
- }
597
- }
598
- async rotateCurrentServer() {
599
- const existing = this.currentServer;
600
- if (existing) {
601
- existing.retired = true;
602
- this.retiredServers.add(existing);
603
- this.currentServer = null;
604
- this.cleanupRetiredServers();
605
- }
606
- if (this.startPromise) {
607
- const pending = await this.startPromise;
608
- pending.retired = true;
609
- this.retiredServers.add(pending);
610
- this.currentServer = null;
611
- this.cleanupRetiredServers();
612
- }
613
- }
614
- async startServer() {
615
- const port = await findAvailablePort();
616
- const url = `http://127.0.0.1:${port}`;
617
- const launchPrefix = await resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveOpenCodeBinary);
618
- return new Promise((resolve, reject) => {
619
- const serverProcess = spawnProcess(launchPrefix.command, [...launchPrefix.args, "serve", "--port", String(port)], {
620
- detached: process.platform !== "win32",
621
- stdio: ["ignore", "pipe", "pipe"],
622
- ...createProviderEnvSpec({ runtimeSettings: this.runtimeSettings }),
623
- });
624
- let started = false;
625
- let stderrBuffer = "";
626
- let stdoutBuffer = "";
627
- const STARTUP_BUFFER_CAP = 8192;
628
- const appendCapped = (current, chunk) => {
629
- if (current.length >= STARTUP_BUFFER_CAP) {
630
- return current;
631
- }
632
- const remaining = STARTUP_BUFFER_CAP - current.length;
633
- return current + chunk.slice(0, remaining);
634
- };
635
- const buildStartupErrorMessage = (headline) => {
636
- const sections = [headline];
637
- const stderrTrimmed = stderrBuffer.trim();
638
- if (stderrTrimmed.length > 0) {
639
- sections.push(`stderr: ${stderrTrimmed}`);
640
- }
641
- const stdoutTrimmed = stdoutBuffer.trim();
642
- if (stdoutTrimmed.length > 0) {
643
- sections.push(`stdout: ${stdoutTrimmed}`);
644
- }
645
- return sections.join("\n");
646
- };
647
- const timeout = setTimeout(() => {
648
- if (!started) {
649
- reject(new Error(buildStartupErrorMessage("OpenCode server startup timeout")));
650
- }
651
- }, 30000);
652
- serverProcess.stdout?.on("data", (data) => {
653
- const output = data.toString();
654
- stdoutBuffer = appendCapped(stdoutBuffer, output);
655
- if (output.includes("listening on") && !started) {
656
- started = true;
657
- clearTimeout(timeout);
658
- resolve({
659
- process: serverProcess,
660
- port,
661
- url,
662
- refCount: 0,
663
- retired: false,
664
- });
665
- }
666
- });
667
- serverProcess.stderr?.on("data", (data) => {
668
- const output = data.toString();
669
- stderrBuffer = appendCapped(stderrBuffer, output);
670
- this.logger.error({ stderr: output.trim() }, "OpenCode server stderr");
671
- });
672
- serverProcess.on("error", (error) => {
673
- clearTimeout(timeout);
674
- const headline = error instanceof Error ? error.message : String(error);
675
- reject(new Error(buildStartupErrorMessage(headline)));
676
- });
677
- serverProcess.on("exit", (code) => {
678
- if (!started) {
679
- clearTimeout(timeout);
680
- reject(new Error(buildStartupErrorMessage(`OpenCode server exited with code ${code}`)));
681
- }
682
- if (this.currentServer?.process === serverProcess) {
683
- this.currentServer = null;
684
- }
685
- for (const retired of Array.from(this.retiredServers)) {
686
- if (retired.process === serverProcess) {
687
- this.retiredServers.delete(retired);
688
- }
689
- }
690
- });
691
- });
657
+ createClient(options) {
658
+ return createSdkOpenCodeClient(options);
692
659
  }
693
660
  async shutdown() {
694
- const servers = [
695
- ...(this.currentServer ? [this.currentServer] : []),
696
- ...Array.from(this.retiredServers),
697
- ];
698
- await Promise.all(servers.map((server) => this.killServer(server)));
699
- this.currentServer = null;
700
- this.retiredServers.clear();
701
- }
702
- cleanupRetiredServers() {
703
- for (const server of Array.from(this.retiredServers)) {
704
- if (server.refCount === 0) {
705
- this.retiredServers.delete(server);
706
- void this.killServer(server);
707
- }
708
- }
709
- }
710
- async killServer(server) {
711
- if (server.process.killed) {
712
- return;
713
- }
714
- const result = await terminateWithTreeKill(server.process, {
715
- gracefulTimeoutMs: OPENCODE_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS,
716
- forceTimeoutMs: OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS,
717
- onForceSignal: () => {
718
- this.logger.warn({ timeoutMs: OPENCODE_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS }, "OpenCode server did not exit after SIGTERM; sending SIGKILL");
719
- },
720
- });
721
- if (result === "kill-timeout") {
722
- this.logger.warn({ timeoutMs: OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS }, "OpenCode server did not report exit after SIGKILL");
723
- }
661
+ await this.serverManager.shutdown();
724
662
  }
725
663
  }
726
- OpenCodeServerManager.instance = null;
727
- OpenCodeServerManager.exitHandlerRegistered = false;
728
664
  export class OpenCodeAgentClient {
729
- constructor(logger, runtimeSettings) {
665
+ constructor(logger, runtimeSettings, storageRoot, deps = {}) {
730
666
  this.provider = "opencode";
731
667
  this.capabilities = OPENCODE_CAPABILITIES;
732
668
  this.modelContextWindows = new Map();
733
669
  this.logger = logger.child({ module: "agent", provider: "opencode" });
734
670
  this.runtimeSettings = runtimeSettings;
735
- this.serverManager = OpenCodeServerManager.getInstance(this.logger, runtimeSettings);
671
+ this.storageRoot = storageRoot ?? resolveOpenCodeStorageRoot();
672
+ this.runtime =
673
+ deps.runtime ??
674
+ new ProductionOpenCodeRuntime(OpenCodeServerManager.getInstance(this.logger, runtimeSettings));
736
675
  }
737
676
  async createSession(config, _launchContext, options) {
738
677
  const openCodeConfig = this.assertConfig(config);
739
- const acquisition = await this.serverManager.acquire({ force: false });
678
+ const acquisition = await this.runtime.acquireServer({ force: false });
740
679
  const { url } = acquisition.server;
741
- const client = createOpencodeClient({
680
+ const client = this.runtime.createClient({
742
681
  baseUrl: url,
743
682
  directory: openCodeConfig.cwd,
744
683
  });
@@ -770,9 +709,9 @@ export class OpenCodeAgentClient {
770
709
  ...overrides,
771
710
  };
772
711
  const openCodeConfig = this.assertConfig(config);
773
- const acquisition = await this.serverManager.acquire({ force: false });
712
+ const acquisition = await this.runtime.acquireServer({ force: false });
774
713
  const { url } = acquisition.server;
775
- const client = createOpencodeClient({
714
+ const client = this.runtime.createClient({
776
715
  baseUrl: url,
777
716
  directory: openCodeConfig.cwd,
778
717
  });
@@ -786,9 +725,9 @@ export class OpenCodeAgentClient {
786
725
  }
787
726
  }
788
727
  async listModels(options) {
789
- const acquisition = await this.serverManager.acquire({ force: options.force });
728
+ const acquisition = await this.runtime.acquireServer({ force: options.force });
790
729
  const { url } = acquisition.server;
791
- const client = createOpencodeClient({
730
+ const client = this.runtime.createClient({
792
731
  baseUrl: url,
793
732
  directory: options.cwd,
794
733
  });
@@ -832,10 +771,10 @@ export class OpenCodeAgentClient {
832
771
  }
833
772
  }
834
773
  async listModes(options) {
835
- const acquisition = await this.serverManager.acquire({ force: options.force });
774
+ const acquisition = await this.runtime.acquireServer({ force: options.force });
836
775
  const { url } = acquisition.server;
837
776
  const directory = options.cwd;
838
- const client = createOpencodeClient({ baseUrl: url, directory });
777
+ const client = this.runtime.createClient({ baseUrl: url, directory });
839
778
  try {
840
779
  const response = await withTimeout(client.app.agents({ directory }), 10000, "OpenCode app.agents timed out after 10s");
841
780
  if (response.error || !response.data) {
@@ -854,9 +793,8 @@ export class OpenCodeAgentClient {
854
793
  acquisition.release();
855
794
  }
856
795
  }
857
- async listPersistedAgents(_options) {
858
- // TODO: Implement by listing sessions from OpenCode
859
- return [];
796
+ async listPersistedAgents(options) {
797
+ return collectOpenCodePersistedAgentsFromStorage(this.storageRoot, options);
860
798
  }
861
799
  async isAvailable() {
862
800
  const command = this.runtimeSettings?.command;
@@ -873,7 +811,7 @@ export class OpenCodeAgentClient {
873
811
  let modelsValue = "Not checked";
874
812
  let status = formatDiagnosticStatus(available);
875
813
  try {
876
- const { url } = await this.serverManager.ensureRunning();
814
+ const { url } = await this.runtime.ensureServerRunning();
877
815
  serverStatus = `Running (${url})`;
878
816
  }
879
817
  catch (error) {