@jkwd/inbase 0.1.3 → 0.1.5

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.
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { Canvas } from '@react-three/fiber'
3
- import { emptyIntent, fetchAgentIntent, performAgentAction, persistSessionBlueprint } from './agentIntent'
3
+ import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint } from './agentIntent'
4
4
  import { fetchCodebase } from './codebase'
5
5
  import {
6
6
  layoutWorld,
@@ -100,7 +100,17 @@ function Explorer({
100
100
  graph: CodebaseGraph
101
101
  onRefreshGraph: () => Promise<void>
102
102
  }) {
103
- const [intent, setIntent] = useState<AgentIntent>(emptyIntent)
103
+ const [intents, setIntents] = useState<AgentIntent[]>([])
104
+ const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
105
+ const intent =
106
+ intents.find((item) => item.sessionId === focusedSessionId) ??
107
+ intents[0] ??
108
+ emptyIntent
109
+ const creationIntent =
110
+ intents.find((item) => item.creationMode) ??
111
+ intents.find((item) => item.status === 'blueprint') ??
112
+ null
113
+ const creationMode = Boolean(creationIntent?.creationMode)
104
114
  const previewing = intent.preview || isPatchPreview(intent.status)
105
115
  const plannedCreates = previewing ? intent.creates : []
106
116
  const [userBlocks, setUserBlocks] = useState<UserCreatedBlock[]>([])
@@ -163,6 +173,7 @@ function Explorer({
163
173
  ])
164
174
  const walkPos = useRef<[number, number]>([layout.spawn[0], layout.spawn[2]])
165
175
  const [selectedId, setSelectedId] = useState<string | null>(null)
176
+ const [selectedTick, setSelectedTick] = useState(0)
166
177
  const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
167
178
  const [aimedRelation, setAimedRelation] = useState<AimedRelation | null>(null)
168
179
  const [flyTo, setFlyTo] = useState<FlyTo | null>(null)
@@ -171,12 +182,30 @@ function Explorer({
171
182
  const [followLook, setFollowLook] = useState(false)
172
183
  const [importedBy, setImportedBy] = useState(false)
173
184
  const lastIntentSig = useRef<string | null>(null)
174
- const viewedDiffId = useRef<string | null>(null)
175
- const browsingHistory = useRef(false)
185
+ const viewedDiffId = useRef<Record<string, string | null>>({})
186
+ const browsingHistory = useRef<Record<string, boolean>>({})
176
187
 
177
- const applyIntent = useCallback((next: AgentIntent) => {
178
- setIntent(next)
179
- viewedDiffId.current = next.diffId
188
+ const applyIntent = useCallback((next: AgentIntent, sessionId?: string) => {
189
+ const targetId = next.sessionId ?? sessionId ?? null
190
+ setIntents((current) => {
191
+ const nextList =
192
+ !targetId || next.status === 'idle' || !next.sessionId
193
+ ? current.filter((item) => item.sessionId !== targetId)
194
+ : current.some((item) => item.sessionId === targetId)
195
+ ? current.map((item) => (item.sessionId === targetId ? next : item))
196
+ : [...current, next]
197
+ setFocusedSessionId((currentFocus) => {
198
+ if (
199
+ currentFocus &&
200
+ nextList.some((item) => item.sessionId === currentFocus)
201
+ ) {
202
+ return currentFocus
203
+ }
204
+ return nextList[0]?.sessionId ?? null
205
+ })
206
+ return nextList
207
+ })
208
+ if (targetId && next.sessionId) viewedDiffId.current[targetId] = next.diffId
180
209
  }, [])
181
210
 
182
211
  const rememberWalk = useCallback((x: number, z: number) => {
@@ -243,23 +272,25 @@ function Explorer({
243
272
 
244
273
  const runWorkflowAction = useCallback(
245
274
  async (
275
+ sessionId: string,
246
276
  action: WorkflowAction,
247
277
  options: { instruction?: string; step?: number } = {},
248
278
  ) => {
249
- if (!intent.sessionId) return
279
+ const current = intents.find((item) => item.sessionId === sessionId)
280
+ if (!current?.sessionId) return
250
281
  if (
251
282
  (action === 'continue' || action === 'instruct') &&
252
- (!intent.diffId || !intent.isActiveDiff)
283
+ (!current.diffId || !current.isActiveDiff)
253
284
  ) {
254
285
  return
255
286
  }
256
287
  try {
257
288
  const next = await performAgentAction(
258
289
  action,
259
- intent.sessionId,
290
+ current.sessionId,
260
291
  {
261
292
  ...options,
262
- diffId: intent.diffId ?? undefined,
293
+ diffId: current.diffId ?? undefined,
263
294
  ...(action === 'blueprint_send'
264
295
  ? {
265
296
  userCreatedBlocks: namedCreatedBlocks(userBlocks),
@@ -271,10 +302,10 @@ function Explorer({
271
302
  : {}),
272
303
  },
273
304
  )
274
- browsingHistory.current = false
275
- lastIntentSig.current = intentSignature(next)
276
- applyIntent(next)
277
- if (action === 'invoke' || action === 'continue') {
305
+ browsingHistory.current[sessionId] = false
306
+ lastIntentSig.current = null
307
+ applyIntent(next, sessionId)
308
+ if (action === 'invoke' || action === 'continue' || action === 'stop') {
278
309
  await onRefreshGraph()
279
310
  }
280
311
  } catch {
@@ -286,9 +317,7 @@ function Explorer({
286
317
  blueprintFunctions,
287
318
  blueprintImports,
288
319
  blueprintVariables,
289
- intent.diffId,
290
- intent.isActiveDiff,
291
- intent.sessionId,
320
+ intents,
292
321
  onRefreshGraph,
293
322
  userBlocks,
294
323
  userIslands,
@@ -296,18 +325,44 @@ function Explorer({
296
325
  )
297
326
 
298
327
  const navigateDiff = useCallback(
299
- async (diffId: string) => {
328
+ async (sessionId: string, diffId: string) => {
329
+ const current = intents.find((item) => item.sessionId === sessionId)
330
+ if (!current) return
300
331
  try {
301
- const latest = intent.chain.at(-1)?.id
302
- browsingHistory.current = diffId !== latest
303
- const next = await fetchAgentIntent(diffId)
304
- lastIntentSig.current = intentSignature(next)
305
- applyIntent(next)
332
+ const latest = current.chain.at(-1)?.id
333
+ browsingHistory.current[sessionId] = diffId !== latest
334
+ try {
335
+ await inspectTargetFile({
336
+ sessionId,
337
+ diffId,
338
+ })
339
+ } catch {
340
+ // Still show the historical preview if disk replay failed.
341
+ }
342
+ const next = await fetchAgentIntent(sessionId, diffId)
343
+ lastIntentSig.current = null
344
+ applyIntent(next, sessionId)
345
+ setFocusedSessionId(sessionId)
306
346
  } catch {
307
347
  // Keep the current chain position if navigation failed.
308
348
  }
309
349
  },
310
- [applyIntent, intent.chain],
350
+ [applyIntent, intents],
351
+ )
352
+
353
+ const inspectFile = useCallback(
354
+ async (fileId: string) => {
355
+ try {
356
+ await inspectTargetFile({
357
+ sessionId: intent.sessionId,
358
+ diffId: intent.diffId,
359
+ fileId,
360
+ })
361
+ } catch {
362
+ // Keep the current view if the editor could not open the file.
363
+ }
364
+ },
365
+ [intent.diffId, intent.sessionId],
311
366
  )
312
367
 
313
368
  useEffect(() => {
@@ -344,7 +399,7 @@ function Explorer({
344
399
  }, [])
345
400
 
346
401
  useEffect(() => {
347
- if (!intent.sessionId) {
402
+ if (!creationIntent?.sessionId) {
348
403
  setUserBlocks([])
349
404
  setUserIslands([])
350
405
  setBlueprintFunctions([])
@@ -354,13 +409,13 @@ function Explorer({
354
409
  }
355
410
  const knownFiles = new Set(graph.files.map((file) => file.id))
356
411
  const knownFolders = new Set(graph.folders.map((folder) => folder.path))
357
- const nextBlocks = parseUserCreatedBlocks(intent.userCreatedBlocks).filter(
412
+ const nextBlocks = parseUserCreatedBlocks(creationIntent.userCreatedBlocks).filter(
358
413
  (block) => !knownFiles.has(block.id),
359
414
  )
360
- const nextIslands = parseUserCreatedIslands(intent.userCreatedIslands).filter(
415
+ const nextIslands = parseUserCreatedIslands(creationIntent.userCreatedIslands).filter(
361
416
  (island) => !knownFolders.has(island.path),
362
417
  )
363
- if (intent.creationMode) {
418
+ if (creationIntent.creationMode) {
364
419
  setUserBlocks((current) =>
365
420
  current.some((block) => block.naming) || current.length > 0
366
421
  ? current
@@ -372,28 +427,29 @@ function Explorer({
372
427
  : nextIslands,
373
428
  )
374
429
  setBlueprintFunctions((current) =>
375
- current.length > 0 ? current : intent.blueprintFunctions,
430
+ current.length > 0 ? current : creationIntent.blueprintFunctions,
376
431
  )
377
432
  setBlueprintVariables((current) =>
378
- current.length > 0 ? current : intent.blueprintVariables,
433
+ current.length > 0 ? current : creationIntent.blueprintVariables,
379
434
  )
380
435
  setBlueprintImports((current) =>
381
- current.length > 0 ? current : intent.blueprintImports,
436
+ current.length > 0 ? current : creationIntent.blueprintImports,
382
437
  )
383
438
  return
384
439
  }
385
440
  setUserBlocks(nextBlocks)
386
441
  setUserIslands(nextIslands)
387
- setBlueprintFunctions(intent.blueprintFunctions)
388
- setBlueprintVariables(intent.blueprintVariables)
389
- setBlueprintImports(intent.blueprintImports)
442
+ setBlueprintFunctions(creationIntent.blueprintFunctions)
443
+ setBlueprintVariables(creationIntent.blueprintVariables)
444
+ setBlueprintImports(creationIntent.blueprintImports)
390
445
  }, [
391
- intent.blueprintFunctions,
392
- intent.blueprintImports,
393
- intent.blueprintVariables,
394
- intent.creationMode,
395
- intent.userCreatedBlocks,
396
- intent.userCreatedIslands,
446
+ creationIntent?.blueprintFunctions,
447
+ creationIntent?.blueprintImports,
448
+ creationIntent?.blueprintVariables,
449
+ creationIntent?.creationMode,
450
+ creationIntent?.sessionId,
451
+ creationIntent?.userCreatedBlocks,
452
+ creationIntent?.userCreatedIslands,
397
453
  graph,
398
454
  ])
399
455
 
@@ -405,8 +461,8 @@ function Explorer({
405
461
  variables: PatchSymbolAddition[] = blueprintVariables,
406
462
  imports: PatchImportAddition[] = blueprintImports,
407
463
  ) => {
408
- if (!intent.sessionId || !intent.creationMode) return
409
- persistSessionBlueprint(intent.sessionId, {
464
+ if (!creationIntent?.sessionId || !creationIntent.creationMode) return
465
+ persistSessionBlueprint(creationIntent.sessionId, {
410
466
  userCreatedBlocks: namedCreatedBlocks(blocks),
411
467
  userCreatedIslands: namedCreatedIslands(islands),
412
468
  addedFunctions: functions,
@@ -418,8 +474,8 @@ function Explorer({
418
474
  blueprintFunctions,
419
475
  blueprintImports,
420
476
  blueprintVariables,
421
- intent.creationMode,
422
- intent.sessionId,
477
+ creationIntent?.creationMode,
478
+ creationIntent?.sessionId,
423
479
  userBlocks,
424
480
  userIslands,
425
481
  ],
@@ -427,7 +483,7 @@ function Explorer({
427
483
 
428
484
  const placeBlock = useCallback(
429
485
  (spot: { x: number; z: number; folder: string }) => {
430
- if (!intent.creationMode) return
486
+ if (!creationMode) return
431
487
  setUserBlocks((current) => {
432
488
  if (current.some((block) => block.naming)) return current
433
489
  return [
@@ -445,12 +501,12 @@ function Explorer({
445
501
  })
446
502
  document.exitPointerLock()
447
503
  },
448
- [intent.creationMode],
504
+ [creationMode],
449
505
  )
450
506
 
451
507
  const placeBlockOnFolder = useCallback(
452
508
  (folderPath: string) => {
453
- if (!intent.creationMode) return
509
+ if (!creationMode) return
454
510
  const fileCount = displayGraph.files.filter(
455
511
  (file) => file.folder === folderPath,
456
512
  ).length
@@ -458,7 +514,7 @@ function Explorer({
458
514
  if (!spot) return
459
515
  placeBlock(spot)
460
516
  },
461
- [displayGraph.files, intent.creationMode, layout, placeBlock],
517
+ [displayGraph.files, creationMode, layout, placeBlock],
462
518
  )
463
519
 
464
520
  const commitBlockName = useCallback(
@@ -495,7 +551,7 @@ function Explorer({
495
551
 
496
552
  const placeIsland = useCallback(
497
553
  (parent: string) => {
498
- if (!intent.creationMode) return
554
+ if (!creationMode) return
499
555
  setUserIslands((current) => {
500
556
  if (current.some((island) => island.naming)) return current
501
557
  return [
@@ -511,15 +567,15 @@ function Explorer({
511
567
  })
512
568
  document.exitPointerLock()
513
569
  },
514
- [intent.creationMode],
570
+ [creationMode],
515
571
  )
516
572
 
517
573
  const placeIslandOnFolder = useCallback(
518
574
  (parent: string) => {
519
- if (!intent.creationMode) return
575
+ if (!creationMode) return
520
576
  placeIsland(parent)
521
577
  },
522
- [intent.creationMode, placeIsland],
578
+ [creationMode, placeIsland],
523
579
  )
524
580
 
525
581
  const commitIslandName = useCallback(
@@ -553,7 +609,7 @@ function Explorer({
553
609
  }, [])
554
610
 
555
611
  const deleteSelectedCreatedBlock = useCallback(() => {
556
- if (!intent.creationMode || !selectedId) return false
612
+ if (!creationMode || !selectedId) return false
557
613
  const selected = userBlocks.find((block) => block.id === selectedId)
558
614
  if (!selected || selected.naming) return false
559
615
  const next = userBlocks.filter((block) => block.id !== selectedId)
@@ -561,15 +617,18 @@ function Explorer({
561
617
  persistBlueprint(next, userIslands)
562
618
  setSelectedId(null)
563
619
  return true
564
- }, [intent.creationMode, persistBlueprint, selectedId, userBlocks, userIslands])
620
+ }, [creationMode, persistBlueprint, selectedId, userBlocks, userIslands])
565
621
 
566
622
  const selectFile = useCallback(
567
623
  (fileId: string | null) => {
568
624
  setSelectedId(fileId)
569
- if (fileId) setSelectedFolder(null)
570
- if (fileId && intent.creationMode) document.exitPointerLock()
625
+ if (fileId) {
626
+ setSelectedFolder(null)
627
+ setSelectedTick((tick) => tick + 1)
628
+ }
629
+ if (fileId && creationMode) document.exitPointerLock()
571
630
  },
572
- [intent.creationMode],
631
+ [creationMode],
573
632
  )
574
633
 
575
634
  const selectFolder = useCallback((folderPath: string | null) => {
@@ -579,7 +638,7 @@ function Explorer({
579
638
 
580
639
  const addBlueprintFunction = useCallback(
581
640
  (fileId: string, rawName: string) => {
582
- if (!intent.creationMode || fileId.startsWith('draft:')) return false
641
+ if (!creationMode || fileId.startsWith('draft:')) return false
583
642
  const name = rawName.trim()
584
643
  if (!isBlueprintSymbolName(name)) return false
585
644
  const exists =
@@ -602,7 +661,7 @@ function Explorer({
602
661
  blueprintImports,
603
662
  blueprintVariables,
604
663
  displayGraph.files,
605
- intent.creationMode,
664
+ creationMode,
606
665
  persistBlueprint,
607
666
  userBlocks,
608
667
  userIslands,
@@ -611,7 +670,7 @@ function Explorer({
611
670
 
612
671
  const addBlueprintVariable = useCallback(
613
672
  (fileId: string, rawName: string) => {
614
- if (!intent.creationMode || fileId.startsWith('draft:')) return false
673
+ if (!creationMode || fileId.startsWith('draft:')) return false
615
674
  const name = rawName.trim()
616
675
  if (!isBlueprintSymbolName(name)) return false
617
676
  const exists =
@@ -634,7 +693,7 @@ function Explorer({
634
693
  blueprintImports,
635
694
  blueprintVariables,
636
695
  displayGraph.files,
637
- intent.creationMode,
696
+ creationMode,
638
697
  persistBlueprint,
639
698
  userBlocks,
640
699
  userIslands,
@@ -643,7 +702,7 @@ function Explorer({
643
702
 
644
703
  const addBlueprintImport = useCallback(
645
704
  (fileId: string, raw: string) => {
646
- if (!intent.creationMode || fileId.startsWith('draft:')) return false
705
+ if (!creationMode || fileId.startsWith('draft:')) return false
647
706
  const parsed = parseBlueprintImport(
648
707
  raw,
649
708
  fileId,
@@ -673,7 +732,7 @@ function Explorer({
673
732
  blueprintImports,
674
733
  blueprintVariables,
675
734
  displayGraph.files,
676
- intent.creationMode,
735
+ creationMode,
677
736
  persistBlueprint,
678
737
  userBlocks,
679
738
  userIslands,
@@ -682,7 +741,7 @@ function Explorer({
682
741
 
683
742
  const removeBlueprintFunction = useCallback(
684
743
  (fileId: string, name: string) => {
685
- if (!intent.creationMode) return
744
+ if (!creationMode) return
686
745
  const next = blueprintFunctions.filter(
687
746
  (item) => !(item.file === fileId && item.name === name),
688
747
  )
@@ -693,7 +752,7 @@ function Explorer({
693
752
  blueprintFunctions,
694
753
  blueprintImports,
695
754
  blueprintVariables,
696
- intent.creationMode,
755
+ creationMode,
697
756
  persistBlueprint,
698
757
  userBlocks,
699
758
  userIslands,
@@ -702,7 +761,7 @@ function Explorer({
702
761
 
703
762
  const removeBlueprintVariable = useCallback(
704
763
  (fileId: string, name: string) => {
705
- if (!intent.creationMode) return
764
+ if (!creationMode) return
706
765
  const next = blueprintVariables.filter(
707
766
  (item) => !(item.file === fileId && item.name === name),
708
767
  )
@@ -713,7 +772,7 @@ function Explorer({
713
772
  blueprintFunctions,
714
773
  blueprintImports,
715
774
  blueprintVariables,
716
- intent.creationMode,
775
+ creationMode,
717
776
  persistBlueprint,
718
777
  userBlocks,
719
778
  userIslands,
@@ -722,7 +781,7 @@ function Explorer({
722
781
 
723
782
  const removeBlueprintImport = useCallback(
724
783
  (fileId: string, name: string, from: string) => {
725
- if (!intent.creationMode) return
784
+ if (!creationMode) return
726
785
  const next = blueprintImports.filter(
727
786
  (item) =>
728
787
  !(item.file === fileId && item.name === name && item.from === from),
@@ -740,7 +799,7 @@ function Explorer({
740
799
  blueprintFunctions,
741
800
  blueprintImports,
742
801
  blueprintVariables,
743
- intent.creationMode,
802
+ creationMode,
744
803
  persistBlueprint,
745
804
  userBlocks,
746
805
  userIslands,
@@ -815,15 +874,45 @@ function Explorer({
815
874
  let cancelled = false
816
875
  const poll = async () => {
817
876
  try {
818
- const next = await fetchAgentIntent(
819
- browsingHistory.current ? viewedDiffId.current ?? undefined : undefined,
820
- )
821
- const signature = intentSignature(next)
877
+ const bundle = await fetchAgentIntents()
878
+ const merged: AgentIntent[] = []
879
+ for (const next of bundle.intents) {
880
+ const sessionId = next.sessionId
881
+ if (
882
+ sessionId &&
883
+ browsingHistory.current[sessionId] &&
884
+ viewedDiffId.current[sessionId]
885
+ ) {
886
+ merged.push(
887
+ await fetchAgentIntent(sessionId, viewedDiffId.current[sessionId] ?? undefined),
888
+ )
889
+ } else {
890
+ merged.push(next)
891
+ }
892
+ }
893
+ const signature = JSON.stringify(merged.map(intentSignature))
822
894
  if (cancelled || signature === lastIntentSig.current) {
823
895
  return
824
896
  }
825
897
  lastIntentSig.current = signature
826
- applyIntent(next)
898
+ setIntents(merged)
899
+ for (const next of merged) {
900
+ if (next.sessionId && !browsingHistory.current[next.sessionId]) {
901
+ viewedDiffId.current[next.sessionId] = next.diffId
902
+ }
903
+ }
904
+ setFocusedSessionId((current) => {
905
+ if (current && merged.some((item) => item.sessionId === current)) {
906
+ return current
907
+ }
908
+ if (
909
+ bundle.focusedSessionId &&
910
+ merged.some((item) => item.sessionId === bundle.focusedSessionId)
911
+ ) {
912
+ return bundle.focusedSessionId
913
+ }
914
+ return merged[0]?.sessionId ?? null
915
+ })
827
916
  } catch {
828
917
  // Explorer may be running without the intent endpoint yet.
829
918
  }
@@ -836,7 +925,7 @@ function Explorer({
836
925
  cancelled = true
837
926
  window.clearInterval(timer)
838
927
  }
839
- }, [applyIntent])
928
+ }, [])
840
929
 
841
930
  const plannedIds = previewing ? [...intent.files, ...intent.creates] : []
842
931
  const blueprintImportEdges = blueprintImports.flatMap((item) => {
@@ -854,6 +943,7 @@ function Explorer({
854
943
  <div className="stage">
855
944
  <Canvas
856
945
  shadows={false}
946
+ dpr={[1, 1.5]}
857
947
  gl={{ antialias: true, toneMappingExposure: 1.25 }}
858
948
  camera={{
859
949
  position: layout.spawn,
@@ -890,8 +980,8 @@ function Explorer({
890
980
  importedBy={importedBy}
891
981
  namingId={namingId}
892
982
  namingIslandId={namingIslandId}
893
- onPlaceBlock={intent.creationMode ? placeBlock : undefined}
894
- onPlaceIsland={intent.creationMode ? placeIsland : undefined}
983
+ onPlaceBlock={creationMode ? placeBlock : undefined}
984
+ onPlaceIsland={creationMode ? placeIsland : undefined}
895
985
  onCommitName={commitBlockName}
896
986
  onCancelName={cancelBlockName}
897
987
  userCreatedBlocks={userBlocks}
@@ -901,13 +991,19 @@ function Explorer({
901
991
  </div>
902
992
  <HUD
903
993
  graph={displayGraph}
994
+ layout={layout}
904
995
  mode={mode}
905
996
  locked={locked}
906
997
  selectedId={selectedId}
998
+ selectedTick={selectedTick}
907
999
  selectedFolder={selectedFolder}
1000
+ landAt={landAt}
908
1001
  aimedRelation={aimedRelation}
909
1002
  currentFolder={currentFolder}
910
1003
  intent={intent}
1004
+ intents={intents}
1005
+ focusedSessionId={focusedSessionId}
1006
+ onFocusSession={setFocusedSessionId}
911
1007
  onWorkflowAction={runWorkflowAction}
912
1008
  onNavigateDiff={navigateDiff}
913
1009
  onOpenMap={openMap}
@@ -935,6 +1031,10 @@ function Explorer({
935
1031
  onRemoveBlueprintImport={removeBlueprintImport}
936
1032
  onMapAddFile={placeBlockOnFolder}
937
1033
  onMapAddFolder={placeIslandOnFolder}
1034
+ onInspectFile={inspectFile}
1035
+ plannedIds={plannedIds}
1036
+ createdIds={plannedCreates}
1037
+ deletedIds={deletedIds}
938
1038
  />
939
1039
  </>
940
1040
  )
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  AgentIntent,
3
+ AgentIntentBundle,
3
4
  PatchImport,
4
5
  PatchImportAddition,
5
6
  PatchSymbolAddition,
@@ -124,8 +125,39 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
124
125
  }
125
126
  }
126
127
 
127
- export async function fetchAgentIntent(diffId?: string): Promise<AgentIntent> {
128
+ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
128
129
  const query = new URLSearchParams({ t: String(Date.now()) })
130
+ const response = await fetch(`/api/agent-intent?${query}`)
131
+ if (!response.ok) return { focusedSessionId: null, intents: [] }
132
+ const data = (await response.json()) as {
133
+ focusedSessionId?: string | null
134
+ intents?: unknown
135
+ sessionId?: string | null
136
+ } & Partial<AgentIntent>
137
+ if (Array.isArray(data.intents)) {
138
+ return {
139
+ focusedSessionId:
140
+ typeof data.focusedSessionId === 'string' ? data.focusedSessionId : null,
141
+ intents: data.intents
142
+ .map((intent) => normalize(intent as Partial<AgentIntent>))
143
+ .filter((intent) => Boolean(intent.sessionId)),
144
+ }
145
+ }
146
+ const intent = normalize(data)
147
+ return {
148
+ focusedSessionId: intent.sessionId,
149
+ intents: intent.sessionId ? [intent] : [],
150
+ }
151
+ }
152
+
153
+ export async function fetchAgentIntent(
154
+ sessionId: string,
155
+ diffId?: string,
156
+ ): Promise<AgentIntent> {
157
+ const query = new URLSearchParams({
158
+ t: String(Date.now()),
159
+ sessionId,
160
+ })
129
161
  if (diffId) query.set('diffId', diffId)
130
162
  const response = await fetch(`/api/agent-intent?${query}`)
131
163
  if (!response.ok) return emptyIntent
@@ -180,3 +212,24 @@ export function persistSessionBlueprint(
180
212
  // Keep local drafts if the session handshake is no longer open.
181
213
  })
182
214
  }
215
+
216
+ export async function inspectTargetFile(payload: {
217
+ sessionId?: string | null
218
+ diffId?: string | null
219
+ fileId?: string
220
+ }) {
221
+ const response = await fetch('/api/inspect-file', {
222
+ method: 'POST',
223
+ headers: { 'Content-Type': 'application/json' },
224
+ body: JSON.stringify(payload),
225
+ })
226
+ if (!response.ok) {
227
+ const detail = await response.text()
228
+ throw new Error(detail || 'Could not inspect file')
229
+ }
230
+ return (await response.json()) as {
231
+ path: string | null
232
+ uri: string | null
233
+ opened: boolean
234
+ }
235
+ }