ds-harness-remote 0.5.7 → 0.5.10

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 (234) hide show
  1. package/dist/acp/adapter.d.ts +1 -1
  2. package/dist/acp/adapter.d.ts.map +1 -1
  3. package/dist/acp/adapters/antigravity/transcript-loader.d.ts.map +1 -1
  4. package/dist/acp/adapters/antigravity/transcript-loader.js +4 -1
  5. package/dist/acp/adapters/antigravity/transcript-loader.js.map +1 -1
  6. package/dist/acp/adapters/claude-history.d.ts +3 -0
  7. package/dist/acp/adapters/claude-history.d.ts.map +1 -0
  8. package/dist/acp/adapters/claude-history.js +118 -0
  9. package/dist/acp/adapters/claude-history.js.map +1 -0
  10. package/dist/acp/adapters/claude-process.d.ts +21 -0
  11. package/dist/acp/adapters/claude-process.d.ts.map +1 -0
  12. package/dist/acp/adapters/claude-process.js +147 -0
  13. package/dist/acp/adapters/claude-process.js.map +1 -0
  14. package/dist/acp/adapters/cursor-process.d.ts +9 -4
  15. package/dist/acp/adapters/cursor-process.d.ts.map +1 -1
  16. package/dist/acp/adapters/cursor-process.js +31 -3
  17. package/dist/acp/adapters/cursor-process.js.map +1 -1
  18. package/dist/acp/adapters/opencode-process.d.ts +67 -0
  19. package/dist/acp/adapters/opencode-process.d.ts.map +1 -0
  20. package/dist/acp/adapters/opencode-process.js +272 -0
  21. package/dist/acp/adapters/opencode-process.js.map +1 -0
  22. package/dist/acp/adapters/opencode.d.ts +8 -0
  23. package/dist/acp/adapters/opencode.d.ts.map +1 -0
  24. package/dist/acp/adapters/opencode.js +8 -0
  25. package/dist/acp/adapters/opencode.js.map +1 -0
  26. package/dist/acp/adapters/pi-process.d.ts +42 -0
  27. package/dist/acp/adapters/pi-process.d.ts.map +1 -0
  28. package/dist/acp/adapters/pi-process.js +410 -0
  29. package/dist/acp/adapters/pi-process.js.map +1 -0
  30. package/dist/acp/gateway.d.ts +10 -0
  31. package/dist/acp/gateway.d.ts.map +1 -1
  32. package/dist/acp/gateway.js +344 -103
  33. package/dist/acp/gateway.js.map +1 -1
  34. package/dist/acp/method-policy.d.ts +19 -19
  35. package/dist/acp/method-policy.js +5 -5
  36. package/dist/acp/method-policy.js.map +1 -1
  37. package/dist/acp/pi-tool-result.d.ts +9 -0
  38. package/dist/acp/pi-tool-result.d.ts.map +1 -0
  39. package/dist/acp/pi-tool-result.js +22 -0
  40. package/dist/acp/pi-tool-result.js.map +1 -0
  41. package/dist/acp/virtual-catalog.d.ts +50 -0
  42. package/dist/acp/virtual-catalog.d.ts.map +1 -0
  43. package/dist/acp/virtual-catalog.js +103 -0
  44. package/dist/acp/virtual-catalog.js.map +1 -0
  45. package/dist/acp/virtual-contract.d.ts +165 -0
  46. package/dist/acp/virtual-contract.d.ts.map +1 -0
  47. package/dist/acp/virtual-contract.js +216 -0
  48. package/dist/acp/virtual-contract.js.map +1 -0
  49. package/dist/acp/virtual-event-carrier.d.ts +50 -0
  50. package/dist/acp/virtual-event-carrier.d.ts.map +1 -0
  51. package/dist/acp/virtual-event-carrier.js +222 -0
  52. package/dist/acp/virtual-event-carrier.js.map +1 -0
  53. package/dist/acp/virtual-harness.d.ts +13 -112
  54. package/dist/acp/virtual-harness.d.ts.map +1 -1
  55. package/dist/acp/virtual-harness.js +129 -911
  56. package/dist/acp/virtual-harness.js.map +1 -1
  57. package/dist/acp/virtual-live.d.ts +78 -0
  58. package/dist/acp/virtual-live.d.ts.map +1 -0
  59. package/dist/acp/virtual-live.js +513 -0
  60. package/dist/acp/virtual-live.js.map +1 -0
  61. package/dist/acp/workspace-tools.js +2 -2
  62. package/dist/acp/workspace-tools.js.map +1 -1
  63. package/dist/acp.d.ts.map +1 -1
  64. package/dist/acp.js +1 -0
  65. package/dist/acp.js.map +1 -1
  66. package/dist/client/header.d.ts +10 -0
  67. package/dist/client/header.d.ts.map +1 -0
  68. package/dist/client/header.js +169 -0
  69. package/dist/client/header.js.map +1 -0
  70. package/dist/client/icons.d.ts +3 -0
  71. package/dist/client/icons.d.ts.map +1 -0
  72. package/dist/client/icons.js +3 -0
  73. package/dist/client/icons.js.map +1 -0
  74. package/dist/client/navigation.d.ts +5 -0
  75. package/dist/client/navigation.d.ts.map +1 -0
  76. package/dist/client/navigation.js +30 -0
  77. package/dist/client/navigation.js.map +1 -0
  78. package/dist/client/presentation.d.ts +17 -0
  79. package/dist/client/presentation.d.ts.map +1 -0
  80. package/dist/client/presentation.js +208 -0
  81. package/dist/client/presentation.js.map +1 -0
  82. package/dist/client/progress.d.ts +10 -0
  83. package/dist/client/progress.d.ts.map +1 -0
  84. package/dist/client/progress.js +82 -0
  85. package/dist/client/progress.js.map +1 -0
  86. package/dist/client/settings.d.ts +10 -0
  87. package/dist/client/settings.d.ts.map +1 -0
  88. package/dist/client/settings.js +335 -0
  89. package/dist/client/settings.js.map +1 -0
  90. package/dist/client/shell.d.ts +6 -0
  91. package/dist/client/shell.d.ts.map +1 -0
  92. package/dist/client/shell.js +128 -0
  93. package/dist/client/shell.js.map +1 -0
  94. package/dist/client/types.d.ts +264 -0
  95. package/dist/client/types.d.ts.map +1 -0
  96. package/dist/client/types.js +2 -0
  97. package/dist/client/types.js.map +1 -0
  98. package/dist/client/workspaces.d.ts +11 -0
  99. package/dist/client/workspaces.d.ts.map +1 -0
  100. package/dist/client/workspaces.js +1179 -0
  101. package/dist/client/workspaces.js.map +1 -0
  102. package/dist/client-capabilities.d.ts +6 -0
  103. package/dist/client-capabilities.d.ts.map +1 -0
  104. package/dist/client-capabilities.js +88 -0
  105. package/dist/client-capabilities.js.map +1 -0
  106. package/dist/client-codex-streams.d.ts +23 -0
  107. package/dist/client-codex-streams.d.ts.map +1 -0
  108. package/dist/client-codex-streams.js +156 -0
  109. package/dist/client-codex-streams.js.map +1 -0
  110. package/dist/client-connection.d.ts +38 -0
  111. package/dist/client-connection.d.ts.map +1 -0
  112. package/dist/client-connection.js +210 -0
  113. package/dist/client-connection.js.map +1 -0
  114. package/dist/client-control.d.ts +48 -0
  115. package/dist/client-control.d.ts.map +1 -0
  116. package/dist/client-control.js +273 -0
  117. package/dist/client-control.js.map +1 -0
  118. package/dist/client-locale.d.ts +8 -0
  119. package/dist/client-locale.d.ts.map +1 -1
  120. package/dist/client-locale.js +16 -0
  121. package/dist/client-locale.js.map +1 -1
  122. package/dist/client-runtime-contract.d.ts +161 -0
  123. package/dist/client-runtime-contract.d.ts.map +1 -0
  124. package/dist/client-runtime-contract.js +127 -0
  125. package/dist/client-runtime-contract.js.map +1 -0
  126. package/dist/client-runtime.d.ts +24 -126
  127. package/dist/client-runtime.d.ts.map +1 -1
  128. package/dist/client-runtime.js +171 -867
  129. package/dist/client-runtime.js.map +1 -1
  130. package/dist/client-secure-transport.d.ts +1 -0
  131. package/dist/client-secure-transport.d.ts.map +1 -1
  132. package/dist/client-secure-transport.js +35 -16
  133. package/dist/client-secure-transport.js.map +1 -1
  134. package/dist/client-workspace-queries.d.ts +12 -0
  135. package/dist/client-workspace-queries.d.ts.map +1 -0
  136. package/dist/client-workspace-queries.js +66 -0
  137. package/dist/client-workspace-queries.js.map +1 -0
  138. package/dist/client.d.ts.map +1 -1
  139. package/dist/client.github.js +2091 -1896
  140. package/dist/client.js +2091 -1896
  141. package/dist/client.js.map +1 -1
  142. package/dist/codex/domain.d.ts.map +1 -1
  143. package/dist/codex/domain.js +67 -46
  144. package/dist/codex/domain.js.map +1 -1
  145. package/dist/codex/method-policy.d.ts +9 -9
  146. package/dist/codex/virtual-catalog.d.ts +90 -0
  147. package/dist/codex/virtual-catalog.d.ts.map +1 -0
  148. package/dist/codex/virtual-catalog.js +317 -0
  149. package/dist/codex/virtual-catalog.js.map +1 -0
  150. package/dist/codex/virtual-contract.d.ts +96 -0
  151. package/dist/codex/virtual-contract.d.ts.map +1 -0
  152. package/dist/codex/virtual-contract.js +192 -0
  153. package/dist/codex/virtual-contract.js.map +1 -0
  154. package/dist/codex/virtual-event-carrier.d.ts +52 -0
  155. package/dist/codex/virtual-event-carrier.d.ts.map +1 -0
  156. package/dist/codex/virtual-event-carrier.js +212 -0
  157. package/dist/codex/virtual-event-carrier.js.map +1 -0
  158. package/dist/codex/virtual-harness.d.ts +16 -117
  159. package/dist/codex/virtual-harness.d.ts.map +1 -1
  160. package/dist/codex/virtual-harness.js +123 -1909
  161. package/dist/codex/virtual-harness.js.map +1 -1
  162. package/dist/codex/virtual-history.d.ts +74 -0
  163. package/dist/codex/virtual-history.d.ts.map +1 -0
  164. package/dist/codex/virtual-history.js +546 -0
  165. package/dist/codex/virtual-history.js.map +1 -0
  166. package/dist/codex/virtual-live.d.ts +75 -0
  167. package/dist/codex/virtual-live.d.ts.map +1 -0
  168. package/dist/codex/virtual-live.js +563 -0
  169. package/dist/codex/virtual-live.js.map +1 -0
  170. package/dist/codex/virtual-values.d.ts +29 -0
  171. package/dist/codex/virtual-values.d.ts.map +1 -0
  172. package/dist/codex/virtual-values.js +75 -0
  173. package/dist/codex/virtual-values.js.map +1 -0
  174. package/dist/config.js +4 -4
  175. package/dist/config.js.map +1 -1
  176. package/dist/control-runtime.js +2 -2
  177. package/dist/control-runtime.js.map +1 -1
  178. package/dist/harness-remote-bridge.d.ts +2 -1
  179. package/dist/harness-remote-bridge.d.ts.map +1 -1
  180. package/dist/harness-remote-bridge.js +13 -5
  181. package/dist/harness-remote-bridge.js.map +1 -1
  182. package/dist/index.d.ts.map +1 -1
  183. package/dist/index.js +23304 -15332
  184. package/dist/index.js.map +4 -4
  185. package/dist/plugin-route-dispatcher.d.ts +2 -2
  186. package/dist/plugin-route-dispatcher.d.ts.map +1 -1
  187. package/dist/plugin-route-dispatcher.js +2 -2
  188. package/dist/plugin-route-dispatcher.js.map +1 -1
  189. package/dist/remote-terminal-controller.d.ts +90 -0
  190. package/dist/remote-terminal-controller.d.ts.map +1 -0
  191. package/dist/remote-terminal-controller.js +494 -0
  192. package/dist/remote-terminal-controller.js.map +1 -0
  193. package/dist/remote-terminal-provider.d.ts +46 -0
  194. package/dist/remote-terminal-provider.d.ts.map +1 -0
  195. package/dist/remote-terminal-provider.js +147 -0
  196. package/dist/remote-terminal-provider.js.map +1 -0
  197. package/dist/remote-typert-gateway.js +7 -1
  198. package/dist/remote-typert-gateway.js.map +1 -1
  199. package/dist/server-api.d.ts +3 -0
  200. package/dist/server-api.d.ts.map +1 -1
  201. package/dist/server-api.js +24 -9
  202. package/dist/server-api.js.map +1 -1
  203. package/dist/server-connection.d.ts +1 -0
  204. package/dist/server-connection.d.ts.map +1 -1
  205. package/dist/server-connection.js +8 -1
  206. package/dist/server-connection.js.map +1 -1
  207. package/dist/service.d.ts +1 -1
  208. package/dist/service.d.ts.map +1 -1
  209. package/dist/service.js +15 -8
  210. package/dist/service.js.map +1 -1
  211. package/dist/terminal-carrier-compat.d.ts +14 -0
  212. package/dist/terminal-carrier-compat.d.ts.map +1 -0
  213. package/dist/terminal-carrier-compat.js +259 -0
  214. package/dist/terminal-carrier-compat.js.map +1 -0
  215. package/dist/terminal-policy.d.ts +6 -0
  216. package/dist/terminal-policy.d.ts.map +1 -1
  217. package/dist/terminal-policy.js +24 -6
  218. package/dist/terminal-policy.js.map +1 -1
  219. package/dist/typert-gateway-contract.d.ts +6 -1
  220. package/dist/typert-gateway-contract.d.ts.map +1 -1
  221. package/dist/typert-gateway-switch.d.ts +2 -1
  222. package/dist/typert-gateway-switch.d.ts.map +1 -1
  223. package/dist/typert-gateway-switch.js +39 -18
  224. package/dist/typert-gateway-switch.js.map +1 -1
  225. package/dist/version.d.ts +1 -1
  226. package/dist/version.d.ts.map +1 -1
  227. package/dist/version.js +1 -1
  228. package/dist/version.js.map +1 -1
  229. package/dist/workspace-bridge.d.ts +6 -89
  230. package/dist/workspace-bridge.d.ts.map +1 -1
  231. package/dist/workspace-bridge.js +37 -294
  232. package/dist/workspace-bridge.js.map +1 -1
  233. package/dsh-plugin.json +1 -1
  234. package/package.json +24 -20
