@deksden-com/dd-flow-cli 0.9.0-beta.134 → 0.9.0-beta.136

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 (161) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +33 -0
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +1 -0
  5. package/dist/cli/command-metadata.js +144 -0
  6. package/dist/cli/command-option-descriptions.js +186 -0
  7. package/dist/cli/completion-cli.js +70 -0
  8. package/dist/cli/completion.js +57 -0
  9. package/dist/cli/error-output.js +52 -0
  10. package/dist/cli/executor-onboarding.js +71 -0
  11. package/dist/cli/harnesses.js +84 -0
  12. package/dist/cli/models.js +39 -0
  13. package/dist/cli/operator-profiles.js +61 -0
  14. package/dist/cli/output-options.js +35 -0
  15. package/dist/cli/response-file.js +69 -0
  16. package/dist/cli/run-cli.js +5 -0
  17. package/dist/cli/setup.js +147 -0
  18. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  19. package/dist/harness-runtime/bin/dd-opencode.mjs +1 -1
  20. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +13 -0
  21. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +193 -39
  22. package/dist/harness-runtime/lib/dd-opencode.mjs +295 -100
  23. package/dist/harness-runtime/lib/delegation-instructions.mjs +3 -3
  24. package/dist/harness-runtime/lib/managed-daemon.mjs +1 -1
  25. package/dist/harness-runtime/lib/native-hook-command.d.mts +2 -2
  26. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  27. package/dist/harness-runtime/lib/opencode-plugin.mjs +60 -0
  28. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  29. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  30. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  31. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  32. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  33. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  34. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  35. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  36. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  37. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  38. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  39. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  40. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  41. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  42. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  43. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  44. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  45. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  46. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  47. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  48. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  49. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  50. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  51. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  52. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  53. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  54. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  55. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  56. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  57. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  58. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  59. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  60. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  61. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  62. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  63. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  64. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  65. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  66. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  67. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  68. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  69. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  70. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  71. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  72. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  73. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  74. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  75. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  76. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  77. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  78. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  79. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  80. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  81. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  82. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  83. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  84. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  85. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  86. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  87. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  88. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  89. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  90. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  91. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  92. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  93. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  94. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  95. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  96. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  97. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  98. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  99. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  100. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  101. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  102. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  103. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  104. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  105. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  106. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  107. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  108. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  109. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  110. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  111. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  112. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  113. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  114. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  115. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  116. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  117. package/dist/services/cli-operation-classifier.js +4 -0
  118. package/dist/services/cli-upgrade-allowlist.js +27 -0
  119. package/dist/services/discovery/agy-discovery.js +2 -0
  120. package/dist/services/discovery/codex-discovery.js +2 -0
  121. package/dist/services/discovery/droid-discovery.js +2 -0
  122. package/dist/services/discovery/grok-discovery.js +2 -0
  123. package/dist/services/discovery/opencode-discovery.js +2 -0
  124. package/dist/services/discovery/static.js +31 -0
  125. package/dist/services/discovery/zcode-discovery.js +2 -0
  126. package/dist/services/engines.js +23 -25
  127. package/dist/services/eval-snapshots.js +1 -1
  128. package/dist/services/executor-onboarding.js +56 -0
  129. package/dist/services/external-work-launch.js +1 -1
  130. package/dist/services/harness-discovery.js +29 -0
  131. package/dist/services/harness-doctor.js +75 -0
  132. package/dist/services/harness-probe-effects.js +51 -0
  133. package/dist/services/harness-version-probe.js +100 -0
  134. package/dist/services/hooks.js +1 -1
  135. package/dist/services/hybrid-contracts.js +455 -0
  136. package/dist/services/installation-lock.js +92 -0
  137. package/dist/services/managed-processes.js +30 -0
  138. package/dist/services/merge-server.js +1 -1
  139. package/dist/services/model-catalog.js +109 -0
  140. package/dist/services/model-discovery.js +46 -0
  141. package/dist/services/operator-profiles.js +236 -0
  142. package/dist/services/release-archive-producer.js +52 -0
  143. package/dist/services/release-archive.js +143 -0
  144. package/dist/services/release-cache.js +142 -0
  145. package/dist/services/release-compatibility.js +18 -0
  146. package/dist/services/release-download.js +67 -0
  147. package/dist/services/release-envelope.js +104 -0
  148. package/dist/services/release-manifest.js +45 -0
  149. package/dist/services/release-payload.js +114 -0
  150. package/dist/services/release-producer.js +63 -0
  151. package/dist/services/release-provenance.js +160 -0
  152. package/dist/services/release-registry.js +66 -0
  153. package/dist/services/release-source.js +194 -0
  154. package/dist/services/run-controller-recovery.js +2 -2
  155. package/dist/services/run-controller.js +3 -2
  156. package/dist/services/run-health.js +149 -0
  157. package/dist/services/runs.js +61 -13
  158. package/dist/services/setup.js +377 -0
  159. package/dist/storage/database.js +18 -3
  160. package/dist/storage/writer-contract.js +8 -0
  161. package/package.json +4 -1
