@kilocode/client 0.0.0-stage → 0.0.0-v2-9

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