@@ -1,53 +1,10 @@
1
- import { ACP_TOOL_CALL_ENDPOINTS, ACP_TOOL_STREAM_ENDPOINTS } from '@dsh-remote/protocol';
2
- import { parseAcpImage, acpImageContent, acpImageLimits } from './image-content.js';
3
1
  import { AgentAcpClient } from '@dsh-remote/client-core';
4
- const CURSOR_SESSION_PREFIX = 'cursor:';
5
- const ACP_SESSION_PREFIX = 'acp:';
6
- const CURSOR_WORKSPACE_PREFIX = 'cursor:cwd:';
7
- const AGY_WORKSPACE_PREFIX = 'antigravity:cwd:';
8
- const CURSOR_PROVIDER = 'cursor';
9
- const CURSOR_MODEL = 'cursor';
10
- /** Build a backend-scoped virtual Workspace id for an absolute Host cwd. */
11
- export function acpCwdWorkspaceId(path, backend) {
12
- if (backend !== 'cursor' && backend !== 'antigravity')
13
- throw new Error('An explicit ACP workspace backend is required.');
14
- return `${backend === 'antigravity' ? AGY_WORKSPACE_PREFIX : CURSOR_WORKSPACE_PREFIX}${encodeURIComponent(path)}`;
15
- }
16
- export function createAcpWorkspaceView(path, backend, title) {
17
- const now = new Date().toISOString();
18
- const label = title?.trim() || path.split(/[\\/]/).filter(Boolean).at(-1) || path;
19
- return {
20
- workspaceId: acpCwdWorkspaceId(path, backend),
21
- path,
22
- title: label,
23
- sessionIds: [],
24
- sessionCount: 0,
25
- createdAt: now,
26
- updatedAt: now,
27
- };
28
- }
29
- /** Discover ACP virtual workspaces visible on the Host. */
30
- export async function discoverAcpVirtualWorkspaces(client, backend, signal) {
31
- if (client?.listWorkspaces !== undefined) {
32
- try {
33
- const items = await client.listWorkspaces(backend, signal);
34
- if (Array.isArray(items) && items.length > 0) {
35
- return items
36
- .filter(item => typeof item?.path === 'string' && item.path.trim() !== '')
37
- .map(item => {
38
- const view = createAcpWorkspaceView(item.path, backend, item.title);
39
- if (typeof item.sessionCount === 'number')
40
- view.sessionCount = item.sessionCount;
41
- return view;
42
- });
43
- }
44
- }
45
- catch {
46
- // Discovery is optional; an unavailable Host returns no workspaces.
47
- }
48
- }
49
- return [];
50
- }
2
+ import { ACP_TOOL_CALL_ENDPOINTS, ACP_TOOL_STREAM_ENDPOINTS } from '@dsh-remote/protocol';
3
+ import { acpImageContent, acpImageLimits, parseAcpImage } from './image-content.js';
4
+ import { createAcpWorkspaceView, modelCatalog, nativeWorkspace, recreateWorkspaceFromId } from './virtual-catalog.js';
5
+ import { array, AsyncValueQueue, business, carrierArgs, extractPromptText, extractSessionId, fail, failFrom, failure, isRecord, nativeAcpId, optionalInteger, optionalPositiveInteger, record, requestArg, requiredString, sessionPrefix, string, success, } from './virtual-contract.js';
6
+ import { AcpEventCarrier } from './virtual-event-carrier.js';
7
+ import { AcpLiveProjection } from './virtual-live.js';
51
8
  /**
52
9
  * Browser-safe, in-memory Harness projection for the independent Agent ACP domain. Projects
53
10
  * cwd workspaces and ACP sessions onto the native DSH Workspace / Session /
@@ -58,6 +15,8 @@ export class AcpVirtualHarness {
58
15
  host;
59
16
  backend;
60
17
  hostCarrier;
18
+ live;
19
+ events;
61
20
  api;
62
21
  workspaceById = new Map();
63
22
  sessions = new Map();
@@ -68,12 +27,13 @@ export class AcpVirtualHarness {
68
27
  rcHostStreams = new Set();
69
28
  follows = new Set();
70
29
  pendingApprovals = new Map();
30
+ pendingHistory = new Map();
71
31
  selectedWorkspaceId;
72
32
  selectedModels = new Map();
73
33
  lastProjectionSeq = 0;
74
34
  imageAttachments = new Map();
75
35
  closed = false;
76
- constructor(client, host, backend = 'cursor',
36
+ constructor(client, host, backend = "cursor",
77
37
  /**
78
38
  * Host gateway used for endpoints this virtual Harness does not project
79
39
  * (settings, plugins, models, credentials, …). A real Harness workspace
@@ -85,16 +45,52 @@ export class AcpVirtualHarness {
85
45
  this.host = host;
86
46
  this.backend = backend;
87
47
  this.hostCarrier = hostCarrier;
88
- this.api = this.createApiProxy();
48
+ const owner = this;
49
+ this.live = new AcpLiveProjection({
50
+ ensureSessionRegistered: (...args) => owner.ensureSessionRegistered(...args),
51
+ get sessions() { return owner.sessions; },
52
+ hydrateSession: (...args) => owner.hydrateSession(...args),
53
+ historyPage: (...args) => owner.historyPage(...args),
54
+ get follows() { return owner.follows; },
55
+ get backend() { return owner.backend; },
56
+ modelSelectionProjection: (...args) => owner.modelSelectionProjection(...args),
57
+ get client() { return owner.client; },
58
+ emitRemoteEvent: (...args) => owner.events.emitRemoteEvent(...args),
59
+ get selectedModels() { return owner.selectedModels; },
60
+ publishProjection: (...args) => owner.events.publishProjection(...args),
61
+ nextProjectionSeq: (...args) => owner.nextProjectionSeq(...args),
62
+ refreshSessionTitle: (...args) => owner.refreshSessionTitle(...args),
63
+ get pendingApprovals() { return owner.pendingApprovals; },
64
+ emitApproval: (...args) => owner.events.emitApproval(...args),
65
+ modelSelection: (...args) => owner.modelSelection(...args),
66
+ broadcastRcMux: (...args) => owner.events.broadcastRcMux(...args),
67
+ });
68
+ this.events = new AcpEventCarrier({
69
+ get workspaceStreams() { return owner.workspaceStreams; },
70
+ visibleWorkspaces: (...args) => owner.visibleWorkspaces(...args),
71
+ get controlStreams() { return owner.controlStreams; },
72
+ get eventStreams() { return owner.eventStreams; },
73
+ get selectedWorkspaceId() { return owner.selectedWorkspaceId; },
74
+ get workspaceById() { return owner.workspaceById; },
75
+ backendLabel: (...args) => owner.backendLabel(...args),
76
+ get pendingApprovals() { return owner.pendingApprovals; },
77
+ get client() { return owner.client; },
78
+ dispatch: (...args) => owner.dispatch(...args),
79
+ get rcMuxStreams() { return owner.rcMuxStreams; },
80
+ get sessions() { return owner.sessions; },
81
+ get rcHostStreams() { return owner.rcHostStreams; },
82
+ nextProjectionSeq: (...args) => owner.nextProjectionSeq(...args),
83
+ });
84
+ this.api = this.events.createApiProxy();
89
85
  }
90
- static remote(core, host, backend = 'cursor', capabilities = [], hostCarrier) {
86
+ static remote(core, host, backend = "cursor", capabilities = [], hostCarrier) {
91
87
  return new AcpVirtualHarness(new AgentAcpClient(core, capabilities), host, backend, hostCarrier);
92
88
  }
93
89
  async workspaces() {
94
90
  return [...this.workspaceById.values()];
95
91
  }
96
92
  backendLabel() {
97
- return this.backend === 'antigravity' ? 'Antigravity' : 'Cursor';
93
+ return this.backend === "pi" ? "PI" : this.backend === "opencode" ? "OpenCode" : this.backend === "antigravity" ? "Antigravity" : "Cursor";
98
94
  }
99
95
  async selectWorkspace(workspaceId) {
100
96
  const workspace = this.workspaceById.get(workspaceId) ?? recreateWorkspaceFromId(workspaceId, this.backend);
@@ -167,7 +163,7 @@ export class AcpVirtualHarness {
167
163
  return business(await this.client.workspaceToolCall(nativeAcpId(sessionId, this.backend), this.backend, endpoint, args, signal));
168
164
  }
169
165
  switch (endpoint) {
170
- case '$events/result': return business(await this.answerRemoteEvent(args, signal));
166
+ case '$events/result': return business(await this.events.answerRemoteEvent(args, signal));
171
167
  case 'workspace/list': return business(success({
172
168
  items: this.visibleWorkspaces().map(nativeWorkspace),
173
169
  archivedSessionIds: [],
@@ -182,6 +178,7 @@ export class AcpVirtualHarness {
182
178
  case 'workspace/insertSessionBefore': return business(await this.workspaceForSession(requestArg(args)));
183
179
  case 'workspace/archiveSession': return business(await this.archiveSession(requestArg(args)));
184
180
  case 'session/list': return business(success({ items: await this.handleSessionList() }));
181
+ case 'session/projections': return business(await this.sessionProjections(requestArg(args)));
185
182
  case 'session/search': return business(success({ items: [], hasMore: false }));
186
183
  case 'session/create': return business(await this.createSession(requestArg(args), signal));
187
184
  case 'session/fork': return business(failure('bad-request', `${this.backendLabel()} Remote does not support session fork yet.`));
@@ -194,7 +191,7 @@ export class AcpVirtualHarness {
194
191
  case 'session/attachment': return business(await this.attachment(requestArg(args)));
195
192
  case 'session/modelCatalog': {
196
193
  const sessionId = await this.preferredSessionId();
197
- if (this.backend !== 'antigravity' || !sessionId)
194
+ if (this.backend === 'cursor' || !sessionId)
198
195
  return business(success(modelCatalog(this.backend)));
199
196
  const models = await this.hostSessionModels(sessionId);
200
197
  return business(success({ default: models.current, routableProviders: models.routable ? [models.current.provider] : [], groups: models.groups, failures: models.failures }));
@@ -202,7 +199,7 @@ export class AcpVirtualHarness {
202
199
  case 'session/models': {
203
200
  const rawId = extractSessionId(requestArg(args));
204
201
  nativeAcpId(rawId, this.backend);
205
- if (this.backend === 'antigravity')
202
+ if (this.backend !== 'cursor')
206
203
  return business(success(await this.hostSessionModels(rawId)));
207
204
  const catalog = modelCatalog(this.backend);
208
205
  return business(success({
@@ -243,15 +240,15 @@ export class AcpVirtualHarness {
243
240
  return queue.iterate(() => { void stream.close().catch(() => undefined); });
244
241
  }
245
242
  if (endpoint === 'workspace/follow')
246
- return this.workspaceFollow(signal);
243
+ return this.events.workspaceFollow(signal);
247
244
  if (endpoint === 'session/control')
248
- return this.sessionControl(signal);
245
+ return this.events.sessionControl(signal);
249
246
  if (endpoint === 'session/follow')
250
- return this.sessionFollow(requestArg(args), signal);
247
+ return this.live.sessionFollow(requestArg(args), signal);
251
248
  if (endpoint === '$events')
252
- return this.remoteEvents(signal);
249
+ return this.events.remoteEvents(signal);
253
250
  if (endpoint === 'job/list' || endpoint === 'job/follow')
254
- return this.emptyJobStream(endpoint, signal);
251
+ return this.events.emptyJobStream(endpoint, signal);
255
252
  if (this.hostCarrier !== undefined)
256
253
  return this.hostCarrier.open(endpoint, payload, signal);
257
254
  throw Object.assign(new Error(`${this.backendLabel()} virtual Harness does not implement stream ${endpoint}.`), {
@@ -341,7 +338,7 @@ export class AcpVirtualHarness {
341
338
  updatedAt: new Date().toISOString(),
342
339
  });
343
340
  }
344
- this.emitRemoteEvent('api-session/removed', [sessionId]);
341
+ this.events.emitRemoteEvent('api-session/removed', [sessionId]);
345
342
  this.publishWorkspaceBaseline();
346
343
  return success({ archivedSessionIds: [sessionId] });
347
344
  }
@@ -357,26 +354,26 @@ export class AcpVirtualHarness {
357
354
  return success({ sessionId: reusable.sessionId });
358
355
  }
359
356
  const created = await this.client.createSession(cwd, 'agent', this.backend, signal);
360
- if (this.backend === 'antigravity' && this.sessions.has(`${ACP_SESSION_PREFIX}${created.sessionId}`)) {
357
+ if (this.backend !== 'cursor' && this.sessions.has(`${sessionPrefix(this.backend)}${created.sessionId}`)) {
361
358
  return failure('session-already-exists', 'The backend returned an existing conversation for a new Session.');
362
359
  }
363
360
  const session = this.registerSession(created.sessionId, cwd, workspace?.title);
364
361
  this.attachSessionToWorkspace(cwd, session.sessionId);
365
362
  this.publishWorkspaceBaseline();
366
363
  const seq = this.nextProjectionSeq();
367
- this.emitRemoteEvent('api-session/added', [this.sessionSummary(session, seq)]);
368
- this.publishProjection(session.sessionId, 'title', session.title ?? this.backendLabel(), seq);
364
+ this.events.emitRemoteEvent('api-session/added', [this.sessionSummary(session, seq)]);
365
+ this.events.publishProjection(session.sessionId, 'title', session.title ?? this.backendLabel(), seq);
369
366
  return success({ sessionId: session.sessionId });
370
367
  }
371
368
  ensureSessionRegistered(sessionId) {
372
369
  let session = this.sessions.get(sessionId);
373
370
  if (session !== undefined)
374
371
  return session;
375
- if (this.backend === 'antigravity' && sessionId.startsWith(ACP_SESSION_PREFIX)) {
376
- const acpSessionId = sessionId.slice(ACP_SESSION_PREFIX.length);
372
+ if (this.backend !== 'cursor' && sessionId.startsWith(sessionPrefix(this.backend))) {
373
+ const acpSessionId = sessionId.slice(sessionPrefix(this.backend).length);
377
374
  const workspace = this.selectedWorkspaceId !== undefined ? this.workspaceById.get(this.selectedWorkspaceId) : undefined;
378
375
  const cwd = workspace?.path ?? '/';
379
- session = this.registerSession(acpSessionId, cwd, 'Antigravity', Date.now(), Date.now(), false);
376
+ session = this.registerSession(acpSessionId, cwd, this.backendLabel(), Date.now(), Date.now(), false);
380
377
  if (workspace && !workspace.sessionIds.includes(sessionId)) {
381
378
  workspace.sessionIds.unshift(sessionId);
382
379
  workspace.sessionCount = workspace.sessionIds.length;
@@ -409,14 +406,14 @@ export class AcpVirtualHarness {
409
406
  const requestId = typeof request.requestId === 'string' && request.requestId.length > 0
410
407
  ? request.requestId
411
408
  : undefined;
412
- await this.ensureFollow(session);
409
+ await this.live.ensureFollow(session);
413
410
  const follow = [...this.follows].find(f => f.sessionId === sessionId);
414
411
  if (follow !== undefined) {
415
412
  follow.turn += 1;
416
413
  follow.stepOpen = true;
417
- this.pushEvent(follow, 'turn/start', { turn: follow.turn });
418
- this.pushEvent(follow, 'step/start', { turn: follow.turn, step: 1 });
419
- this.pushEvent(follow, 'user/message', {
414
+ this.live.pushEvent(follow, 'turn/start', { turn: follow.turn });
415
+ this.live.pushEvent(follow, 'step/start', { turn: follow.turn, step: 1 });
416
+ this.live.pushEvent(follow, 'user/message', {
420
417
  id: `user:${Date.now()}`,
421
418
  role: 'user',
422
419
  content: [...(text ? [{ type: 'text', text }] : []), ...imageBlocks],
@@ -426,7 +423,7 @@ export class AcpVirtualHarness {
426
423
  session.blank = false;
427
424
  session.running = true;
428
425
  session.updatedAt = Date.now();
429
- this.emitRemoteEvent('api-session/status', [sessionId, true]);
426
+ this.events.emitRemoteEvent('api-session/status', [sessionId, true]);
430
427
  await this.client.prompt(session.acpSessionId, text ?? '', signal, images, this.backend);
431
428
  return success({ accepted: true });
432
429
  }
@@ -439,10 +436,10 @@ export class AcpVirtualHarness {
439
436
  session.running = false;
440
437
  for (const follow of this.follows) {
441
438
  if (follow.sessionId === sessionId) {
442
- this.closeFollowAfterRemoteStreamClosed(follow);
439
+ this.live.closeFollowAfterRemoteStreamClosed(follow, { kind: 'aborted', reason: { kind: 'user' } });
443
440
  }
444
441
  }
445
- this.emitRemoteEvent('api-session/status', [sessionId, false]);
442
+ this.events.emitRemoteEvent('api-session/status', [sessionId, false]);
446
443
  return success({ accepted: true });
447
444
  }
448
445
  async renameSession(request) {
@@ -456,7 +453,7 @@ export class AcpVirtualHarness {
456
453
  session.title = title;
457
454
  session.titleOverridden = true;
458
455
  session.updatedAt = Date.now();
459
- this.publishProjection(sessionId, 'title', title);
456
+ this.events.publishProjection(sessionId, 'title', title);
460
457
  return success({ sessionId });
461
458
  }
462
459
  cacheImageAttachments(sessionId, content) {
@@ -479,15 +476,35 @@ export class AcpVirtualHarness {
479
476
  return cached?.sessionId === sessionId ? success({ attachment: cached.attachment, data: cached.data }) : failure('attachment-error', 'The AGY image is unavailable or expired.');
480
477
  }
481
478
  async hydrateSession(session) {
482
- if (this.backend !== 'antigravity' || session.blank || session.events.length > 0)
479
+ if (this.backend === 'cursor' || session.blank || session.events.length > 0)
483
480
  return;
481
+ if (this.backend !== 'pi')
482
+ return this.hydrateSessionOnce(session);
483
+ const pending = this.pendingHistory.get(session.sessionId);
484
+ if (pending)
485
+ return pending;
486
+ const read = this.hydrateSessionOnce(session);
487
+ this.pendingHistory.set(session.sessionId, read);
488
+ try {
489
+ await read;
490
+ }
491
+ finally {
492
+ if (this.pendingHistory.get(session.sessionId) === read)
493
+ this.pendingHistory.delete(session.sessionId);
494
+ }
495
+ }
496
+ async hydrateSessionOnce(session) {
484
497
  let events = [];
485
498
  if (this.client.loadSessionHistory !== undefined) {
486
499
  try {
487
500
  events = await this.client.loadSessionHistory(session.sessionId, this.backend);
488
501
  }
489
- catch {
490
- // Keep the projection empty until remote history is available.
502
+ catch (error) {
503
+ // A failed PI read is not a successful empty baseline; do not continue
504
+ // a restored conversation until its authoritative history is available.
505
+ if (this.backend === 'pi')
506
+ throw error;
507
+ // Preserve legacy AGY history availability behavior.
491
508
  }
492
509
  }
493
510
  if (events.length > 0) {
@@ -514,7 +531,7 @@ export class AcpVirtualHarness {
514
531
  projections: {
515
532
  asOfSeq: page.cursor,
516
533
  values: {
517
- title: session.title ?? (this.backend === 'antigravity' ? 'Antigravity' : 'Cursor'),
534
+ title: session.title ?? (this.backend === "pi" ? "PI" : this.backend === "opencode" ? "OpenCode" : this.backend === "antigravity" ? "Antigravity" : "Cursor"),
518
535
  sessionListMetadata: { blank: session.blank && session.events.length === 0, lastPromptAt: null },
519
536
  modelSelection: this.modelSelectionProjection(sessionId),
520
537
  imageLimits: acpImageLimits(this.backend),
@@ -522,6 +539,14 @@ export class AcpVirtualHarness {
522
539
  },
523
540
  });
524
541
  }
542
+ async sessionProjections(request) {
543
+ const sessionId = extractSessionId(request);
544
+ const session = this.ensureSessionRegistered(sessionId) ?? this.sessions.get(sessionId);
545
+ if (session === undefined)
546
+ return success(null);
547
+ await this.hydrateSession(session);
548
+ return success(this.sessionSummary(session, session.events.at(-1)?.event.seq ?? -1).projections);
549
+ }
525
550
  async sessionPage(request) {
526
551
  const sessionId = extractSessionId(request);
527
552
  const session = this.ensureSessionRegistered(sessionId) ?? this.sessions.get(sessionId);
@@ -583,437 +608,9 @@ export class AcpVirtualHarness {
583
608
  const path = string(request.path) ?? workspace.path;
584
609
  return success(await this.client.listDirectory(path, signal));
585
610
  }
586
- async answerRemoteEvent(args, signal) {
587
- const eventId = string(args.eventId);
588
- const outcome = record(args.outcome);
589
- if (eventId === undefined)
590
- throw new Error(`The ${this.backendLabel()} approval result is missing its event id.`);
591
- const pending = this.pendingApprovals.get(eventId);
592
- if (pending === undefined)
593
- return undefined;
594
- this.pendingApprovals.delete(eventId);
595
- const decision = outcome.kind === 'result' && outcome.value === 'allowed-once'
596
- ? 'allow-once'
597
- : outcome.kind === 'result' && outcome.value === 'cancelled'
598
- ? 'cancel'
599
- : 'reject-once';
600
- await this.client.respond(pending.requestHandle, decision, undefined, signal);
601
- return undefined;
602
- }
603
- async workspaceFollow(signal) {
604
- const queue = new AsyncValueQueue(signal);
605
- this.workspaceStreams.add(queue);
606
- queue.push({
607
- type: 'baseline',
608
- value: {
609
- items: this.visibleWorkspaces().map(nativeWorkspace),
610
- archivedSessionIds: [],
611
- pinnedSessionIds: [],
612
- },
613
- });
614
- return queue.iterate(() => this.workspaceStreams.delete(queue));
615
- }
616
- async sessionControl(signal) {
617
- const queue = new AsyncValueQueue(signal);
618
- this.controlStreams.add(queue);
619
- // Session Controller treats the first control frame as the opening
620
- // snapshot. Without it the Client-side RemoteSnapshotStream never moves
621
- // to `ready`, so a workspace selected through the Remote modal remains
622
- // stuck on the empty shell after the page reload.
623
- queue.push({ type: 'baseline', value: { projections: {} } });
624
- return queue.iterate(() => this.controlStreams.delete(queue));
625
- }
626
- /**
627
- * Background jobs belong to the Host Harness session, not to a backend-scoped
628
- * virtual Workspace. The native session header mounts a job roster stream as
629
- * soon as a Session opens, so answer it with an authoritative empty roster
630
- * instead of a terminal `method-not-found` failure that tears the page down.
631
- */
632
- emptyJobStream(endpoint, signal) {
633
- const queue = new AsyncValueQueue(signal);
634
- if (endpoint === 'job/list')
635
- queue.push({ type: 'rows', jobs: [] });
636
- return queue.iterate(() => queue.close());
637
- }
638
- async remoteEvents(signal) {
639
- const id = `cursor-events:${Date.now()}:${Math.random()}`;
640
- const queue = new AsyncValueQueue(signal);
641
- this.eventStreams.set(id, queue);
642
- const workspace = this.selectedWorkspaceId === undefined
643
- ? undefined
644
- : this.workspaceById.get(this.selectedWorkspaceId);
645
- queue.push({
646
- type: 'ready',
647
- clientId: id,
648
- host: { home: workspace?.path ?? '/' },
649
- });
650
- return queue.iterate(() => this.eventStreams.delete(id));
651
- }
652
- async sessionFollow(request, signal) {
653
- const sessionId = extractSessionId(request);
654
- const session = this.ensureSessionRegistered(sessionId) ?? this.sessions.get(sessionId);
655
- if (session === undefined)
656
- throw new Error('The Session was not found.');
657
- await this.hydrateSession(session);
658
- const history = this.historyPage(session, {
659
- limit: optionalPositiveInteger(request.maxMessages) ?? 50,
660
- });
661
- const queue = new AsyncValueQueue(signal);
662
- const follow = {
663
- sessionId,
664
- acpSessionId: session.acpSessionId,
665
- queue,
666
- nextSeq: session.events.length,
667
- turn: Math.max(0, ...session.events.map(entry => (isRecord(entry.event.data) && typeof entry.event.data.turn === 'number' ? entry.event.data.turn : 0))),
668
- stepOpen: session.running,
669
- streamActive: false,
670
- assistantStreamRevision: 0,
671
- accumulatedText: '',
672
- activeToolCalls: new Set(),
673
- };
674
- this.follows.add(follow);
675
- queue.push({
676
- type: 'snapshot',
677
- header: history.header,
678
- cursor: history.cursor,
679
- records: history.records,
680
- hasMore: history.hasMore,
681
- projections: {
682
- asOfSeq: history.cursor,
683
- values: {
684
- title: session.title ?? (this.backend === 'antigravity' ? 'Antigravity' : 'Cursor'),
685
- sessionListMetadata: { blank: session.blank && session.events.length === 0, lastPromptAt: null },
686
- modelSelection: this.modelSelectionProjection(sessionId),
687
- imageLimits: acpImageLimits(this.backend),
688
- },
689
- },
690
- assistantStream: { revision: 0 },
691
- });
692
- try {
693
- const stream = await this.client.openStream(session.acpSessionId, frame => this.acceptAcpFrame(follow, frame), () => {
694
- session.running = false;
695
- this.emitRemoteEvent('api-session/status', [sessionId, false]);
696
- this.closeFollowAfterRemoteStreamClosed(follow);
697
- }, signal);
698
- follow.close = () => stream.close();
699
- }
700
- catch (error) {
701
- this.follows.delete(follow);
702
- queue.close();
703
- throw error;
704
- }
705
- return queue.iterate(() => {
706
- this.follows.delete(follow);
707
- void follow.close?.().catch(() => undefined);
708
- });
709
- }
710
- async ensureFollow(session) {
711
- if ([...this.follows].some(follow => follow.sessionId === session.sessionId))
712
- return;
713
- // Native UI opens session/follow; prompts before follow still work via Host stream open on demand.
714
- const controller = new AbortController();
715
- const queue = new AsyncValueQueue(controller.signal);
716
- const follow = {
717
- sessionId: session.sessionId,
718
- acpSessionId: session.acpSessionId,
719
- queue,
720
- nextSeq: session.events.length === 0 ? 0 : Math.max(...session.events.map(entry => entry.event.seq)) + 1,
721
- turn: Math.max(0, ...session.events.map(entry => (isRecord(entry.event.data) && typeof entry.event.data.turn === 'number' ? entry.event.data.turn : 0))),
722
- stepOpen: false,
723
- streamActive: false,
724
- assistantStreamRevision: 0,
725
- accumulatedText: '',
726
- activeToolCalls: new Set(),
727
- };
728
- this.follows.add(follow);
729
- const stream = await this.client.openStream(session.acpSessionId, frame => this.acceptAcpFrame(follow, frame), () => this.closeFollowAfterRemoteStreamClosed(follow));
730
- follow.close = async () => {
731
- controller.abort();
732
- await stream.close();
733
- };
734
- }
735
- acceptAcpFrame(follow, frame) {
736
- const method = frame.frame.method;
737
- const params = record(frame.frame.params);
738
- if (method === 'session/update') {
739
- this.acceptSessionUpdate(follow, params);
740
- return;
741
- }
742
- if (method === 'session/request_permission' || method === 'cursor/ask_question' || method === 'cursor/create_plan') {
743
- this.acceptApproval(follow, params, method);
744
- }
745
- }
746
- acceptSessionUpdate(follow, params) {
747
- const update = isRecord(params.update) ? params.update : params;
748
- const kind = string(update.sessionUpdate) ?? string(update.type);
749
- const session = this.sessions.get(follow.sessionId);
750
- if (kind === 'model_selection' && isRecord(update.selected)
751
- && update.selected.provider === this.backend && typeof update.selected.model === 'string') {
752
- this.selectedModels.set(follow.sessionId, update.selected);
753
- this.publishProjection(follow.sessionId, 'modelSelection', this.modelSelectionProjection(follow.sessionId), this.nextProjectionSeq());
754
- return;
755
- }
756
- if (kind === 'agent_message_chunk' || kind === 'agent_message') {
757
- const text = extractText(update);
758
- if (text === undefined || text.length === 0)
759
- return;
760
- this.appendAssistantDelta(follow, text);
761
- return;
762
- }
763
- if (kind === 'tool_call' || kind === 'tool_call_update') {
764
- const toolName = string(update.title) ?? string(update.toolName) ?? string(update.name) ?? 'tool';
765
- const status = string(update.status);
766
- const callId = string(update.toolCallId) ?? string(update.callId) ?? toolName;
767
- const terminal = status === 'completed' || status === 'failed';
768
- if (kind === 'tool_call_update' && !terminal)
769
- return;
770
- if (kind === 'tool_call' && follow.activeToolCalls.has(callId))
771
- return;
772
- this.pushEvent(follow, 'tool/call', {
773
- turn: follow.turn,
774
- step: 1,
775
- callId,
776
- name: toolName,
777
- arguments: typeof update.parameters === 'object' && update.parameters !== null
778
- ? JSON.stringify(update.parameters)
779
- : typeof update.rawInput === 'object' && update.rawInput !== null
780
- ? JSON.stringify(update.rawInput)
781
- : '{}',
782
- toolCallId: callId,
783
- toolName,
784
- status: status === 'completed' ? 'finished' : status === 'failed' ? 'failed' : 'running',
785
- });
786
- if (terminal)
787
- follow.activeToolCalls.delete(callId);
788
- else
789
- follow.activeToolCalls.add(callId);
790
- if (session !== undefined)
791
- session.updatedAt = Date.now();
792
- return;
793
- }
794
- if (kind === 'agent_thought_chunk') {
795
- const text = extractText(update);
796
- if (text === undefined || text.length === 0)
797
- return;
798
- this.appendReasoningDelta(follow, text);
799
- return;
800
- }
801
- if (kind === 'prompt_completed' || kind === 'prompt_failed') {
802
- const hasLiveContent = follow.stepOpen || follow.streamActive;
803
- if (!hasLiveContent && Array.isArray(update.catchUp)) {
804
- for (const item of update.catchUp) {
805
- if (!isRecord(item) || typeof item.method !== 'string')
806
- continue;
807
- const params = isRecord(item.params) ? item.params : {};
808
- if (item.method === 'session/update')
809
- this.acceptSessionUpdate(follow, params);
810
- }
811
- }
812
- if (session !== undefined) {
813
- session.running = false;
814
- session.updatedAt = Date.now();
815
- this.emitRemoteEvent('api-session/status', [follow.sessionId, false]);
816
- if (kind === 'prompt_completed')
817
- void this.refreshSessionTitle(session);
818
- }
819
- this.closeFollowAfterRemoteStreamClosed(follow);
820
- }
821
- }
822
- acceptApproval(follow, params, method) {
823
- const requestHandle = string(params.requestHandle);
824
- if (requestHandle === undefined)
825
- return;
826
- this.pendingApprovals.set(requestHandle, { requestHandle, sessionId: follow.sessionId });
827
- const toolName = method === 'cursor/create_plan'
828
- ? (string(params.name) ?? 'plan')
829
- : method === 'cursor/ask_question'
830
- ? (string(params.title) ?? 'question')
831
- : (string(params.toolName) ?? 'permission');
832
- const reason = method === 'cursor/create_plan'
833
- ? string(params.overview) ?? string(params.plan)
834
- : method === 'cursor/ask_question'
835
- ? summarizeQuestions(params)
836
- : string(params.reason);
837
- this.emitApproval({
838
- eventId: requestHandle,
839
- agentId: follow.sessionId,
840
- request: {
841
- toolName,
842
- ...(reason === undefined ? {} : { reason }),
843
- },
844
- });
845
- }
846
- pushAssistantChunk(follow, chunk) {
847
- let attempt = follow.assistantAttempt;
848
- if (attempt === undefined) {
849
- attempt = {
850
- attemptId: `cursor-attempt:${follow.turn}:${follow.nextSeq}`,
851
- startedAfterSeq: follow.nextSeq - 1,
852
- nextIndex: 0,
853
- stream: [],
854
- };
855
- follow.assistantAttempt = attempt;
856
- follow.assistantStreamRevision += 1;
857
- follow.queue.push({
858
- type: 'assistant-stream',
859
- frame: {
860
- type: 'start',
861
- attemptId: attempt.attemptId,
862
- revision: follow.assistantStreamRevision,
863
- startedAfterSeq: attempt.startedAfterSeq,
864
- turn: follow.turn,
865
- step: 1,
866
- },
867
- });
868
- }
869
- const time = Date.now();
870
- const index = attempt.nextIndex++;
871
- // Durable streams use AssistantStreamRecord, unlike the timed chunks in
872
- // the live feed. Without this discriminator the native reader treats a
873
- // raw chunk as a packed run and throws while settling the conversation.
874
- attempt.stream.push({ type: 'chunk', time, chunk });
875
- follow.assistantStreamRevision += 1;
876
- follow.queue.push({
877
- type: 'assistant-stream',
878
- frame: {
879
- type: 'chunk',
880
- attemptId: attempt.attemptId,
881
- revision: follow.assistantStreamRevision,
882
- index,
883
- time,
884
- chunk,
885
- },
886
- });
887
- }
888
- endAssistantAttempt(follow, outcome) {
889
- const attempt = follow.assistantAttempt;
890
- if (attempt === undefined)
891
- return;
892
- follow.assistantAttempt = undefined;
893
- follow.assistantStreamRevision += 1;
894
- follow.queue.push({
895
- type: 'assistant-stream',
896
- frame: {
897
- type: 'end',
898
- attemptId: attempt.attemptId,
899
- revision: follow.assistantStreamRevision,
900
- index: attempt.nextIndex,
901
- outcome,
902
- },
903
- });
904
- }
905
- appendAssistantDelta(follow, delta) {
906
- if (!follow.stepOpen) {
907
- follow.turn += 1;
908
- follow.stepOpen = true;
909
- this.pushEvent(follow, 'turn/start', { turn: follow.turn });
910
- this.pushEvent(follow, 'step/start', { turn: follow.turn, step: 1 });
911
- }
912
- if (follow.blockIndex === undefined) {
913
- follow.blockIndex = (follow.reasoningBlockIndex ?? -1) + 1;
914
- follow.streamActive = true;
915
- this.pushAssistantChunk(follow, { type: 'block-start', index: follow.blockIndex, blockType: 'text' });
916
- }
917
- follow.accumulatedText += delta;
918
- this.pushAssistantChunk(follow, { type: 'text-delta', index: follow.blockIndex, text: delta });
919
- }
920
- appendReasoningDelta(follow, delta) {
921
- if (!follow.stepOpen) {
922
- follow.turn += 1;
923
- follow.stepOpen = true;
924
- this.pushEvent(follow, 'turn/start', { turn: follow.turn });
925
- this.pushEvent(follow, 'step/start', { turn: follow.turn, step: 1 });
926
- }
927
- if (follow.reasoningBlockIndex === undefined) {
928
- follow.reasoningBlockIndex = (follow.blockIndex ?? -1) + 1;
929
- follow.streamActive = true;
930
- this.pushAssistantChunk(follow, { type: 'block-start', index: follow.reasoningBlockIndex, blockType: 'reasoning' });
931
- }
932
- this.pushAssistantChunk(follow, { type: 'reasoning-delta', index: follow.reasoningBlockIndex, text: delta });
933
- }
934
- closeFollowAfterRemoteStreamClosed(follow) {
935
- if (follow.streamActive) {
936
- if (follow.reasoningBlockIndex !== undefined) {
937
- this.pushAssistantChunk(follow, {
938
- type: 'block-end',
939
- index: follow.reasoningBlockIndex,
940
- block: { type: 'reasoning', text: '' },
941
- });
942
- follow.reasoningBlockIndex = undefined;
943
- }
944
- if (follow.blockIndex !== undefined) {
945
- const fullText = follow.accumulatedText;
946
- this.pushAssistantChunk(follow, {
947
- type: 'block-end',
948
- index: follow.blockIndex,
949
- block: { type: 'text', text: fullText },
950
- });
951
- }
952
- this.pushAssistantChunk(follow, {
953
- type: 'finish',
954
- reason: { kind: 'stop' },
955
- });
956
- const fullText = follow.accumulatedText;
957
- const selection = this.modelSelection(follow.sessionId);
958
- const stream = follow.assistantAttempt?.stream ?? [];
959
- const assistantMessageSeq = this.pushEvent(follow, 'assistant/message', {
960
- turn: follow.turn,
961
- step: 1,
962
- message: {
963
- id: `${follow.sessionId}:${follow.turn}`,
964
- role: 'assistant',
965
- content: [{ type: 'text', text: fullText }],
966
- source: { kind: 'model', provider: selection.provider, model: selection.model },
967
- },
968
- stream,
969
- });
970
- this.endAssistantAttempt(follow, {
971
- kind: 'committed',
972
- eventType: 'assistant/message',
973
- seq: assistantMessageSeq,
974
- });
975
- this.pushEvent(follow, 'step/end', { turn: follow.turn, step: 1 });
976
- this.pushEvent(follow, 'turn/end', { turn: follow.turn, reason: { kind: 'completed' } });
977
- }
978
- else if (follow.stepOpen) {
979
- this.pushEvent(follow, 'step/end', { turn: follow.turn, step: 1 });
980
- this.pushEvent(follow, 'turn/end', { turn: follow.turn, reason: { kind: 'completed' } });
981
- }
982
- follow.streamActive = false;
983
- follow.stepOpen = false;
984
- follow.blockIndex = undefined;
985
- follow.reasoningBlockIndex = undefined;
986
- follow.accumulatedText = '';
987
- }
988
- pushEvent(follow, type, data) {
989
- const session = this.sessions.get(follow.sessionId);
990
- const seq = session !== undefined ? session.events.length : follow.nextSeq++;
991
- const event = {
992
- type,
993
- seq,
994
- time: Date.now(),
995
- data,
996
- ...(isSurfaceEvent(type) ? { surfaceOp: 'append' } : {}),
997
- };
998
- if (session !== undefined) {
999
- session.events.push({ type: 'event', event });
1000
- }
1001
- for (const f of this.follows) {
1002
- if (f.sessionId === follow.sessionId) {
1003
- f.queue.push({ type: 'event', event });
1004
- f.nextSeq = seq + 1;
1005
- }
1006
- }
1007
- this.broadcastRcMux({
1008
- type: 'session/event',
1009
- sessionId: follow.sessionId,
1010
- event,
1011
- });
1012
- return seq;
1013
- }
1014
611
  registerSession(acpSessionId, cwd, title, createdAt = Date.now(), updatedAt = Date.now(), blank = true) {
1015
612
  const session = {
1016
- sessionId: `${this.backend === 'antigravity' ? ACP_SESSION_PREFIX : CURSOR_SESSION_PREFIX}${acpSessionId}`,
613
+ sessionId: `${sessionPrefix(this.backend)}${acpSessionId}`,
1017
614
  acpSessionId,
1018
615
  cwd,
1019
616
  ...(title === undefined ? {} : { title }),
@@ -1031,10 +628,10 @@ export class AcpVirtualHarness {
1031
628
  if (this.closed || session.titleOverridden || !next || next === 'Untitled Session' || next === session.title)
1032
629
  return;
1033
630
  session.title = next;
1034
- this.publishProjection(session.sessionId, 'title', next);
631
+ this.events.publishProjection(session.sessionId, 'title', next);
1035
632
  }
1036
633
  async refreshSessionTitle(session) {
1037
- if (this.backend !== 'antigravity' || this.closed || session.titleOverridden || this.client.listSessions === undefined)
634
+ if (this.backend === 'cursor' || this.closed || session.titleOverridden || this.client.listSessions === undefined)
1038
635
  return;
1039
636
  try {
1040
637
  const items = await this.client.listSessions(session.cwd, this.backend, 30);
@@ -1047,7 +644,7 @@ export class AcpVirtualHarness {
1047
644
  }
1048
645
  }
1049
646
  async discoverAndAttachSessions(workspace) {
1050
- if (this.backend !== 'antigravity')
647
+ if (this.backend === 'cursor')
1051
648
  return;
1052
649
  try {
1053
650
  let discovered = [];
@@ -1055,12 +652,14 @@ export class AcpVirtualHarness {
1055
652
  try {
1056
653
  discovered = await this.client.listSessions(workspace.path, this.backend, 30);
1057
654
  }
1058
- catch {
1059
- // Keep the projection empty until remote history is available.
655
+ catch (error) {
656
+ if (this.backend === 'pi')
657
+ throw error;
658
+ // Preserve legacy AGY catalog availability behavior.
1060
659
  }
1061
660
  }
1062
661
  for (const item of discovered) {
1063
- const sessionId = `${ACP_SESSION_PREFIX}${item.conversationId}`;
662
+ const sessionId = `${sessionPrefix(this.backend)}${item.conversationId}`;
1064
663
  if (!this.sessions.has(sessionId)) {
1065
664
  this.registerSession(item.conversationId, workspace.path, item.title, item.createdAt, item.updatedAt, false);
1066
665
  }
@@ -1074,8 +673,10 @@ export class AcpVirtualHarness {
1074
673
  workspace.sessionCount = workspace.sessionIds.length;
1075
674
  workspace.updatedAt = new Date().toISOString();
1076
675
  }
1077
- catch {
1078
- // 容错降级
676
+ catch (error) {
677
+ if (this.backend === 'pi')
678
+ throw error;
679
+ // Preserve legacy AGY catalog availability behavior.
1079
680
  }
1080
681
  }
1081
682
  attachSessionToWorkspace(cwd, sessionId) {
@@ -1096,10 +697,10 @@ export class AcpVirtualHarness {
1096
697
  const cwd = workspace.path;
1097
698
  const created = await this.client.createSession(cwd, 'agent', this.backend);
1098
699
  const sessionId = created.sessionId;
1099
- const title = this.backend === 'antigravity' ? 'Antigravity' : workspace.title;
700
+ const title = this.backend === 'antigravity' ? 'Antigravity' : this.backend === 'opencode' ? 'OpenCode' : workspace.title;
1100
701
  const session = this.registerSession(sessionId, cwd, title);
1101
702
  this.attachSessionToWorkspace(cwd, session.sessionId);
1102
- this.emitRemoteEvent('api-session/added', [this.sessionSummary(session, 0)]);
703
+ this.events.emitRemoteEvent('api-session/added', [this.sessionSummary(session, 0)]);
1103
704
  this.publishWorkspaceBaseline();
1104
705
  return session;
1105
706
  }
@@ -1133,7 +734,7 @@ export class AcpVirtualHarness {
1133
734
  kind: 'sequenced',
1134
735
  asOfSeq,
1135
736
  values: {
1136
- title: session.title ?? (this.backend === 'antigravity' ? 'Antigravity' : 'Cursor'),
737
+ title: session.title ?? (this.backend === "pi" ? "PI" : this.backend === "opencode" ? "OpenCode" : this.backend === "antigravity" ? "Antigravity" : "Cursor"),
1137
738
  sessionListMetadata: { blank: session.blank, lastPromptAt: null },
1138
739
  modelSelection: this.modelSelectionProjection(session.sessionId),
1139
740
  imageLimits: acpImageLimits(this.backend),
@@ -1149,9 +750,9 @@ export class AcpVirtualHarness {
1149
750
  return { lastUsed: selection, next: selection };
1150
751
  }
1151
752
  async hostSessionModels(sessionId) {
1152
- if (this.backend !== 'antigravity' || !this.client.sessionModels)
753
+ if (this.backend === 'cursor' || this.backend === 'opencode' || !this.client.sessionModels)
1153
754
  return { current: { provider: this.backend, model: 'host-settings' }, routable: true, groups: [], failures: [] };
1154
- const models = await this.client.sessionModels(nativeAcpId(sessionId, this.backend), 'antigravity');
755
+ const models = await this.client.sessionModels(nativeAcpId(sessionId, this.backend), this.backend);
1155
756
  this.selectedModels.set(sessionId, models.current);
1156
757
  return models;
1157
758
  }
@@ -1161,7 +762,7 @@ export class AcpVirtualHarness {
1161
762
  const provider = requiredString(request.provider, 'provider');
1162
763
  const model = requiredString(request.model, 'model');
1163
764
  const reasoningEffort = string(request.reasoningEffort);
1164
- const catalog = this.backend === 'antigravity' ? await this.hostSessionModels(sessionId) : modelCatalog(this.backend);
765
+ const catalog = this.backend !== 'cursor' ? await this.hostSessionModels(sessionId) : modelCatalog(this.backend);
1165
766
  const group = catalog.groups.find(g => g.id === provider);
1166
767
  const targetModel = group?.models.find(m => m.id === model);
1167
768
  if (group === undefined || targetModel === undefined) {
@@ -1176,16 +777,16 @@ export class AcpVirtualHarness {
1176
777
  model,
1177
778
  ...(reasoningEffort === undefined ? {} : { reasoningEffort }),
1178
779
  };
1179
- if (this.backend === 'antigravity') {
780
+ if (this.backend !== 'cursor' && this.backend !== 'opencode') {
1180
781
  if (!this.client.selectModel)
1181
782
  return failure('model-unavailable', 'The Host does not support ACP model selection.');
1182
- const confirmed = await this.client.selectModel(nativeAcpId(sessionId, this.backend), 'antigravity', selected);
783
+ const confirmed = await this.client.selectModel(nativeAcpId(sessionId, this.backend), this.backend, selected);
1183
784
  this.selectedModels.set(sessionId, confirmed);
1184
785
  }
1185
786
  else
1186
- return failure('model-unavailable', 'Cursor model selection follows Host settings.');
787
+ return failure('model-unavailable', `${this.backendLabel()} model selection follows Host settings.`);
1187
788
  const seq = this.nextProjectionSeq();
1188
- this.publishProjection(sessionId, 'modelSelection', this.modelSelectionProjection(sessionId), seq);
789
+ this.events.publishProjection(sessionId, 'modelSelection', this.modelSelectionProjection(sessionId), seq);
1189
790
  return success({ selected: this.modelSelection(sessionId) });
1190
791
  }
1191
792
  publishWorkspaceBaseline() {
@@ -1196,393 +797,10 @@ export class AcpVirtualHarness {
1196
797
  queue.push({ type: 'archived', archivedSessionIds: [] });
1197
798
  }
1198
799
  }
1199
- publishProjection(sessionId, key, value, seq = this.nextProjectionSeq()) {
1200
- for (const queue of this.controlStreams) {
1201
- queue.push({ type: 'projection', sessionId, key, value, asOfSeq: seq, seq });
1202
- }
1203
- this.broadcastRcMux({ type: 'session/projection', sessionId, key, value, seq });
1204
- }
1205
- emitRemoteEvent(event, args) {
1206
- for (const queue of this.eventStreams.values())
1207
- queue.push({ type: 'emit', event, args });
1208
- const sessionId = typeof args[0] === 'string' ? args[0] : undefined;
1209
- if (event === 'api-session/status' && sessionId !== undefined && typeof args[1] === 'boolean') {
1210
- this.broadcastRcHost({ type: 'host/session-status', sessionId, running: args[1] });
1211
- }
1212
- else if (event === 'api-session/removed' && sessionId !== undefined) {
1213
- this.broadcastRcHost({ type: 'host/session-removed', sessionId });
1214
- }
1215
- else if (event === 'api-session/added' && isRecord(args[0])) {
1216
- const summary = args[0];
1217
- this.broadcastRcHost({
1218
- type: 'host/session-added',
1219
- sessionId: summary.sessionId,
1220
- blank: summary.blank === true,
1221
- ...(typeof summary.cwd === 'string' ? { cwd: summary.cwd } : {}),
1222
- });
1223
- }
1224
- }
1225
- emitApproval(input) {
1226
- for (const queue of this.eventStreams.values())
1227
- queue.push({
1228
- type: 'waterfall',
1229
- event: 'approval/request',
1230
- eventId: input.eventId,
1231
- agentId: input.agentId,
1232
- request: input.request,
1233
- });
1234
- this.broadcastRcMux({
1235
- type: 'approval/requested',
1236
- sessionId: input.agentId,
1237
- approvalId: input.eventId,
1238
- toolName: string(input.request.toolName) ?? this.backendLabel(),
1239
- ...(typeof input.request.reason === 'string' ? { reason: input.request.reason } : {}),
1240
- }, input.eventId);
1241
- }
1242
800
  nextProjectionSeq() {
1243
801
  this.lastProjectionSeq += 1;
1244
802
  return this.lastProjectionSeq;
1245
803
  }
1246
- createApiProxy() {
1247
- const call = (endpoint) => async (request, signal) => {
1248
- const payload = endpoint === 'session.prompt' && isRecord(request.payload)
1249
- ? { ...request.payload, requestId: String(request.rpcId) }
1250
- : request.payload;
1251
- const result = await this.dispatch(rcEndpoint(endpoint), { args: { request: payload } }, signal ?? new AbortController().signal);
1252
- return {
1253
- rpcId: request.rpcId,
1254
- result: (result.ok
1255
- ? success(result.value)
1256
- : failure(result.error.code, result.error.message, result.error.details)),
1257
- };
1258
- };
1259
- return {
1260
- sessions: {
1261
- list: call('session.list'),
1262
- search: call('session.search'),
1263
- create: call('session.create'),
1264
- history: call('session.history'),
1265
- models: call('session.models'),
1266
- selectModel: call('session.selectModel'),
1267
- rename: call('session.rename'),
1268
- fork: call('session.fork'),
1269
- prompt: call('session.prompt'),
1270
- attachment: call('session.attachment'),
1271
- updateQueue: call('session.updateQueue'),
1272
- cancel: call('session.cancel'),
1273
- },
1274
- workspace: {
1275
- list: call('workspace.list'),
1276
- create: call('workspace.create'),
1277
- rename: call('workspace.rename'),
1278
- delete: call('workspace.delete'),
1279
- insertBefore: call('workspace.insertBefore'),
1280
- insertSessionBefore: call('workspace.insertSessionBefore'),
1281
- archiveSession: call('workspace.archiveSession'),
1282
- },
1283
- subagents: {},
1284
- host: {
1285
- describe: call('host.describe'),
1286
- listDirectory: call('host.listDirectory'),
1287
- },
1288
- skills: { list: call('skills.list') },
1289
- agentPresets: {},
1290
- goals: {},
1291
- settings: {},
1292
- credentials: {},
1293
- llm: {},
1294
- events: {
1295
- mux: ((request, signal) => this.rcMux(request, signal)),
1296
- host: ((request, signal) => this.rcHost(request, signal)),
1297
- },
1298
- downloads: {},
1299
- respond: async (message) => {
1300
- const pending = this.pendingApprovals.get(String(message.rpcId))
1301
- ?? [...this.pendingApprovals.values()].find(item => item.requestHandle === String(message.rpcId));
1302
- if (pending === undefined)
1303
- return { accepted: false, reason: 'not-pending' };
1304
- const result = message.result;
1305
- const outcome = result.ok ? result.value : undefined;
1306
- const decision = outcome === 'allowed-once' ? 'allow-once' : outcome === 'cancelled' ? 'cancel' : 'reject-once';
1307
- await this.client.respond(pending.requestHandle, decision);
1308
- this.pendingApprovals.delete(pending.requestHandle);
1309
- return { accepted: true };
1310
- },
1311
- };
1312
- }
1313
- async *rcMux(request, signal) {
1314
- const queue = new AsyncValueQueue(signal);
1315
- this.rcMuxStreams.add(queue);
1316
- for (const session of this.sessions.values()) {
1317
- queue.push({
1318
- rpcId: `${String(request.rpcId)}:${session.sessionId}:subscribed`,
1319
- payload: { type: 'session/subscribed', sessionId: session.sessionId, lastSeq: -1 },
1320
- });
1321
- }
1322
- try {
1323
- yield* queue;
1324
- }
1325
- finally {
1326
- this.rcMuxStreams.delete(queue);
1327
- queue.close();
1328
- }
1329
- }
1330
- async *rcHost(_request, signal) {
1331
- const queue = new AsyncValueQueue(signal);
1332
- this.rcHostStreams.add(queue);
1333
- try {
1334
- yield* queue;
1335
- }
1336
- finally {
1337
- this.rcHostStreams.delete(queue);
1338
- queue.close();
1339
- }
1340
- }
1341
- broadcastRcMux(payload, rpcId = `cursor-mux:${Date.now()}:${Math.random()}`) {
1342
- for (const queue of this.rcMuxStreams)
1343
- queue.push({ rpcId, payload });
1344
- }
1345
- broadcastRcHost(payload) {
1346
- const frame = { rpcId: `cursor-host:${Date.now()}:${Math.random()}`, payload };
1347
- for (const queue of this.rcHostStreams)
1348
- queue.push(frame);
1349
- }
1350
- }
1351
- function recreateWorkspaceFromId(workspaceId, backend) {
1352
- const prefix = backend === 'antigravity' ? AGY_WORKSPACE_PREFIX : CURSOR_WORKSPACE_PREFIX;
1353
- if (!workspaceId.startsWith(prefix))
1354
- return undefined;
1355
- try {
1356
- const path = decodeURIComponent(workspaceId.slice(prefix.length));
1357
- if (path.length > 0)
1358
- return createAcpWorkspaceView(path, backend);
1359
- }
1360
- catch {
1361
- return undefined;
1362
- }
1363
- return undefined;
1364
- }
1365
- function nativeWorkspace(view) {
1366
- return {
1367
- workspaceId: view.workspaceId,
1368
- path: view.path,
1369
- title: view.title,
1370
- sessionIds: view.sessionIds,
1371
- createdAt: view.createdAt,
1372
- updatedAt: view.updatedAt,
1373
- };
1374
- }
1375
- const CURSOR_MODELS = [
1376
- { id: 'auto', name: 'Auto' },
1377
- { id: 'claude-3.7-sonnet', name: 'Claude 3.7 Sonnet' },
1378
- { id: 'claude-3.5-sonnet', name: 'Claude 3.5 Sonnet' },
1379
- { id: 'gpt-4o', name: 'GPT-4o' },
1380
- { id: 'o3-mini', name: 'o3-mini' },
1381
- { id: 'deepseek-r1', name: 'DeepSeek R1' },
1382
- ];
1383
- export function modelCatalog(backend = 'cursor') {
1384
- const provider = backend === 'antigravity' ? 'antigravity' : CURSOR_PROVIDER;
1385
- const name = backend === 'antigravity' ? 'Antigravity' : 'Cursor';
1386
- const models = backend === 'antigravity' ? [] : CURSOR_MODELS;
1387
- const defaultModel = models[0]?.id ?? 'host-settings';
1388
- return {
1389
- default: { provider, model: defaultModel },
1390
- routableProviders: [provider],
1391
- groups: [{
1392
- id: provider,
1393
- name,
1394
- models,
1395
- }],
1396
- failures: [],
1397
- };
1398
- }
1399
- function extractPromptText(content) {
1400
- const parts = content.flatMap(part => {
1401
- const item = record(part);
1402
- if (item.type === 'text' && typeof item.text === 'string')
1403
- return [item.text];
1404
- return [];
1405
- });
1406
- const text = parts.join('');
1407
- return text.length === 0 ? undefined : text;
1408
- }
1409
- function extractText(update) {
1410
- const content = update.content;
1411
- if (typeof content === 'string')
1412
- return content;
1413
- if (isRecord(content) && typeof content.text === 'string')
1414
- return content.text;
1415
- if (Array.isArray(content)) {
1416
- const parts = content
1417
- .map(part => (isRecord(part) && typeof part.text === 'string' ? part.text : undefined))
1418
- .filter((part) => part !== undefined);
1419
- return parts.length === 0 ? undefined : parts.join('');
1420
- }
1421
- return typeof update.text === 'string' ? update.text : undefined;
1422
- }
1423
- function summarizeQuestions(params) {
1424
- if (!Array.isArray(params.questions))
1425
- return undefined;
1426
- const lines = params.questions
1427
- .map(question => (isRecord(question) ? string(question.prompt) : undefined))
1428
- .filter((line) => line !== undefined);
1429
- return lines.length === 0 ? undefined : lines.join('\n');
1430
- }
1431
- function sessionIdFromAddress(address) {
1432
- if (typeof address.sessionId === 'string' && address.sessionId.length > 0)
1433
- return address.sessionId;
1434
- if (typeof address.childSessionId === 'string' && address.childSessionId.length > 0)
1435
- return address.childSessionId;
1436
- if (address.kind === 'session' && typeof address.sessionId === 'string' && address.sessionId.length > 0) {
1437
- return address.sessionId;
1438
- }
1439
- throw new Error('The sessionId is required.');
1440
- }
1441
- function extractSessionId(request) {
1442
- if (typeof request.sessionId === 'string' && request.sessionId.length > 0)
1443
- return request.sessionId;
1444
- if (isRecord(request.address))
1445
- return sessionIdFromAddress(request.address);
1446
- throw new Error('The sessionId is required.');
1447
- }
1448
- function nativeAcpId(sessionId, backend) {
1449
- const prefix = backend === 'antigravity' ? ACP_SESSION_PREFIX : CURSOR_SESSION_PREFIX;
1450
- if (!sessionId.startsWith(prefix) || sessionId.length === prefix.length) {
1451
- throw new Error('The selected Session does not belong to the virtual harness.');
1452
- }
1453
- return sessionId.slice(prefix.length);
1454
- }
1455
- function isSurfaceEvent(type) {
1456
- return type === 'user/message' || type === 'assistant/message' || type === 'tool/result';
1457
- }
1458
- function carrierArgs(payload) {
1459
- return record(record(payload).args);
1460
- }
1461
- function requestArg(args) {
1462
- return record(args.request ?? args._request ?? args);
1463
- }
1464
- function rcEndpoint(endpoint) {
1465
- if (endpoint === 'workspace.list')
1466
- return 'workspace/list';
1467
- return endpoint.replace('.', '/');
1468
- }
1469
- function success(value) {
1470
- return { ok: true, value };
1471
- }
1472
- function failure(code, message, details = {}) {
1473
- return { ok: false, error: { code, message, details } };
1474
- }
1475
- function business(value) {
1476
- if (isRecord(value) && value.ok === false && isRecord(value.error)) {
1477
- return { ok: false, error: {
1478
- code: string(value.error.code) ?? 'internal',
1479
- message: string(value.error.message) ?? 'The virtual Harness request failed.',
1480
- details: isRecord(value.error.details) ? value.error.details : {},
1481
- } };
1482
- }
1483
- if (isRecord(value) && value.ok === true)
1484
- return { ok: true, value: value.value };
1485
- return { ok: true, value };
1486
- }
1487
- function fail(code, message, details = {}) {
1488
- return { ok: false, error: { code, message, details } };
1489
- }
1490
- function failFrom(error) {
1491
- const source = error instanceof Error ? error : new Error(String(error));
1492
- const code = 'code' in source && typeof source.code === 'string' ? source.code : 'internal';
1493
- return fail(code, source.message);
1494
- }
1495
- function record(value) {
1496
- return isRecord(value) ? value : {};
1497
- }
1498
- function isRecord(value) {
1499
- return typeof value === 'object' && value !== null && !Array.isArray(value);
1500
- }
1501
- function array(value) {
1502
- return Array.isArray(value) ? value : [];
1503
- }
1504
- function string(value) {
1505
- return typeof value === 'string' ? value : undefined;
1506
- }
1507
- function integer(value) {
1508
- return typeof value === 'number' && Number.isSafeInteger(value) ? value : undefined;
1509
- }
1510
- function optionalInteger(value) {
1511
- if (value === undefined)
1512
- return undefined;
1513
- const parsed = integer(value);
1514
- if (parsed === undefined)
1515
- throw new Error('The History cursor is invalid.');
1516
- return parsed;
1517
- }
1518
- function optionalPositiveInteger(value) {
1519
- const parsed = optionalInteger(value);
1520
- if (parsed !== undefined && parsed <= 0)
1521
- throw new Error('The History page size is invalid.');
1522
- return parsed;
1523
- }
1524
- function requiredString(value, field) {
1525
- if (typeof value !== 'string' || value.length === 0)
1526
- throw new Error(`The ${field} is required.`);
1527
- return value;
1528
- }
1529
- class AsyncValueQueue {
1530
- signal;
1531
- values = [];
1532
- waiters = [];
1533
- closed = false;
1534
- onAbort;
1535
- constructor(signal) {
1536
- this.signal = signal;
1537
- this.onAbort = () => this.close();
1538
- signal.addEventListener('abort', this.onAbort, { once: true });
1539
- if (signal.aborted)
1540
- this.close();
1541
- }
1542
- push(value) {
1543
- if (this.closed)
1544
- return;
1545
- const waiter = this.waiters.shift();
1546
- if (waiter === undefined)
1547
- this.values.push(value);
1548
- else
1549
- waiter({ done: false, value });
1550
- }
1551
- close() {
1552
- if (this.closed)
1553
- return;
1554
- this.closed = true;
1555
- this.signal.removeEventListener('abort', this.onAbort);
1556
- for (const waiter of this.waiters.splice(0))
1557
- waiter({ done: true, value: undefined });
1558
- }
1559
- iterate(dispose) {
1560
- const queue = this;
1561
- return {
1562
- async *[Symbol.asyncIterator]() {
1563
- try {
1564
- yield* queue;
1565
- }
1566
- finally {
1567
- dispose();
1568
- queue.close();
1569
- }
1570
- },
1571
- };
1572
- }
1573
- async *[Symbol.asyncIterator]() {
1574
- while (true) {
1575
- if (this.values.length > 0) {
1576
- yield this.values.shift();
1577
- continue;
1578
- }
1579
- if (this.closed)
1580
- return;
1581
- const next = await new Promise(resolve => this.waiters.push(resolve));
1582
- if (next.done)
1583
- return;
1584
- yield next.value;
1585
- }
1586
- }
1587
804
  }
805
+ export { acpCwdWorkspaceId, createAcpWorkspaceView, discoverAcpVirtualWorkspaces, modelCatalog } from './virtual-catalog.js';
1588
806
  //# sourceMappingURL=virtual-harness.js.map