@@ -0,0 +1,1661 @@
1
+ import {
2
+ isFormNotFoundError2,
3
+ isFormAlreadySettledError2,
4
+ isPermissionNotFoundError2
5
+ } from "./service-probe-vt9jbch8.js";
6
+
7
+ // src/solid/data.ts
8
+ import { Worktree } from "@opencode/schema/worktree";
9
+ import { SessionID } from "@opencode/schema/session-id";
10
+ import { SessionMessage } from "@opencode/schema/session-message";
11
+ import { createStore, produce, reconcile } from "solid-js/store";
12
+ import { batch, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
13
+ var messageIDFromEvent = (eventID) => eventID.replace(/^evt_/, "msg_");
14
+ var messagePageLimit = 20;
15
+ var settleMs2 = 150;
16
+ function locationKey2(location) {
17
+ return JSON.stringify([location.directory, location.workspaceID]);
18
+ }
19
+ function locationQuery(ref) {
20
+ return { directory: ref.directory };
21
+ }
22
+ function formRequestOptions(sessionID, ref) {
23
+ if (sessionID !== "global" || !ref)
24
+ return;
25
+ return {
26
+ headers: {
27
+ "x-opencode-directory": encodeURIComponent(ref.directory)
28
+ }
29
+ };
30
+ }
31
+ function createSync() {
32
+ const state = new Map;
33
+ const start = (key, load, wait) => {
34
+ const entry = { promise: Promise.resolve(), invalidated: false, started: !wait };
35
+ state.set(key, entry);
36
+ const run = () => {
37
+ entry.started = true;
38
+ return load();
39
+ };
40
+ entry.promise = (wait ? wait.catch(() => {
41
+ return;
42
+ }).then(run) : run()).then(() => {
43
+ if (state.get(key) === entry && !entry.invalidated)
44
+ state.set(key, true);
45
+ }).finally(() => {
46
+ if (state.get(key) === entry)
47
+ state.delete(key);
48
+ });
49
+ return entry.promise;
50
+ };
51
+ return {
52
+ run(key, load) {
53
+ const active = state.get(key);
54
+ if (active === true)
55
+ return Promise.resolve();
56
+ if (!active)
57
+ return start(key, load);
58
+ if (!active.invalidated)
59
+ return active.promise;
60
+ return start(key, load, active.promise);
61
+ },
62
+ complete(key) {
63
+ if (state.has(key))
64
+ return;
65
+ state.set(key, true);
66
+ },
67
+ has(key) {
68
+ return state.has(key);
69
+ },
70
+ pending(key) {
71
+ const active = state.get(key);
72
+ return active !== undefined && active !== true;
73
+ },
74
+ invalidate(key) {
75
+ if (key) {
76
+ const active = state.get(key);
77
+ if (active === true)
78
+ state.delete(key);
79
+ if (active !== undefined && active !== true && active.started)
80
+ active.invalidated = true;
81
+ return;
82
+ }
83
+ state.forEach((active, current) => {
84
+ if (active === true)
85
+ state.delete(current);
86
+ if (active !== true && active.started)
87
+ active.invalidated = true;
88
+ });
89
+ }
90
+ };
91
+ }
92
+ function createData2(config) {
93
+ const api = config.api;
94
+ let disposed = false;
95
+ onCleanup(() => disposed = true);
96
+ function refresh(load) {
97
+ if (disposed || config.connection && config.connection.status() !== "connected")
98
+ return;
99
+ load().catch((error) => {
100
+ if (disposed || config.connection && config.connection.status() !== "connected")
101
+ return;
102
+ if (config.onError)
103
+ return config.onError(error);
104
+ console.error("Failed to refresh client data", error);
105
+ });
106
+ }
107
+ const settling = new Map;
108
+ onCleanup(() => settling.forEach((timer) => clearTimeout(timer)));
109
+ function settle(key, load) {
110
+ clearTimeout(settling.get(key));
111
+ settling.set(key, setTimeout(() => {
112
+ settling.delete(key);
113
+ refresh(load);
114
+ }, settleMs2));
115
+ }
116
+ const [store, setStore] = createStore({
117
+ session: {
118
+ info: {},
119
+ family: {},
120
+ active: {},
121
+ message: {},
122
+ messageCursor: {},
123
+ messageLoading: {},
124
+ pending: {},
125
+ permission: {},
126
+ form: {}
127
+ },
128
+ project: {
129
+ info: {},
130
+ permission: {}
131
+ },
132
+ location: {}
133
+ });
134
+ const [defaultLocation, setDefaultLocation] = createSignal({ directory: config.directory });
135
+ const sessions = createMemo(() => Object.values(store.session.info).toSorted((a, b) => b.time.updated - a.time.updated));
136
+ const messageIndex = new Map;
137
+ const sync = createSync();
138
+ let activeUpdates;
139
+ const pendingUpdates = new Map;
140
+ function setSessionActive(sessionID, status) {
141
+ activeUpdates?.set(sessionID, status);
142
+ setStore("session", "active", sessionID, status);
143
+ }
144
+ function removePending(sessionID, inboxID) {
145
+ if (!inboxID)
146
+ return;
147
+ pendingUpdates.get(sessionID)?.set(inboxID, undefined);
148
+ if (store.session.pending[sessionID]?.some((item) => item.id === inboxID))
149
+ setStore("session", "pending", sessionID, (store.session.pending[sessionID] ?? []).filter((item) => item.id !== inboxID));
150
+ }
151
+ function removePermission(sessionID, requestID) {
152
+ const requests = store.session.permission[sessionID];
153
+ if (!requests?.some((request) => request.id === requestID))
154
+ return;
155
+ setStore("session", "permission", sessionID, requests.filter((request) => request.id !== requestID));
156
+ }
157
+ function removeForm(sessionID, formID, ref) {
158
+ const forms = store.session.form[sessionID];
159
+ if (!forms)
160
+ return false;
161
+ const location = ref && locationKey2(ref);
162
+ const next = forms.filter((form) => {
163
+ if (form.id !== formID)
164
+ return true;
165
+ if (sessionID !== "global" || !location)
166
+ return false;
167
+ return !form.location || locationKey2(form.location) !== location;
168
+ });
169
+ if (next.length === forms.length)
170
+ return false;
171
+ setStore("session", "form", sessionID, next);
172
+ return true;
173
+ }
174
+ function settleForm(input, ref, request) {
175
+ return request.catch((error) => {
176
+ if (!isFormNotFoundError2(error) && !isFormAlreadySettledError2(error) || error.id !== input.formID)
177
+ throw error;
178
+ }).then(() => {
179
+ if (!removeForm(input.sessionID, input.formID, ref))
180
+ return;
181
+ result.session.form.invalidate(input.sessionID, ref);
182
+ result.session.form.sync(input.sessionID, ref).catch(() => {
183
+ return;
184
+ });
185
+ });
186
+ }
187
+ function updatePending(sessionID, inboxID, delivery) {
188
+ const index = store.session.pending[sessionID]?.findIndex((item) => item.id === inboxID) ?? -1;
189
+ const item = store.session.pending[sessionID]?.[index];
190
+ pendingUpdates.get(sessionID)?.set(inboxID, item ? { ...item, delivery } : delivery);
191
+ if (index < 0 || !item || item.delivery === delivery)
192
+ return;
193
+ setStore("session", "pending", sessionID, index, { ...item, delivery });
194
+ }
195
+ const outbox = new Set;
196
+ const sessionOutbox = new Set;
197
+ const creating = new Map;
198
+ const sending = new Map;
199
+ const messageLoads = new Map;
200
+ const compacting = new Map;
201
+ onCleanup(() => compacting.clear());
202
+ function track(map, key, promise) {
203
+ map.set(key, promise);
204
+ const settle = () => {
205
+ if (map.get(key) === promise)
206
+ map.delete(key);
207
+ };
208
+ promise.then(settle, settle);
209
+ }
210
+ function sendAdmission(sessionID, send, gate) {
211
+ const created = creating.get(sessionID);
212
+ const previous = sending.get(sessionID);
213
+ const request = Promise.resolve().then(() => Promise.all([gate, created, previous])).then(send);
214
+ track(sending, sessionID, request.catch(() => {
215
+ return;
216
+ }));
217
+ return request;
218
+ }
219
+ function admitLocal(item) {
220
+ batch(() => {
221
+ const pending = store.session.pending[item.sessionID] ?? [];
222
+ const at = pending.findIndex((entry) => entry.id === item.id);
223
+ setStore("session", "pending", item.sessionID, at < 0 ? [...pending, item] : pending.map((entry, index) => index === at ? item : entry));
224
+ if (item.type === "compaction")
225
+ return;
226
+ materializeInboxMessage(item);
227
+ });
228
+ }
229
+ function materializeInboxMessage(item) {
230
+ if (item.type !== "user" && item.type !== "synthetic")
231
+ return;
232
+ message.update(item.sessionID, (draft, index) => {
233
+ const row = item.type === "user" ? { id: item.id, type: "user", ...item.payload, time: { created: item.time.created } } : { id: item.id, type: "synthetic", ...item.payload, time: { created: item.time.created } };
234
+ const position = index.get(item.id);
235
+ if (position === undefined)
236
+ return message.append(draft, index, row);
237
+ draft[position] = row;
238
+ });
239
+ }
240
+ function retractLocal(sessionID, inboxID) {
241
+ batch(() => {
242
+ removePending(sessionID, inboxID);
243
+ if (!messageIndex.get(sessionID)?.has(inboxID))
244
+ return;
245
+ message.update(sessionID, (draft, index) => {
246
+ const position = index.get(inboxID);
247
+ if (position === undefined)
248
+ return;
249
+ draft.splice(position, 1);
250
+ index.delete(inboxID);
251
+ message.reindex(draft, index, position);
252
+ });
253
+ });
254
+ }
255
+ const message = {
256
+ update(sessionID, fn) {
257
+ setStore("session", "message", produce((draft) => {
258
+ fn(draft[sessionID] ??= [], index(sessionID));
259
+ }));
260
+ },
261
+ append(messages, index, item) {
262
+ if (index.has(item.id))
263
+ return;
264
+ index.set(item.id, messages.length);
265
+ messages.push(item);
266
+ },
267
+ insert(sessionID, item) {
268
+ message.update(sessionID, (draft, index) => message.append(draft, index, item));
269
+ },
270
+ editAssistant(sessionID, messageID, fn) {
271
+ message.update(sessionID, (draft, index) => {
272
+ const position = index.get(messageID);
273
+ const item = position === undefined ? undefined : draft[position];
274
+ if (item?.type === "assistant")
275
+ fn(item);
276
+ });
277
+ },
278
+ editTool(sessionID, messageID, toolID, fn) {
279
+ message.editAssistant(sessionID, messageID, (assistant) => {
280
+ const tool = assistant.content.findLast((item) => item.type === "tool" && item.id === toolID);
281
+ if (tool)
282
+ fn(tool);
283
+ });
284
+ },
285
+ editText(sessionID, messageID, fn) {
286
+ message.editAssistant(sessionID, messageID, (assistant) => {
287
+ const text = assistant.content.findLast((item) => item.type === "text");
288
+ if (text)
289
+ fn(text);
290
+ });
291
+ },
292
+ editReasoning(sessionID, messageID, fn) {
293
+ message.editAssistant(sessionID, messageID, (assistant) => {
294
+ const reasoning = assistant.content.findLast((item) => item.type === "reasoning" && !item.time?.completed);
295
+ if (reasoning)
296
+ fn(reasoning);
297
+ });
298
+ },
299
+ activeAssistant(messages) {
300
+ const item = messages.findLast((item) => item.type === "assistant" && !item.time.completed);
301
+ return item?.type === "assistant" ? item : undefined;
302
+ },
303
+ shell(messages, shellID) {
304
+ const item = messages.findLast((item) => item.type === "shell" && item.shellID === shellID);
305
+ return item?.type === "shell" ? item : undefined;
306
+ },
307
+ compaction(messages) {
308
+ const item = messages.findLast((item) => item.type === "compaction" && item.status === "running");
309
+ return item?.type === "compaction" ? item : undefined;
310
+ },
311
+ reindex(messages, index, start) {
312
+ for (let position = start;position < messages.length; position++) {
313
+ const item = messages[position];
314
+ if (item)
315
+ index.set(item.id, position);
316
+ }
317
+ }
318
+ };
319
+ function index(sessionID) {
320
+ const existing = messageIndex.get(sessionID);
321
+ if (existing)
322
+ return existing;
323
+ const created = new Map;
324
+ messageIndex.set(sessionID, created);
325
+ return created;
326
+ }
327
+ function resolveRoot(sessionID) {
328
+ let current = sessionID;
329
+ let parentID = store.session.info[sessionID]?.parentID;
330
+ const seen = new Set([sessionID]);
331
+ while (parentID) {
332
+ if (seen.has(parentID))
333
+ break;
334
+ seen.add(parentID);
335
+ current = parentID;
336
+ parentID = store.session.info[parentID]?.parentID;
337
+ }
338
+ return current;
339
+ }
340
+ function registerSession(sessionID) {
341
+ const info = store.session.info[sessionID];
342
+ if (!info)
343
+ return;
344
+ const rootID = resolveRoot(sessionID);
345
+ setStore("session", "family", produce((draft) => {
346
+ if (sessionID !== rootID && draft[sessionID]) {
347
+ const members = draft[rootID] ??= [];
348
+ for (const id of draft[sessionID]) {
349
+ if (!members.includes(id))
350
+ members.push(id);
351
+ }
352
+ delete draft[sessionID];
353
+ }
354
+ const family = draft[rootID] ??= [];
355
+ if (!family.includes(sessionID))
356
+ family.push(sessionID);
357
+ }));
358
+ }
359
+ function evictSession(sessionID) {
360
+ if (sessionOutbox.has(sessionID))
361
+ return;
362
+ sync.invalidate(`session.pending:${sessionID}`);
363
+ sync.invalidate(`session.message:${sessionID}`);
364
+ messageLoads.delete(sessionID);
365
+ const pending = store.session.pending[sessionID]?.filter((item) => outbox.has(item.id)) ?? [];
366
+ const messages = store.session.message[sessionID]?.filter((item) => outbox.has(item.id)) ?? [];
367
+ messageIndex.delete(sessionID);
368
+ if (messages.length)
369
+ messageIndex.set(sessionID, new Map(messages.map((item, index) => [item.id, index])));
370
+ setStore("session", produce((draft) => {
371
+ delete draft.message[sessionID];
372
+ delete draft.messageCursor[sessionID];
373
+ delete draft.messageLoading[sessionID];
374
+ delete draft.pending[sessionID];
375
+ if (messages.length)
376
+ draft.message[sessionID] = messages;
377
+ if (pending.length)
378
+ draft.pending[sessionID] = pending;
379
+ }));
380
+ }
381
+ function removeSession(sessionID) {
382
+ activeUpdates?.set(sessionID, undefined);
383
+ store.session.pending[sessionID]?.forEach((item) => outbox.delete(item.id));
384
+ messageIndex.delete(sessionID);
385
+ sync.invalidate(`session:${sessionID}`);
386
+ sync.invalidate(`session.family:${sessionID}`);
387
+ sync.invalidate(`session.pending:${sessionID}`);
388
+ sync.invalidate(`session.message:${sessionID}`);
389
+ sync.invalidate(`session.permission:${sessionID}`);
390
+ sync.invalidate(`session.form:${sessionID}:`);
391
+ setStore("session", produce((draft) => {
392
+ delete draft.info[sessionID];
393
+ delete draft.active[sessionID];
394
+ delete draft.message[sessionID];
395
+ delete draft.messageCursor[sessionID];
396
+ delete draft.messageLoading[sessionID];
397
+ delete draft.pending[sessionID];
398
+ delete draft.permission[sessionID];
399
+ delete draft.form[sessionID];
400
+ for (const [rootID, family] of Object.entries(draft.family)) {
401
+ const next = family.filter((id) => id !== sessionID);
402
+ if (next.length === 0)
403
+ delete draft.family[rootID];
404
+ else
405
+ draft.family[rootID] = next;
406
+ }
407
+ }));
408
+ }
409
+ function handleEvent(event) {
410
+ switch (event.type) {
411
+ case "server.connected": {
412
+ const updates = new Map;
413
+ activeUpdates = updates;
414
+ refresh(() => api().session.active().then((active) => {
415
+ if (activeUpdates !== updates)
416
+ return;
417
+ const snapshot = new Map(Object.keys(active).map((id) => [id, "running"]));
418
+ updates.forEach((status, id) => {
419
+ if (status === undefined)
420
+ return snapshot.delete(id);
421
+ snapshot.set(id, status);
422
+ });
423
+ activeUpdates = undefined;
424
+ setStore("session", "active", reconcile(Object.fromEntries(snapshot)));
425
+ }).catch(() => {
426
+ if (activeUpdates === updates)
427
+ activeUpdates = undefined;
428
+ }));
429
+ refresh(() => api().location.get({ location: locationQuery(defaultLocation()) }).then((location) => {
430
+ const key = locationKey2(location);
431
+ setStore("location", key, { info: location });
432
+ }));
433
+ refresh(() => result.location.vcs.sync());
434
+ refresh(() => result.project.sync());
435
+ return;
436
+ }
437
+ case "project.updated":
438
+ setStore("project", "info", event.data.id, reconcile(event.data));
439
+ return;
440
+ case "session.created":
441
+ sessionOutbox.delete(event.data.sessionID);
442
+ result.session.invalidate(event.data.sessionID);
443
+ refresh(() => result.session.sync(event.data.sessionID));
444
+ sync.complete(`session.pending:${event.data.sessionID}`);
445
+ sync.complete(`session.message:${event.data.sessionID}`);
446
+ return;
447
+ case "session.deleted":
448
+ removeSession(event.data.sessionID);
449
+ return;
450
+ case "session.usage.updated":
451
+ if (store.session.info[event.data.sessionID])
452
+ setStore("session", "info", event.data.sessionID, {
453
+ cost: event.data.cost,
454
+ tokens: event.data.tokens
455
+ });
456
+ return;
457
+ case "session.agent.selected": {
458
+ const previous = store.session.info[event.data.sessionID]?.agent;
459
+ if (store.session.info[event.data.sessionID])
460
+ setStore("session", "info", event.data.sessionID, "agent", event.data.agent);
461
+ message.insert(event.data.sessionID, {
462
+ id: messageIDFromEvent(event.id),
463
+ type: "agent-switched",
464
+ agent: event.data.agent,
465
+ previous,
466
+ time: { created: event.created }
467
+ });
468
+ return;
469
+ }
470
+ case "session.model.selected":
471
+ if (store.session.info[event.data.sessionID])
472
+ setStore("session", "info", event.data.sessionID, "model", event.data.model);
473
+ if (!store.session.message[event.data.sessionID])
474
+ return;
475
+ message.insert(event.data.sessionID, {
476
+ id: messageIDFromEvent(event.id),
477
+ type: "model-switched",
478
+ model: event.data.model,
479
+ time: { created: event.created }
480
+ });
481
+ refresh(() => api().session.message.get({ sessionID: event.data.sessionID, messageID: messageIDFromEvent(event.id) }).then((item) => {
482
+ message.update(event.data.sessionID, (draft, index) => {
483
+ const position = index.get(item.id);
484
+ if (position === undefined)
485
+ return message.append(draft, index, item);
486
+ draft[position] = item;
487
+ });
488
+ }));
489
+ return;
490
+ case "session.renamed": {
491
+ refresh(() => {
492
+ const family = sync.pending(`session.family:${event.data.sessionID}`) ? result.session.sync(event.data.sessionID, { children: true }) : Promise.resolve();
493
+ return Promise.all([result.session.sync(event.data.sessionID), family]).then(() => {
494
+ if (store.session.info[event.data.sessionID])
495
+ setStore("session", "info", event.data.sessionID, "title", event.data.title);
496
+ });
497
+ });
498
+ return;
499
+ }
500
+ case "session.permissions":
501
+ if (store.session.info[event.data.sessionID])
502
+ setStore("session", "info", event.data.sessionID, "permissions", event.data.permissions);
503
+ return;
504
+ case "session.moved": {
505
+ const current = store.session.info[event.data.sessionID];
506
+ if (current) {
507
+ const previous = {
508
+ location: { ...current.location },
509
+ projectID: current.projectID,
510
+ subpath: current.subpath
511
+ };
512
+ setStore("session", "info", event.data.sessionID, "location", event.data.location);
513
+ if (event.data.projectID)
514
+ setStore("session", "info", event.data.sessionID, "projectID", event.data.projectID);
515
+ setStore("session", "info", event.data.sessionID, "subpath", event.data.subpath);
516
+ message.insert(event.data.sessionID, {
517
+ id: messageIDFromEvent(event.id),
518
+ type: "location-switched",
519
+ location: event.data.location,
520
+ projectID: event.data.projectID,
521
+ subpath: event.data.subpath,
522
+ previous,
523
+ time: { created: event.created }
524
+ });
525
+ }
526
+ return;
527
+ }
528
+ case "worktree.resolved": {
529
+ for (const [sessionID, info] of Object.entries(store.session.info)) {
530
+ const explicit = event.data.adopted?.includes(info.projectID);
531
+ const directory = explicit ? store.project.info[info.projectID]?.canonical : info.location.directory;
532
+ if (!directory) {
533
+ result.session.invalidate(sessionID);
534
+ refresh(() => result.session.sync(sessionID));
535
+ continue;
536
+ }
537
+ const adopted = Worktree.adopt({
538
+ projectID: info.projectID,
539
+ directory
540
+ }, event.data);
541
+ if (!adopted)
542
+ continue;
543
+ setStore("session", "info", sessionID, "projectID", adopted.projectID);
544
+ setStore("session", "info", sessionID, "subpath", adopted.subpath);
545
+ }
546
+ return;
547
+ }
548
+ case "session.inbox.delivered": {
549
+ const admitted = result.session.input.has(event.data.sessionID, event.data.inboxID);
550
+ removePending(event.data.sessionID, event.data.inboxID);
551
+ message.update(event.data.sessionID, (draft, index) => {
552
+ const position = index.get(event.data.inboxID);
553
+ if (position === undefined)
554
+ return;
555
+ const existing = draft[position];
556
+ if (!existing || !admitted)
557
+ return;
558
+ existing.time.created = event.created;
559
+ draft.splice(position, 1);
560
+ draft.push(existing);
561
+ message.reindex(draft, index, position);
562
+ });
563
+ compacting.get(event.data.sessionID)?.observed.add(event.data.inboxID);
564
+ return;
565
+ }
566
+ case "session.inbox.delivery.changed":
567
+ updatePending(event.data.sessionID, event.data.inboxID, event.data.delivery);
568
+ return;
569
+ case "session.inbox.cancelled": {
570
+ retractLocal(event.data.sessionID, event.data.inboxID);
571
+ compacting.get(event.data.sessionID)?.observed.add(event.data.inboxID);
572
+ return;
573
+ }
574
+ case "session.inbox.enqueued": {
575
+ outbox.delete(event.data.inboxID);
576
+ const item = {
577
+ id: event.data.inboxID,
578
+ sessionID: event.data.sessionID,
579
+ time: { created: event.created },
580
+ ...event.data.item
581
+ };
582
+ pendingUpdates.get(item.sessionID)?.set(item.id, item);
583
+ admitLocal(item);
584
+ if (event.data.item.type === "compaction") {
585
+ const active = compacting.get(event.data.sessionID);
586
+ active?.observed.add(event.data.inboxID);
587
+ if (active && active.id !== event.data.inboxID && outbox.delete(active.id))
588
+ removePending(event.data.sessionID, active.id);
589
+ }
590
+ return;
591
+ }
592
+ case "session.instructions.updated":
593
+ const updateText = event.data.text;
594
+ if (updateText === undefined)
595
+ return;
596
+ message.insert(event.data.sessionID, {
597
+ id: messageIDFromEvent(event.id),
598
+ type: "system",
599
+ text: updateText,
600
+ description: `Instructions updated: ${Object.keys(event.data.delta).join(", ")}`,
601
+ metadata: event.metadata,
602
+ time: { created: event.created }
603
+ });
604
+ return;
605
+ case "session.synthetic":
606
+ message.insert(event.data.sessionID, {
607
+ id: messageIDFromEvent(event.id),
608
+ type: "synthetic",
609
+ text: event.data.text,
610
+ description: event.data.description,
611
+ metadata: event.data.metadata,
612
+ time: { created: event.created }
613
+ });
614
+ return;
615
+ case "session.shell.started":
616
+ message.insert(event.data.sessionID, {
617
+ id: messageIDFromEvent(event.id),
618
+ type: "shell",
619
+ shellID: event.data.shell.id,
620
+ command: event.data.shell.command,
621
+ status: event.data.shell.status,
622
+ exit: event.data.shell.exit,
623
+ metadata: event.data.shell.metadata.background === true ? { ...event.metadata, background: true } : event.metadata,
624
+ time: { created: event.created }
625
+ });
626
+ return;
627
+ case "session.shell.ended":
628
+ message.update(event.data.sessionID, (draft) => {
629
+ const match = message.shell(draft, event.data.shell.id);
630
+ if (!match)
631
+ return;
632
+ match.status = event.data.shell.status;
633
+ match.exit = event.data.shell.exit;
634
+ match.output = event.data.output;
635
+ match.time.completed = event.created;
636
+ });
637
+ return;
638
+ case "session.step.started":
639
+ message.update(event.data.sessionID, (draft, index) => {
640
+ const position = index.get(event.data.assistantMessageID);
641
+ const existing = position === undefined ? undefined : draft[position];
642
+ if (existing?.type === "assistant") {
643
+ existing.agent = event.data.agent;
644
+ existing.model = event.data.model;
645
+ existing.retry = undefined;
646
+ existing.error = undefined;
647
+ existing.finish = undefined;
648
+ existing.rawFinish = undefined;
649
+ existing.providerState = undefined;
650
+ existing.time.created = event.data.started;
651
+ existing.time.streamed = undefined;
652
+ existing.time.completed = undefined;
653
+ if (event.data.snapshot)
654
+ existing.snapshot = { ...existing.snapshot, start: event.data.snapshot };
655
+ return;
656
+ }
657
+ const currentAssistant = message.activeAssistant(draft);
658
+ if (currentAssistant) {
659
+ currentAssistant.retry = undefined;
660
+ currentAssistant.time.completed = event.created;
661
+ }
662
+ message.append(draft, index, {
663
+ id: event.data.assistantMessageID,
664
+ type: "assistant",
665
+ agent: event.data.agent,
666
+ model: event.data.model,
667
+ metadata: event.metadata,
668
+ content: [],
669
+ snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined,
670
+ time: { created: event.data.started }
671
+ });
672
+ });
673
+ return;
674
+ case "session.step.streamed":
675
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
676
+ assistant.time.streamed = event.created;
677
+ });
678
+ return;
679
+ case "session.step.ended": {
680
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
681
+ assistant.time.completed = event.created;
682
+ assistant.finish = event.data.finish;
683
+ assistant.rawFinish = event.data.rawFinish;
684
+ assistant.providerState = event.data.providerState;
685
+ assistant.cost = event.data.cost;
686
+ assistant.tokens = event.data.tokens;
687
+ if (event.data.snapshot)
688
+ assistant.snapshot = { ...assistant.snapshot, end: event.data.snapshot };
689
+ });
690
+ return;
691
+ }
692
+ case "session.step.failed":
693
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
694
+ assistant.time.completed = event.created;
695
+ assistant.finish = event.data.finish ?? "error";
696
+ assistant.rawFinish = event.data.rawFinish;
697
+ assistant.providerState = event.data.providerState;
698
+ assistant.error = event.data.error;
699
+ assistant.retry = undefined;
700
+ if (event.data.cost !== undefined && event.data.tokens !== undefined) {
701
+ assistant.cost = event.data.cost;
702
+ assistant.tokens = event.data.tokens;
703
+ }
704
+ });
705
+ return;
706
+ case "session.text.started":
707
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
708
+ assistant.content.push({ type: "text", text: "" });
709
+ });
710
+ return;
711
+ case "session.text.delta":
712
+ message.editText(event.data.sessionID, event.data.assistantMessageID, (text) => {
713
+ text.text += event.data.delta;
714
+ });
715
+ return;
716
+ case "session.text.ended":
717
+ message.editText(event.data.sessionID, event.data.assistantMessageID, (text) => {
718
+ text.text = event.data.text;
719
+ });
720
+ return;
721
+ case "session.tool.input.started":
722
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
723
+ assistant.content.push({
724
+ type: "tool",
725
+ id: event.data.id,
726
+ name: event.data.name,
727
+ time: { created: event.created },
728
+ state: { status: "streaming", input: "" }
729
+ });
730
+ });
731
+ return;
732
+ case "session.tool.input.delta":
733
+ message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
734
+ if (tool.state.status === "streaming")
735
+ tool.state.input += event.data.delta;
736
+ });
737
+ return;
738
+ case "session.tool.input.ended":
739
+ message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
740
+ if (tool.state.status === "streaming")
741
+ tool.state.input = event.data.text;
742
+ });
743
+ return;
744
+ case "session.tool.called":
745
+ message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
746
+ tool.time.ran = event.created;
747
+ tool.executed = event.data.executed;
748
+ tool.providerState = event.data.state;
749
+ tool.state = { status: "running", input: event.data.input, metadata: {} };
750
+ });
751
+ return;
752
+ case "session.tool.progress":
753
+ message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
754
+ if (tool.state.status === "running")
755
+ tool.state.metadata = event.data.metadata;
756
+ });
757
+ return;
758
+ case "session.tool.success":
759
+ message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
760
+ if (tool.state.status !== "running")
761
+ return;
762
+ tool.state = {
763
+ status: "completed",
764
+ input: tool.state.input,
765
+ metadata: event.data.metadata,
766
+ content: [...event.data.content]
767
+ };
768
+ tool.executed = event.data.executed || tool.executed === true;
769
+ tool.providerResultState = event.data.resultState;
770
+ tool.time.completed = event.created;
771
+ });
772
+ return;
773
+ case "session.tool.failed":
774
+ message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
775
+ if (tool.state.status !== "streaming" && tool.state.status !== "running")
776
+ return;
777
+ tool.state = {
778
+ status: "error",
779
+ error: event.data.error,
780
+ input: typeof tool.state.input === "string" ? {} : tool.state.input,
781
+ metadata: event.data.metadata,
782
+ content: event.data.content
783
+ };
784
+ tool.executed = event.data.executed || tool.executed === true;
785
+ tool.providerResultState = event.data.resultState;
786
+ tool.time.completed = event.created;
787
+ });
788
+ return;
789
+ case "session.reasoning.started":
790
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
791
+ assistant.content.push({
792
+ type: "reasoning",
793
+ text: "",
794
+ state: event.data.state,
795
+ time: { created: event.created }
796
+ });
797
+ });
798
+ return;
799
+ case "session.reasoning.delta":
800
+ message.editReasoning(event.data.sessionID, event.data.assistantMessageID, (reasoning) => {
801
+ reasoning.text += event.data.delta;
802
+ });
803
+ return;
804
+ case "session.reasoning.ended":
805
+ message.editReasoning(event.data.sessionID, event.data.assistantMessageID, (reasoning) => {
806
+ reasoning.text = event.data.text;
807
+ reasoning.time = { created: reasoning.time?.created ?? event.created, completed: event.created };
808
+ if (event.data.state !== undefined)
809
+ reasoning.state = event.data.state;
810
+ });
811
+ return;
812
+ case "session.retry.scheduled":
813
+ message.editAssistant(event.data.sessionID, event.data.assistantMessageID, (assistant) => {
814
+ assistant.retry = { attempt: event.data.attempt, at: event.data.at, error: event.data.error };
815
+ });
816
+ return;
817
+ case "session.execution.started":
818
+ setSessionActive(event.data.sessionID, "running");
819
+ return;
820
+ case "session.compaction.started":
821
+ if (event.data.inputID)
822
+ removePending(event.data.sessionID, event.data.inputID);
823
+ message.insert(event.data.sessionID, {
824
+ id: event.data.inputID ?? messageIDFromEvent(event.id),
825
+ type: "compaction",
826
+ status: "running",
827
+ reason: event.data.reason,
828
+ summary: "",
829
+ recent: event.data.recent ?? "",
830
+ time: { created: event.created }
831
+ });
832
+ if (event.data.inputID)
833
+ compacting.get(event.data.sessionID)?.observed.add(event.data.inputID);
834
+ return;
835
+ case "session.execution.succeeded":
836
+ case "session.execution.failed":
837
+ case "session.execution.interrupted":
838
+ setSessionActive(event.data.sessionID, "idle");
839
+ message.update(event.data.sessionID, (draft) => {
840
+ const currentAssistant = message.activeAssistant(draft);
841
+ if (currentAssistant)
842
+ currentAssistant.retry = undefined;
843
+ });
844
+ if (event.type === "session.execution.interrupted" && event.data.reason === "shutdown")
845
+ return;
846
+ message.insert(event.data.sessionID, {
847
+ id: messageIDFromEvent(event.id),
848
+ type: "idle",
849
+ outcome: event.type === "session.execution.succeeded" ? "succeeded" : event.type === "session.execution.failed" ? "failed" : "interrupted",
850
+ ...event.type === "session.execution.failed" ? { error: event.data.error } : {},
851
+ time: { created: event.created }
852
+ });
853
+ if (store.session.message[event.data.sessionID]?.some((item) => item.type === "assistant" && item.content.some((part) => part.type === "tool" && (part.state.status === "streaming" || part.state.status === "running")))) {
854
+ sync.invalidate(`session.message:${event.data.sessionID}`);
855
+ refresh(() => result.session.message.sync(event.data.sessionID));
856
+ }
857
+ if (!store.session.info[event.data.sessionID] && !sync.has(`session:${event.data.sessionID}`))
858
+ return;
859
+ result.session.invalidate(event.data.sessionID);
860
+ refresh(() => result.session.sync(event.data.sessionID));
861
+ return;
862
+ case "session.viewed":
863
+ if (!store.session.info[event.data.sessionID] && !sync.has(`session:${event.data.sessionID}`))
864
+ return;
865
+ result.session.invalidate(event.data.sessionID);
866
+ refresh(() => result.session.sync(event.data.sessionID));
867
+ return;
868
+ case "session.revert.staged":
869
+ if (store.session.info[event.data.sessionID])
870
+ setStore("session", "info", event.data.sessionID, "revert", event.data.revert);
871
+ return;
872
+ case "session.revert.cleared":
873
+ if (store.session.info[event.data.sessionID])
874
+ setStore("session", "info", event.data.sessionID, "revert", undefined);
875
+ return;
876
+ case "session.revert.committed":
877
+ if (store.session.info[event.data.sessionID]) {
878
+ setStore("session", "info", event.data.sessionID, "revert", undefined);
879
+ }
880
+ setStore("session", "pending", event.data.sessionID, (store.session.pending[event.data.sessionID] ?? []).filter((item) => item.id < event.data.to));
881
+ message.update(event.data.sessionID, (draft, index) => {
882
+ const position = draft.findIndex((item) => item.id >= event.data.to);
883
+ if (position === -1)
884
+ return;
885
+ for (const item of draft.splice(position))
886
+ index.delete(item.id);
887
+ });
888
+ return;
889
+ case "session.compaction.delta":
890
+ message.update(event.data.sessionID, (draft) => {
891
+ const current = message.compaction(draft);
892
+ if (current?.status === "running")
893
+ current.summary += event.data.text;
894
+ });
895
+ return;
896
+ case "session.compaction.ended":
897
+ message.update(event.data.sessionID, (draft, index) => {
898
+ const position = draft.findLastIndex((item) => item.type === "compaction" && item.status === "running");
899
+ const current = draft[position];
900
+ if (current?.type === "compaction") {
901
+ Object.assign(current, {
902
+ status: "completed",
903
+ reason: event.data.reason,
904
+ model: event.data.model,
905
+ providerState: event.data.providerState,
906
+ providerContext: event.data.providerContext,
907
+ summary: event.data.text,
908
+ recent: event.data.recent,
909
+ cost: event.data.cost,
910
+ tokens: event.data.tokens
911
+ });
912
+ return;
913
+ }
914
+ message.append(draft, index, {
915
+ id: messageIDFromEvent(event.id),
916
+ type: "compaction",
917
+ status: "completed",
918
+ reason: event.data.reason,
919
+ model: event.data.model,
920
+ providerState: event.data.providerState,
921
+ providerContext: event.data.providerContext,
922
+ summary: event.data.text,
923
+ recent: event.data.recent,
924
+ cost: event.data.cost,
925
+ tokens: event.data.tokens,
926
+ time: { created: event.created }
927
+ });
928
+ });
929
+ return;
930
+ case "session.compaction.failed":
931
+ if (event.data.inputID)
932
+ removePending(event.data.sessionID, event.data.inputID);
933
+ message.update(event.data.sessionID, (draft, index) => {
934
+ const position = draft.findLastIndex((item) => item.type === "compaction" && item.status === "running");
935
+ const current = draft[position];
936
+ const failed = {
937
+ id: current?.id ?? event.data.inputID ?? messageIDFromEvent(event.id),
938
+ type: "compaction",
939
+ status: "failed",
940
+ reason: event.data.reason ?? "manual",
941
+ error: event.data.error ?? {
942
+ type: "compaction.failed",
943
+ message: "Compaction failed before recording an error"
944
+ },
945
+ metadata: current?.type === "compaction" ? current.metadata : event.metadata,
946
+ cost: event.data.cost,
947
+ tokens: event.data.tokens,
948
+ time: current?.type === "compaction" ? current.time : { created: event.created }
949
+ };
950
+ if (current?.type === "compaction") {
951
+ draft[position] = failed;
952
+ return;
953
+ }
954
+ message.append(draft, index, failed);
955
+ });
956
+ if (event.data.inputID)
957
+ compacting.get(event.data.sessionID)?.observed.add(event.data.inputID);
958
+ return;
959
+ case "permission.asked":
960
+ if (store.session.permission[event.data.sessionID]?.some((request) => request.id === event.data.id))
961
+ return;
962
+ setStore("session", "permission", event.data.sessionID, [
963
+ ...store.session.permission[event.data.sessionID] ?? [],
964
+ event.data
965
+ ]);
966
+ return;
967
+ case "permission.replied":
968
+ removePermission(event.data.sessionID, event.data.requestID);
969
+ return;
970
+ case "form.replied":
971
+ case "form.cancelled":
972
+ removeForm(event.data.sessionID, event.data.id, event.location);
973
+ return;
974
+ }
975
+ if (event.type === "credential.updated" || event.type === "credential.switched") {
976
+ Object.keys(store.location).forEach((key) => {
977
+ const ref = JSON.parse(key);
978
+ const location = { directory: ref[0], workspaceID: ref[1] ?? undefined };
979
+ if (event.type === "credential.updated") {
980
+ result.location.integration.invalidate(location);
981
+ refresh(() => result.location.integration.sync(location));
982
+ return;
983
+ }
984
+ setStore("location", key, (data) => ({
985
+ integration: data?.integration?.map((integration) => {
986
+ if (integration.id !== event.data.integrationID)
987
+ return integration;
988
+ const active = integration.connections.find((connection) => connection.type === "credential" && connection.id === event.data.credentialID);
989
+ if (!active)
990
+ return integration;
991
+ return {
992
+ ...integration,
993
+ connections: [active, ...integration.connections.filter((connection) => connection !== active)]
994
+ };
995
+ })
996
+ }));
997
+ result.location.model.invalidate(location);
998
+ result.location.provider.invalidate(location);
999
+ refresh(() => Promise.all([result.location.model.sync(location), result.location.provider.sync(location)]));
1000
+ });
1001
+ return;
1002
+ }
1003
+ if (!event.location)
1004
+ return;
1005
+ const location = event.location;
1006
+ switch (event.type) {
1007
+ case "provider.updated":
1008
+ result.location.provider.invalidate(location);
1009
+ refresh(() => result.location.provider.sync(location));
1010
+ break;
1011
+ case "model.updated":
1012
+ result.location.model.invalidate(location);
1013
+ refresh(() => result.location.model.sync(location));
1014
+ break;
1015
+ case "agent.updated":
1016
+ result.location.agent.invalidate(location);
1017
+ refresh(() => result.location.agent.sync(location));
1018
+ break;
1019
+ case "command.updated":
1020
+ result.location.command.invalidate(location);
1021
+ refresh(() => result.location.command.sync(location));
1022
+ break;
1023
+ case "skill.updated":
1024
+ result.location.skill.invalidate(location);
1025
+ refresh(() => result.location.skill.sync(location));
1026
+ break;
1027
+ case "vcs.branch.updated":
1028
+ setStore("location", locationKey2(location), (data) => ({
1029
+ vcs: {
1030
+ branch: {
1031
+ ...data?.vcs?.branch,
1032
+ current: event.data.branch
1033
+ }
1034
+ }
1035
+ }));
1036
+ break;
1037
+ case "form.created":
1038
+ if (store.session.form[event.data.form.sessionID]?.some((form) => form.id === event.data.form.id))
1039
+ break;
1040
+ setStore("session", "form", event.data.form.sessionID, [
1041
+ ...store.session.form[event.data.form.sessionID] ?? [],
1042
+ event.data.form.sessionID === "global" ? { ...event.data.form, location } : event.data.form
1043
+ ]);
1044
+ break;
1045
+ case "shell.created":
1046
+ setStore("location", locationKey2(location), (data) => ({
1047
+ shell: {
1048
+ ...data?.shell,
1049
+ [event.data.info.id]: { ...event.data.info, location }
1050
+ }
1051
+ }));
1052
+ break;
1053
+ case "shell.exited":
1054
+ case "shell.deleted":
1055
+ setStore("location", locationKey2(location), (data) => ({
1056
+ shell: Object.fromEntries(Object.entries(data?.shell ?? {}).filter(([id]) => id !== event.data.id))
1057
+ }));
1058
+ break;
1059
+ case "reference.updated":
1060
+ result.location.reference.invalidate(location);
1061
+ refresh(() => result.location.reference.sync(location));
1062
+ break;
1063
+ case "integration.updated":
1064
+ result.location.integration.invalidate(location);
1065
+ result.location.model.invalidate(location);
1066
+ result.location.provider.invalidate(location);
1067
+ refresh(() => Promise.all([
1068
+ result.location.integration.sync(location),
1069
+ result.location.model.sync(location),
1070
+ result.location.provider.sync(location)
1071
+ ]));
1072
+ break;
1073
+ case "config.updated":
1074
+ result.location.config.invalidate(location);
1075
+ if (result.location.config.list(location) !== undefined || sync.has(`location.config:${locationKey2(location)}`))
1076
+ refresh(() => result.location.config.sync(location));
1077
+ refresh(() => result.location.websearch.refresh(location));
1078
+ break;
1079
+ case "websearch.updated":
1080
+ refresh(() => result.location.websearch.refresh(location));
1081
+ break;
1082
+ case "mcp.status.changed":
1083
+ result.location.mcp.server.invalidate(location);
1084
+ settle(`mcp.status:${locationKey2(location)}`, () => result.location.mcp.server.sync(location));
1085
+ break;
1086
+ case "mcp.resources.changed":
1087
+ result.location.mcp.resource.invalidate(location);
1088
+ refresh(() => result.location.mcp.resource.sync(location));
1089
+ break;
1090
+ }
1091
+ }
1092
+ function locationResource(field, load, options) {
1093
+ const publish = (key, value) => setStore("location", key, { [field]: value });
1094
+ return {
1095
+ list: (ref) => store.location[locationKey2(ref ?? defaultLocation())]?.[field],
1096
+ sync: (ref) => {
1097
+ const location = ref ?? defaultLocation();
1098
+ const id = locationKey2(location);
1099
+ return sync.run(`location.${field}:${id}`, async () => {
1100
+ const response = await load(locationQuery(location));
1101
+ const key = locationKey2(response.location);
1102
+ publish(key, response.data);
1103
+ if (options?.alias && key !== id)
1104
+ publish(id, response.data);
1105
+ });
1106
+ },
1107
+ invalidate: (ref) => sync.invalidate(`location.${field}:${locationKey2(ref ?? defaultLocation())}`)
1108
+ };
1109
+ }
1110
+ const vcs = locationResource("vcs", (location) => api().vcs.get({ location }));
1111
+ const shells = locationResource("shell", async (location) => {
1112
+ const response = await api().shell.list({ location });
1113
+ const ref = { directory: response.location.directory };
1114
+ return {
1115
+ location: response.location,
1116
+ data: Object.fromEntries(response.data.map((info) => [info.id, { ...info, location: ref }]))
1117
+ };
1118
+ });
1119
+ const result = {
1120
+ on: config.event.on,
1121
+ listen: config.event.listen,
1122
+ session: {
1123
+ list() {
1124
+ return sessions();
1125
+ },
1126
+ get(sessionID) {
1127
+ return store.session.info[sessionID];
1128
+ },
1129
+ creating(sessionID) {
1130
+ return creating.has(sessionID);
1131
+ },
1132
+ remember(info) {
1133
+ batch(() => {
1134
+ setStore("session", "info", info.id, reconcile(info));
1135
+ sync.complete(`session:${info.id}`);
1136
+ registerSession(info.id);
1137
+ });
1138
+ },
1139
+ setStatus(sessionID, status) {
1140
+ setSessionActive(sessionID, status);
1141
+ },
1142
+ root(sessionID) {
1143
+ return resolveRoot(sessionID);
1144
+ },
1145
+ family(sessionID) {
1146
+ return store.session.family[resolveRoot(sessionID)] ?? [];
1147
+ },
1148
+ evict(sessionID) {
1149
+ const root = resolveRoot(sessionID);
1150
+ batch(() => {
1151
+ for (const id of new Set([root, sessionID, ...store.session.family[root] ?? []]))
1152
+ evictSession(id);
1153
+ });
1154
+ },
1155
+ cost(sessionID) {
1156
+ const session = store.session.info[sessionID];
1157
+ if (!session)
1158
+ return 0;
1159
+ if (session.parentID)
1160
+ return session.cost;
1161
+ return (store.session.family[sessionID] ?? [sessionID]).reduce((total, id) => total + (store.session.info[id]?.cost ?? 0), 0);
1162
+ },
1163
+ status(sessionID) {
1164
+ return store.session.active[sessionID] ?? "idle";
1165
+ },
1166
+ active() {
1167
+ return Object.entries(store.session.active).flatMap(([sessionID, status]) => status === "running" ? [sessionID] : []);
1168
+ },
1169
+ input: {
1170
+ list(sessionID) {
1171
+ return (store.session.pending[sessionID] ?? []).flatMap((item) => item.type === "compaction" ? [] : [item.id]);
1172
+ },
1173
+ has(sessionID, inboxID) {
1174
+ return store.session.pending[sessionID]?.some((item) => item.id === inboxID && item.type !== "compaction") ?? false;
1175
+ }
1176
+ },
1177
+ pending: {
1178
+ list(sessionID) {
1179
+ return store.session.pending[sessionID] ?? [];
1180
+ },
1181
+ status(sessionID, inboxID) {
1182
+ const pending = store.session.pending[sessionID] ?? [];
1183
+ const item = pending.find((entry) => entry.id === inboxID && entry.type === "user");
1184
+ if (!item)
1185
+ return;
1186
+ if (item.delivery === "queue")
1187
+ return "queued";
1188
+ const messages = store.session.message[sessionID] ?? [];
1189
+ const boundary = messages.findLastIndex((entry) => entry.type === "idle");
1190
+ if ((store.session.active[sessionID] ?? "idle") === "idle")
1191
+ return boundary >= 0 && item.time.created <= messages[boundary].time.created ? "stranded" : "starting";
1192
+ return messages.slice(boundary + 1).some((entry) => entry.type === "user" && !pending.some((other) => other.id === entry.id)) ? "steering" : "starting";
1193
+ },
1194
+ sync(sessionID) {
1195
+ return sync.run(`session.pending:${sessionID}`, async () => {
1196
+ const updates = new Map;
1197
+ pendingUpdates.set(sessionID, updates);
1198
+ try {
1199
+ const snapshot = await api().session.inbox.list({ sessionID });
1200
+ const current = new Map(snapshot.map((item) => [item.id, item]));
1201
+ updates.forEach((item, id) => {
1202
+ if (item === undefined)
1203
+ current.delete(id);
1204
+ else if (typeof item === "string") {
1205
+ const existing = current.get(id);
1206
+ if (existing)
1207
+ current.set(id, { ...existing, delivery: item });
1208
+ } else
1209
+ current.set(id, item);
1210
+ });
1211
+ const pending = [...current.values()];
1212
+ pending.forEach((item) => outbox.delete(item.id));
1213
+ if (pending.some((item) => item.type === "compaction"))
1214
+ store.session.pending[sessionID]?.filter((item) => item.type === "compaction").forEach((item) => outbox.delete(item.id));
1215
+ const inflight = (store.session.pending[sessionID] ?? []).filter((item) => outbox.has(item.id));
1216
+ const merged = inflight.length === 0 ? pending : [...pending, ...inflight];
1217
+ batch(() => {
1218
+ setStore("session", "pending", sessionID, reconcile(merged));
1219
+ merged.forEach(materializeInboxMessage);
1220
+ });
1221
+ } finally {
1222
+ if (pendingUpdates.get(sessionID) === updates)
1223
+ pendingUpdates.delete(sessionID);
1224
+ }
1225
+ });
1226
+ },
1227
+ invalidate(sessionID) {
1228
+ sync.invalidate(`session.pending:${sessionID}`);
1229
+ }
1230
+ },
1231
+ create(input) {
1232
+ const { projectID, ...payload } = input;
1233
+ const id = payload.id ?? SessionID.create();
1234
+ const location = payload.location ?? defaultLocation();
1235
+ const fresh = !store.session.info[id];
1236
+ if (fresh) {
1237
+ const now = Date.now();
1238
+ sessionOutbox.add(id);
1239
+ result.session.remember({
1240
+ id,
1241
+ projectID: projectID ?? store.location[locationKey2(location)]?.info?.project.id ?? "",
1242
+ agent: payload.agent,
1243
+ model: payload.model,
1244
+ cost: 0,
1245
+ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
1246
+ time: { created: now, updated: now },
1247
+ title: payload.title,
1248
+ location
1249
+ });
1250
+ sync.complete(`session.family:${id}`);
1251
+ sync.complete(`session.pending:${id}`);
1252
+ sync.complete(`session.message:${id}`);
1253
+ }
1254
+ const request = Promise.resolve().then(() => api().session.create({ ...payload, id, location })).then((info) => {
1255
+ sessionOutbox.delete(id);
1256
+ result.session.remember(info);
1257
+ return info;
1258
+ }).catch((error) => {
1259
+ if (fresh && sessionOutbox.delete(id))
1260
+ removeSession(id);
1261
+ throw error;
1262
+ });
1263
+ if (fresh)
1264
+ track(creating, id, request);
1265
+ return { id, request };
1266
+ },
1267
+ compact(input) {
1268
+ const active = compacting.get(input.sessionID);
1269
+ if (active)
1270
+ return active.request;
1271
+ const id = SessionMessage.ID.create();
1272
+ if (!store.session.pending[input.sessionID]?.some((item) => item.type === "compaction")) {
1273
+ outbox.add(id);
1274
+ admitLocal({
1275
+ id,
1276
+ sessionID: input.sessionID,
1277
+ time: { created: Date.now() },
1278
+ type: "compaction",
1279
+ delivery: "steer",
1280
+ payload: {}
1281
+ });
1282
+ }
1283
+ const observed = new Set;
1284
+ const request = sendAdmission(input.sessionID, async () => {
1285
+ if (input.model)
1286
+ await api().session.switchModel({ sessionID: input.sessionID, model: input.model });
1287
+ return api().session.compact({ sessionID: input.sessionID, id });
1288
+ }).then((item) => {
1289
+ batch(() => {
1290
+ outbox.delete(id);
1291
+ if (item.id !== id)
1292
+ removePending(input.sessionID, id);
1293
+ if (!observed.has(item.id) && !messageIndex.get(input.sessionID)?.has(item.id))
1294
+ admitLocal(item);
1295
+ });
1296
+ return item;
1297
+ }).catch((error) => {
1298
+ if (outbox.delete(id))
1299
+ removePending(input.sessionID, id);
1300
+ throw error;
1301
+ }).finally(() => {
1302
+ if (compacting.get(input.sessionID)?.request === request)
1303
+ compacting.delete(input.sessionID);
1304
+ });
1305
+ compacting.set(input.sessionID, { id, observed, request });
1306
+ return request;
1307
+ },
1308
+ prompt(input) {
1309
+ const { gate, prepare, ...request } = input;
1310
+ const id = request.id ?? SessionMessage.ID.create();
1311
+ const fresh = !messageIndex.get(request.sessionID)?.has(id) && !store.session.pending[request.sessionID]?.some((item) => item.id === id);
1312
+ if (fresh) {
1313
+ outbox.add(id);
1314
+ admitLocal({
1315
+ id,
1316
+ sessionID: request.sessionID,
1317
+ time: { created: Date.now() },
1318
+ type: "user",
1319
+ delivery: request.delivery ?? "steer",
1320
+ payload: {
1321
+ text: request.text,
1322
+ agents: request.agents?.map((agent) => ({ ...agent })),
1323
+ metadata: request.metadata
1324
+ }
1325
+ });
1326
+ }
1327
+ return sendAdmission(request.sessionID, async () => {
1328
+ await prepare?.();
1329
+ return api().session.prompt({ ...request, id });
1330
+ }, gate).catch((error) => {
1331
+ if (fresh && outbox.delete(id))
1332
+ retractLocal(request.sessionID, id);
1333
+ throw error;
1334
+ });
1335
+ },
1336
+ sync(sessionID, options) {
1337
+ return sync.run(options?.children ? `session.family:${sessionID}` : `session:${sessionID}`, async () => {
1338
+ const [info, children] = await Promise.all([
1339
+ api().session.get({ sessionID }),
1340
+ options?.children ? api().session.list({ parentID: sessionID, order: "desc" }).then((response) => response.data) : []
1341
+ ]);
1342
+ const sessions = [info, ...children];
1343
+ batch(() => {
1344
+ setStore("session", "info", produce((draft) => {
1345
+ for (const session of sessions)
1346
+ draft[session.id] = session;
1347
+ }));
1348
+ for (const session of sessions) {
1349
+ sync.complete(`session:${session.id}`);
1350
+ registerSession(session.id);
1351
+ }
1352
+ });
1353
+ });
1354
+ },
1355
+ invalidate(sessionID) {
1356
+ sync.invalidate(`session:${sessionID}`);
1357
+ },
1358
+ message: {
1359
+ list(sessionID) {
1360
+ return store.session.message[sessionID] ?? [];
1361
+ },
1362
+ get(sessionID, messageID) {
1363
+ const messages = store.session.message[sessionID];
1364
+ const position = messageIndex.get(sessionID)?.get(messageID);
1365
+ return position === undefined ? undefined : messages?.[position];
1366
+ },
1367
+ sync(sessionID) {
1368
+ return sync.run(`session.message:${sessionID}`, async () => {
1369
+ const response = await api().message.list({
1370
+ sessionID,
1371
+ limit: config.initialMessageLimit?.() ?? messagePageLimit,
1372
+ order: "desc"
1373
+ });
1374
+ const fetched = response.data.toReversed();
1375
+ const ids = new Set(fetched.map((item) => item.id));
1376
+ const admitted = new Set((store.session.pending[sessionID] ?? []).flatMap((item) => item.type === "user" || item.type === "synthetic" ? [item.id] : []));
1377
+ const local = (store.session.message[sessionID] ?? []).filter((item) => !ids.has(item.id) && (outbox.has(item.id) || admitted.has(item.id)));
1378
+ const messages = local.length === 0 ? fetched : [...fetched, ...local];
1379
+ batch(() => {
1380
+ messageIndex.set(sessionID, new Map(messages.map((message, index) => [message.id, index])));
1381
+ setStore("session", "message", sessionID, reconcile(messages));
1382
+ setStore("session", "messageCursor", sessionID, response.cursor.next ?? undefined);
1383
+ });
1384
+ });
1385
+ },
1386
+ more(sessionID) {
1387
+ return store.session.messageCursor[sessionID] !== undefined;
1388
+ },
1389
+ loading(sessionID) {
1390
+ return store.session.messageLoading[sessionID] ?? false;
1391
+ },
1392
+ async loadMore(sessionID, options) {
1393
+ const signal = options?.signal;
1394
+ if (signal?.aborted)
1395
+ return;
1396
+ while (messageLoads.has(sessionID)) {
1397
+ const published = await (() => {
1398
+ const pending = messageLoads.get(sessionID);
1399
+ if (!signal)
1400
+ return pending;
1401
+ const aborted = Promise.withResolvers();
1402
+ const cancel = () => aborted.resolve();
1403
+ signal.addEventListener("abort", cancel, { once: true });
1404
+ return Promise.race([pending, aborted.promise]).catch((error) => {
1405
+ if (!signal.aborted)
1406
+ throw error;
1407
+ }).finally(() => signal.removeEventListener("abort", cancel));
1408
+ })();
1409
+ if (!options?.all && published || signal?.aborted)
1410
+ return;
1411
+ }
1412
+ const cursor = store.session.messageCursor[sessionID];
1413
+ if (!cursor || signal?.aborted)
1414
+ return;
1415
+ setStore("session", "messageLoading", sessionID, true);
1416
+ const request = (async () => {
1417
+ const fetched = [];
1418
+ let next = cursor;
1419
+ do {
1420
+ const response = await api().message.list({
1421
+ sessionID,
1422
+ limit: options?.all ? 200 : messagePageLimit,
1423
+ cursor: next
1424
+ }, { signal });
1425
+ if (signal?.aborted)
1426
+ return;
1427
+ fetched.push(...response.data);
1428
+ next = response.cursor.next ?? undefined;
1429
+ if (!options?.all)
1430
+ break;
1431
+ } while (next);
1432
+ const existing = store.session.message[sessionID] ?? [];
1433
+ const ids = new Set(existing.map((item) => item.id));
1434
+ const messages = [...fetched.reverse().filter((item) => !ids.has(item.id)), ...existing];
1435
+ batch(() => {
1436
+ options?.beforePublish?.();
1437
+ messageIndex.set(sessionID, new Map(messages.map((item, position) => [item.id, position])));
1438
+ setStore("session", "message", sessionID, reconcile(messages));
1439
+ setStore("session", "messageCursor", sessionID, next);
1440
+ });
1441
+ return true;
1442
+ })().catch((error) => {
1443
+ if (!signal?.aborted)
1444
+ throw error;
1445
+ }).finally(() => setStore("session", "messageLoading", sessionID, false));
1446
+ track(messageLoads, sessionID, request);
1447
+ await request;
1448
+ },
1449
+ invalidate(sessionID) {
1450
+ sync.invalidate(`session.message:${sessionID}`);
1451
+ }
1452
+ },
1453
+ permission: {
1454
+ list(sessionID) {
1455
+ return store.session.permission[sessionID];
1456
+ },
1457
+ sessions() {
1458
+ return Object.entries(store.session.permission).flatMap(([sessionID, requests]) => requests.length > 0 ? [sessionID] : []);
1459
+ },
1460
+ sync(sessionID) {
1461
+ return sync.run(`session.permission:${sessionID}`, async () => {
1462
+ setStore("session", "permission", sessionID, await api().permission.list({ sessionID }));
1463
+ });
1464
+ },
1465
+ invalidate(sessionID) {
1466
+ sync.invalidate(`session.permission:${sessionID}`);
1467
+ },
1468
+ async reply(input) {
1469
+ await api().permission.reply(input).catch((error) => {
1470
+ if (!isPermissionNotFoundError2(error))
1471
+ throw error;
1472
+ });
1473
+ removePermission(input.sessionID, input.requestID);
1474
+ }
1475
+ },
1476
+ form: {
1477
+ list(sessionID, ref) {
1478
+ const forms = store.session.form[sessionID];
1479
+ if (sessionID !== "global")
1480
+ return forms;
1481
+ if (!ref)
1482
+ return;
1483
+ const key = locationKey2(ref);
1484
+ return forms?.filter((form) => form.location && locationKey2(form.location) === key);
1485
+ },
1486
+ sessions() {
1487
+ return Object.entries(store.session.form).flatMap(([sessionID, forms]) => sessionID !== "global" && forms.length > 0 ? [sessionID] : []);
1488
+ },
1489
+ sync(sessionID, ref) {
1490
+ const key = `session.form:${sessionID}:${sessionID === "global" ? locationKey2(ref ?? defaultLocation()) : ""}`;
1491
+ return sync.run(key, async () => {
1492
+ if (sessionID === "global") {
1493
+ const response = await api().form.list({
1494
+ location: locationQuery(ref ?? defaultLocation())
1495
+ });
1496
+ const location = {
1497
+ directory: response.location.directory
1498
+ };
1499
+ const locationID = locationKey2(location);
1500
+ setStore("session", "form", sessionID, [
1501
+ ...(store.session.form[sessionID] ?? []).filter((form) => form.location && locationKey2(form.location) !== locationID),
1502
+ ...response.data.filter((form) => form.sessionID === "global").map((form) => ({ ...form, location }))
1503
+ ]);
1504
+ return;
1505
+ }
1506
+ setStore("session", "form", sessionID, await api().session.form.list({ sessionID }));
1507
+ });
1508
+ },
1509
+ invalidate(sessionID, ref) {
1510
+ sync.invalidate(`session.form:${sessionID}:${sessionID === "global" ? locationKey2(ref ?? defaultLocation()) : ""}`);
1511
+ },
1512
+ reply(input, ref) {
1513
+ return settleForm(input, ref, api().session.form.reply(input, formRequestOptions(input.sessionID, ref)));
1514
+ },
1515
+ cancel(input, ref) {
1516
+ return settleForm(input, ref, api().session.form.cancel(input, formRequestOptions(input.sessionID, ref)));
1517
+ }
1518
+ }
1519
+ },
1520
+ project: {
1521
+ list() {
1522
+ return Object.values(store.project.info).filter((project) => project.time.archived === undefined).toSorted((a, b) => b.time.active - a.time.active);
1523
+ },
1524
+ get(projectID) {
1525
+ return store.project.info[projectID];
1526
+ },
1527
+ sync() {
1528
+ return sync.run("project", async () => {
1529
+ const projects = await api().project.list();
1530
+ setStore("project", "info", reconcile(Object.fromEntries(projects.map((project) => [project.id, project]))));
1531
+ });
1532
+ },
1533
+ invalidate() {
1534
+ sync.invalidate("project");
1535
+ },
1536
+ permission: {
1537
+ list(projectID) {
1538
+ return store.project.permission[projectID];
1539
+ },
1540
+ sync(projectID) {
1541
+ return sync.run(`project.permission:${projectID}`, async () => {
1542
+ setStore("project", "permission", projectID, await api().permission.saved.list({ projectID }));
1543
+ });
1544
+ },
1545
+ invalidate(projectID) {
1546
+ sync.invalidate(`project.permission:${projectID}`);
1547
+ }
1548
+ }
1549
+ },
1550
+ shell: {
1551
+ list(location) {
1552
+ return Object.values(shells.list(location) ?? {});
1553
+ },
1554
+ listBySession(sessionID) {
1555
+ return Object.values(store.location).flatMap((data) => Object.values(data.shell ?? {})).filter((shell) => shell.metadata.sessionID === sessionID);
1556
+ },
1557
+ get(id) {
1558
+ return Object.values(store.location).map((data) => data.shell?.[id]).find((shell) => shell !== undefined);
1559
+ },
1560
+ sync: shells.sync,
1561
+ invalidate: shells.invalidate
1562
+ },
1563
+ location: {
1564
+ info(ref) {
1565
+ return store.location[locationKey2(ref ?? defaultLocation())]?.info;
1566
+ },
1567
+ default() {
1568
+ return defaultLocation();
1569
+ },
1570
+ syncInfo(ref) {
1571
+ const current = ref ?? defaultLocation();
1572
+ return sync.run(`location:${locationKey2(current)}`, async () => {
1573
+ const location = await api().location.get({ location: locationQuery(current) });
1574
+ const key = locationKey2(location);
1575
+ if (!store.location[key])
1576
+ setStore("location", key, {});
1577
+ setStore("location", key, "info", location);
1578
+ if (!ref) {
1579
+ setDefaultLocation({ directory: location.directory });
1580
+ }
1581
+ });
1582
+ },
1583
+ async sync(ref) {
1584
+ await result.location.syncInfo(ref);
1585
+ const location = ref ?? defaultLocation();
1586
+ await Promise.all([
1587
+ result.location.vcs.sync(location),
1588
+ result.location.agent.sync(location),
1589
+ result.location.command.sync(location),
1590
+ result.location.integration.sync(location),
1591
+ result.location.mcp.server.sync(location),
1592
+ result.location.mcp.resource.sync(location),
1593
+ result.location.model.sync(location),
1594
+ result.location.provider.sync(location),
1595
+ result.location.reference.sync(location),
1596
+ result.location.skill.sync(location),
1597
+ result.shell.sync(location),
1598
+ result.session.form.sync("global", location)
1599
+ ]);
1600
+ },
1601
+ invalidate(ref) {
1602
+ const location = ref ?? defaultLocation();
1603
+ sync.invalidate(`location:${locationKey2(location)}`);
1604
+ result.location.vcs.invalidate(location);
1605
+ result.location.agent.invalidate(location);
1606
+ result.location.command.invalidate(location);
1607
+ result.location.config.invalidate(location);
1608
+ result.location.integration.invalidate(location);
1609
+ result.location.mcp.server.invalidate(location);
1610
+ result.location.mcp.resource.invalidate(location);
1611
+ result.location.model.invalidate(location);
1612
+ result.location.provider.invalidate(location);
1613
+ result.location.reference.invalidate(location);
1614
+ result.location.skill.invalidate(location);
1615
+ result.shell.invalidate(location);
1616
+ result.session.form.invalidate("global", location);
1617
+ },
1618
+ vcs: { info: vcs.list, sync: vcs.sync, invalidate: vcs.invalidate },
1619
+ agent: locationResource("agent", (location) => api().agent.list({ location })),
1620
+ command: locationResource("command", (location) => api().command.list({ location })),
1621
+ config: locationResource("config", async (location) => ({
1622
+ location: { directory: location.directory },
1623
+ data: await api().config.get({ location })
1624
+ })),
1625
+ integration: locationResource("integration", (location) => api().integration.list({ location })),
1626
+ mcp: {
1627
+ server: locationResource("mcpServer", (location) => api().mcp.list({ location })),
1628
+ resource: locationResource("mcpResource", async (location) => {
1629
+ const response = await api().mcp.resource.catalog({ location });
1630
+ return { location: response.location, data: response.data.resources };
1631
+ })
1632
+ },
1633
+ model: locationResource("model", (location) => api().model.list({ location }), { alias: true }),
1634
+ provider: locationResource("provider", (location) => api().provider.list({ location }), { alias: true }),
1635
+ reference: locationResource("reference", (location) => api().reference.list({ location })),
1636
+ websearch: {
1637
+ list(location) {
1638
+ return store.location[locationKey2(location ?? defaultLocation())]?.websearch;
1639
+ },
1640
+ async refresh(ref) {
1641
+ const input = { location: locationQuery(ref ?? defaultLocation()) };
1642
+ const providers = await api().websearch.providers(input);
1643
+ const key = locationKey2(providers.location);
1644
+ setStore("location", key, { websearch: providers.data });
1645
+ }
1646
+ },
1647
+ skill: locationResource("skill", (location) => api().skill.list({ location }))
1648
+ }
1649
+ };
1650
+ createEffect(() => {
1651
+ if (config.connection?.status() === "connected")
1652
+ return;
1653
+ sync.invalidate();
1654
+ });
1655
+ onCleanup(config.event.listen(({ details }) => {
1656
+ handleEvent(details);
1657
+ }));
1658
+ return result;
1659
+ }
1660
+
1661
+ export { settleMs2, locationKey2, createData2 };