@jkwd/inbase 0.1.4 → 0.1.6

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, inspectTargetFile, 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,
@@ -8,6 +8,8 @@ import {
8
8
  standInFront,
9
9
  relationTravelTarget,
10
10
  withPreviewGraph,
11
+ filterGraphToChangePaths,
12
+ mapPointOntoFolder,
11
13
  } from './layout'
12
14
  import { World } from './scene/World'
13
15
  import { HUD } from './ui/HUD'
@@ -49,6 +51,7 @@ function intentSignature(intent: AgentIntent) {
49
51
  updatedAt: intent.updatedAt,
50
52
  status: intent.status,
51
53
  phase: intent.phase,
54
+ stalledWait: intent.stalledWait,
52
55
  sessionId: intent.sessionId,
53
56
  creationMode: intent.creationMode,
54
57
  diffId: intent.diffId,
@@ -61,6 +64,8 @@ function intentSignature(intent: AgentIntent) {
61
64
  addedFunctions: intent.addedFunctions,
62
65
  addedVariables: intent.addedVariables,
63
66
  addedImports: intent.addedImports,
67
+ changedFunctions: intent.changedFunctions,
68
+ changedVariables: intent.changedVariables,
64
69
  })
65
70
  }
66
71
 
@@ -100,7 +105,17 @@ function Explorer({
100
105
  graph: CodebaseGraph
101
106
  onRefreshGraph: () => Promise<void>
102
107
  }) {
103
- const [intent, setIntent] = useState<AgentIntent>(emptyIntent)
108
+ const [intents, setIntents] = useState<AgentIntent[]>([])
109
+ const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
110
+ const intent =
111
+ intents.find((item) => item.sessionId === focusedSessionId) ??
112
+ intents[0] ??
113
+ emptyIntent
114
+ const creationIntent =
115
+ intents.find((item) => item.creationMode) ??
116
+ intents.find((item) => item.status === 'blueprint') ??
117
+ null
118
+ const creationMode = Boolean(creationIntent?.creationMode)
104
119
  const previewing = intent.preview || isPatchPreview(intent.status)
105
120
  const plannedCreates = previewing ? intent.creates : []
106
121
  const [userBlocks, setUserBlocks] = useState<UserCreatedBlock[]>([])
@@ -156,6 +171,59 @@ function Explorer({
156
171
  if (previewing) markCreatedFolders(world, intent.createFolders ?? [])
157
172
  return withUserCreatedLayout(world, userBlocks, userIslands)
158
173
  }, [intent.createFolders, previewGraph, previewing, userBlocks, userIslands])
174
+ const changeFileIds = useMemo(() => {
175
+ const ids = new Set<string>()
176
+ for (const id of intent.files) ids.add(id)
177
+ for (const id of intent.creates) ids.add(id)
178
+ for (const id of intent.deletes) ids.add(id)
179
+ for (const block of userBlocks) ids.add(block.id)
180
+ for (const item of blueprintFunctions) ids.add(item.file)
181
+ for (const item of blueprintVariables) ids.add(item.file)
182
+ for (const item of blueprintImports) ids.add(item.file)
183
+ return [...ids]
184
+ }, [
185
+ blueprintFunctions,
186
+ blueprintImports,
187
+ blueprintVariables,
188
+ intent.creates,
189
+ intent.deletes,
190
+ intent.files,
191
+ userBlocks,
192
+ ])
193
+ const changeFolderPaths = useMemo(() => {
194
+ const paths = new Set<string>()
195
+ for (const path of intent.createFolders ?? []) paths.add(path)
196
+ for (const island of userIslands) {
197
+ if (island.path) paths.add(island.path)
198
+ else if (island.id) paths.add(island.id)
199
+ }
200
+ return [...paths]
201
+ }, [intent.createFolders, userIslands])
202
+ const hasChangeSet =
203
+ changeFileIds.length > 0 || changeFolderPaths.length > 0
204
+ const changePathGraph = useMemo(() => {
205
+ if (!hasChangeSet) return displayGraph
206
+ return filterGraphToChangePaths(
207
+ displayGraph,
208
+ changeFileIds,
209
+ changeFolderPaths,
210
+ )
211
+ }, [changeFileIds, changeFolderPaths, displayGraph, hasChangeSet])
212
+ const changePathLayout = useMemo(() => {
213
+ if (!hasChangeSet) return layout
214
+ const world = layoutWorld(changePathGraph)
215
+ markCreatedFolders(world, [
216
+ ...(intent.createFolders ?? []),
217
+ ...userIslands.map((island) => island.path || island.id),
218
+ ])
219
+ return world
220
+ }, [
221
+ changePathGraph,
222
+ hasChangeSet,
223
+ intent.createFolders,
224
+ layout,
225
+ userIslands,
226
+ ])
159
227
  const [mode, setMode] = useState<ViewMode>('map')
160
228
  const [landAt, setLandAt] = useState<[number, number]>([
161
229
  layout.spawn[0],
@@ -166,18 +234,39 @@ function Explorer({
166
234
  const [selectedTick, setSelectedTick] = useState(0)
167
235
  const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
168
236
  const [aimedRelation, setAimedRelation] = useState<AimedRelation | null>(null)
237
+ const [aimedFileId, setAimedFileId] = useState<string | null>(null)
238
+ const [inspectTick, setInspectTick] = useState(0)
169
239
  const [flyTo, setFlyTo] = useState<FlyTo | null>(null)
170
240
  const [locked, setLocked] = useState(false)
171
241
  const [currentFolder, setCurrentFolder] = useState(graph.targetName)
172
242
  const [followLook, setFollowLook] = useState(false)
173
243
  const [importedBy, setImportedBy] = useState(false)
244
+ const [changePathsOnly, setChangePathsOnly] = useState(false)
174
245
  const lastIntentSig = useRef<string | null>(null)
175
- const viewedDiffId = useRef<string | null>(null)
176
- const browsingHistory = useRef(false)
246
+ const viewedDiffId = useRef<Record<string, string | null>>({})
247
+ const browsingHistory = useRef<Record<string, boolean>>({})
177
248
 
178
- const applyIntent = useCallback((next: AgentIntent) => {
179
- setIntent(next)
180
- viewedDiffId.current = next.diffId
249
+ const applyIntent = useCallback((next: AgentIntent, sessionId?: string) => {
250
+ const targetId = next.sessionId ?? sessionId ?? null
251
+ setIntents((current) => {
252
+ const nextList =
253
+ !targetId || next.status === 'idle' || !next.sessionId
254
+ ? current.filter((item) => item.sessionId !== targetId)
255
+ : current.some((item) => item.sessionId === targetId)
256
+ ? current.map((item) => (item.sessionId === targetId ? next : item))
257
+ : [...current, next]
258
+ setFocusedSessionId((currentFocus) => {
259
+ if (
260
+ currentFocus &&
261
+ nextList.some((item) => item.sessionId === currentFocus)
262
+ ) {
263
+ return currentFocus
264
+ }
265
+ return nextList[0]?.sessionId ?? null
266
+ })
267
+ return nextList
268
+ })
269
+ if (targetId && next.sessionId) viewedDiffId.current[targetId] = next.diffId
181
270
  }, [])
182
271
 
183
272
  const rememberWalk = useCallback((x: number, z: number) => {
@@ -213,6 +302,22 @@ function Explorer({
213
302
  setMode('walk')
214
303
  }, [])
215
304
 
305
+ const landFromMap = useCallback(
306
+ (x: number, z: number) => {
307
+ if (!changePathsOnly || !hasChangeSet) {
308
+ land(x, z)
309
+ return
310
+ }
311
+ const mapped = mapPointOntoFolder(x, z, changePathLayout, layout, [
312
+ layout.spawn[0],
313
+ layout.spawn[2],
314
+ ])
315
+ if (!mapped) return
316
+ land(mapped[0], mapped[1])
317
+ },
318
+ [changePathLayout, changePathsOnly, hasChangeSet, land, layout],
319
+ )
320
+
216
321
  const travelToFile = useCallback(
217
322
  (fileId: string, fly: boolean) => {
218
323
  const placed = layout.files[fileId]
@@ -244,23 +349,25 @@ function Explorer({
244
349
 
245
350
  const runWorkflowAction = useCallback(
246
351
  async (
352
+ sessionId: string,
247
353
  action: WorkflowAction,
248
- options: { instruction?: string; step?: number } = {},
354
+ options: { instruction?: string; step?: number; stepByStep?: boolean } = {},
249
355
  ) => {
250
- if (!intent.sessionId) return
356
+ const current = intents.find((item) => item.sessionId === sessionId)
357
+ if (!current?.sessionId) return
251
358
  if (
252
359
  (action === 'continue' || action === 'instruct') &&
253
- (!intent.diffId || !intent.isActiveDiff)
360
+ (!current.diffId || !current.isActiveDiff)
254
361
  ) {
255
362
  return
256
363
  }
257
364
  try {
258
365
  const next = await performAgentAction(
259
366
  action,
260
- intent.sessionId,
367
+ current.sessionId,
261
368
  {
262
369
  ...options,
263
- diffId: intent.diffId ?? undefined,
370
+ diffId: current.diffId ?? undefined,
264
371
  ...(action === 'blueprint_send'
265
372
  ? {
266
373
  userCreatedBlocks: namedCreatedBlocks(userBlocks),
@@ -272,10 +379,15 @@ function Explorer({
272
379
  : {}),
273
380
  },
274
381
  )
275
- browsingHistory.current = false
276
- lastIntentSig.current = intentSignature(next)
277
- applyIntent(next)
278
- if (action === 'invoke' || action === 'continue' || action === 'stop') {
382
+ browsingHistory.current[sessionId] = false
383
+ lastIntentSig.current = null
384
+ applyIntent(next, sessionId)
385
+ if (
386
+ action === 'invoke' ||
387
+ action === 'continue' ||
388
+ action === 'stop' ||
389
+ action === 'set_step_by_step'
390
+ ) {
279
391
  await onRefreshGraph()
280
392
  }
281
393
  } catch {
@@ -287,9 +399,7 @@ function Explorer({
287
399
  blueprintFunctions,
288
400
  blueprintImports,
289
401
  blueprintVariables,
290
- intent.diffId,
291
- intent.isActiveDiff,
292
- intent.sessionId,
402
+ intents,
293
403
  onRefreshGraph,
294
404
  userBlocks,
295
405
  userIslands,
@@ -297,28 +407,29 @@ function Explorer({
297
407
  )
298
408
 
299
409
  const navigateDiff = useCallback(
300
- async (diffId: string) => {
410
+ async (sessionId: string, diffId: string) => {
411
+ const current = intents.find((item) => item.sessionId === sessionId)
412
+ if (!current) return
301
413
  try {
302
- const latest = intent.chain.at(-1)?.id
303
- browsingHistory.current = diffId !== latest
304
- if (intent.sessionId) {
305
- try {
306
- await inspectTargetFile({
307
- sessionId: intent.sessionId,
308
- diffId,
309
- })
310
- } catch {
311
- // Still show the historical preview if disk replay failed.
312
- }
414
+ const latest = current.chain.at(-1)?.id
415
+ browsingHistory.current[sessionId] = diffId !== latest
416
+ try {
417
+ await inspectTargetFile({
418
+ sessionId,
419
+ diffId,
420
+ })
421
+ } catch {
422
+ // Still show the historical preview if disk replay failed.
313
423
  }
314
- const next = await fetchAgentIntent(diffId)
315
- lastIntentSig.current = intentSignature(next)
316
- applyIntent(next)
424
+ const next = await fetchAgentIntent(sessionId, diffId)
425
+ lastIntentSig.current = null
426
+ applyIntent(next, sessionId)
427
+ setFocusedSessionId(sessionId)
317
428
  } catch {
318
429
  // Keep the current chain position if navigation failed.
319
430
  }
320
431
  },
321
- [applyIntent, intent.chain, intent.sessionId],
432
+ [applyIntent, intents],
322
433
  )
323
434
 
324
435
  const inspectFile = useCallback(
@@ -370,7 +481,7 @@ function Explorer({
370
481
  }, [])
371
482
 
372
483
  useEffect(() => {
373
- if (!intent.sessionId) {
484
+ if (!creationIntent?.sessionId) {
374
485
  setUserBlocks([])
375
486
  setUserIslands([])
376
487
  setBlueprintFunctions([])
@@ -380,13 +491,13 @@ function Explorer({
380
491
  }
381
492
  const knownFiles = new Set(graph.files.map((file) => file.id))
382
493
  const knownFolders = new Set(graph.folders.map((folder) => folder.path))
383
- const nextBlocks = parseUserCreatedBlocks(intent.userCreatedBlocks).filter(
494
+ const nextBlocks = parseUserCreatedBlocks(creationIntent.userCreatedBlocks).filter(
384
495
  (block) => !knownFiles.has(block.id),
385
496
  )
386
- const nextIslands = parseUserCreatedIslands(intent.userCreatedIslands).filter(
497
+ const nextIslands = parseUserCreatedIslands(creationIntent.userCreatedIslands).filter(
387
498
  (island) => !knownFolders.has(island.path),
388
499
  )
389
- if (intent.creationMode) {
500
+ if (creationIntent.creationMode) {
390
501
  setUserBlocks((current) =>
391
502
  current.some((block) => block.naming) || current.length > 0
392
503
  ? current
@@ -398,28 +509,29 @@ function Explorer({
398
509
  : nextIslands,
399
510
  )
400
511
  setBlueprintFunctions((current) =>
401
- current.length > 0 ? current : intent.blueprintFunctions,
512
+ current.length > 0 ? current : creationIntent.blueprintFunctions,
402
513
  )
403
514
  setBlueprintVariables((current) =>
404
- current.length > 0 ? current : intent.blueprintVariables,
515
+ current.length > 0 ? current : creationIntent.blueprintVariables,
405
516
  )
406
517
  setBlueprintImports((current) =>
407
- current.length > 0 ? current : intent.blueprintImports,
518
+ current.length > 0 ? current : creationIntent.blueprintImports,
408
519
  )
409
520
  return
410
521
  }
411
522
  setUserBlocks(nextBlocks)
412
523
  setUserIslands(nextIslands)
413
- setBlueprintFunctions(intent.blueprintFunctions)
414
- setBlueprintVariables(intent.blueprintVariables)
415
- setBlueprintImports(intent.blueprintImports)
524
+ setBlueprintFunctions(creationIntent.blueprintFunctions)
525
+ setBlueprintVariables(creationIntent.blueprintVariables)
526
+ setBlueprintImports(creationIntent.blueprintImports)
416
527
  }, [
417
- intent.blueprintFunctions,
418
- intent.blueprintImports,
419
- intent.blueprintVariables,
420
- intent.creationMode,
421
- intent.userCreatedBlocks,
422
- intent.userCreatedIslands,
528
+ creationIntent?.blueprintFunctions,
529
+ creationIntent?.blueprintImports,
530
+ creationIntent?.blueprintVariables,
531
+ creationIntent?.creationMode,
532
+ creationIntent?.sessionId,
533
+ creationIntent?.userCreatedBlocks,
534
+ creationIntent?.userCreatedIslands,
423
535
  graph,
424
536
  ])
425
537
 
@@ -431,8 +543,8 @@ function Explorer({
431
543
  variables: PatchSymbolAddition[] = blueprintVariables,
432
544
  imports: PatchImportAddition[] = blueprintImports,
433
545
  ) => {
434
- if (!intent.sessionId || !intent.creationMode) return
435
- persistSessionBlueprint(intent.sessionId, {
546
+ if (!creationIntent?.sessionId || !creationIntent.creationMode) return
547
+ persistSessionBlueprint(creationIntent.sessionId, {
436
548
  userCreatedBlocks: namedCreatedBlocks(blocks),
437
549
  userCreatedIslands: namedCreatedIslands(islands),
438
550
  addedFunctions: functions,
@@ -444,8 +556,8 @@ function Explorer({
444
556
  blueprintFunctions,
445
557
  blueprintImports,
446
558
  blueprintVariables,
447
- intent.creationMode,
448
- intent.sessionId,
559
+ creationIntent?.creationMode,
560
+ creationIntent?.sessionId,
449
561
  userBlocks,
450
562
  userIslands,
451
563
  ],
@@ -453,7 +565,7 @@ function Explorer({
453
565
 
454
566
  const placeBlock = useCallback(
455
567
  (spot: { x: number; z: number; folder: string }) => {
456
- if (!intent.creationMode) return
568
+ if (!creationMode) return
457
569
  setUserBlocks((current) => {
458
570
  if (current.some((block) => block.naming)) return current
459
571
  return [
@@ -471,12 +583,12 @@ function Explorer({
471
583
  })
472
584
  document.exitPointerLock()
473
585
  },
474
- [intent.creationMode],
586
+ [creationMode],
475
587
  )
476
588
 
477
589
  const placeBlockOnFolder = useCallback(
478
590
  (folderPath: string) => {
479
- if (!intent.creationMode) return
591
+ if (!creationMode) return
480
592
  const fileCount = displayGraph.files.filter(
481
593
  (file) => file.folder === folderPath,
482
594
  ).length
@@ -484,7 +596,7 @@ function Explorer({
484
596
  if (!spot) return
485
597
  placeBlock(spot)
486
598
  },
487
- [displayGraph.files, intent.creationMode, layout, placeBlock],
599
+ [displayGraph.files, creationMode, layout, placeBlock],
488
600
  )
489
601
 
490
602
  const commitBlockName = useCallback(
@@ -521,7 +633,7 @@ function Explorer({
521
633
 
522
634
  const placeIsland = useCallback(
523
635
  (parent: string) => {
524
- if (!intent.creationMode) return
636
+ if (!creationMode) return
525
637
  setUserIslands((current) => {
526
638
  if (current.some((island) => island.naming)) return current
527
639
  return [
@@ -537,15 +649,15 @@ function Explorer({
537
649
  })
538
650
  document.exitPointerLock()
539
651
  },
540
- [intent.creationMode],
652
+ [creationMode],
541
653
  )
542
654
 
543
655
  const placeIslandOnFolder = useCallback(
544
656
  (parent: string) => {
545
- if (!intent.creationMode) return
657
+ if (!creationMode) return
546
658
  placeIsland(parent)
547
659
  },
548
- [intent.creationMode, placeIsland],
660
+ [creationMode, placeIsland],
549
661
  )
550
662
 
551
663
  const commitIslandName = useCallback(
@@ -579,7 +691,7 @@ function Explorer({
579
691
  }, [])
580
692
 
581
693
  const deleteSelectedCreatedBlock = useCallback(() => {
582
- if (!intent.creationMode || !selectedId) return false
694
+ if (!creationMode || !selectedId) return false
583
695
  const selected = userBlocks.find((block) => block.id === selectedId)
584
696
  if (!selected || selected.naming) return false
585
697
  const next = userBlocks.filter((block) => block.id !== selectedId)
@@ -587,7 +699,7 @@ function Explorer({
587
699
  persistBlueprint(next, userIslands)
588
700
  setSelectedId(null)
589
701
  return true
590
- }, [intent.creationMode, persistBlueprint, selectedId, userBlocks, userIslands])
702
+ }, [creationMode, persistBlueprint, selectedId, userBlocks, userIslands])
591
703
 
592
704
  const selectFile = useCallback(
593
705
  (fileId: string | null) => {
@@ -596,9 +708,17 @@ function Explorer({
596
708
  setSelectedFolder(null)
597
709
  setSelectedTick((tick) => tick + 1)
598
710
  }
599
- if (fileId && intent.creationMode) document.exitPointerLock()
711
+ if (fileId && creationMode) document.exitPointerLock()
600
712
  },
601
- [intent.creationMode],
713
+ [creationMode],
714
+ )
715
+
716
+ const inspectBlock = useCallback(
717
+ (fileId: string) => {
718
+ selectFile(fileId)
719
+ setInspectTick((tick) => tick + 1)
720
+ },
721
+ [selectFile],
602
722
  )
603
723
 
604
724
  const selectFolder = useCallback((folderPath: string | null) => {
@@ -606,9 +726,29 @@ function Explorer({
606
726
  if (folderPath) setSelectedId(null)
607
727
  }, [])
608
728
 
729
+ useEffect(() => {
730
+ if (!changePathsOnly || !hasChangeSet) return
731
+ if (selectedFolder && !changePathLayout.folders[selectedFolder]) {
732
+ setSelectedFolder(null)
733
+ }
734
+ if (
735
+ selectedId &&
736
+ !changePathGraph.files.some((file) => file.id === selectedId)
737
+ ) {
738
+ setSelectedId(null)
739
+ }
740
+ }, [
741
+ changePathGraph.files,
742
+ changePathLayout.folders,
743
+ changePathsOnly,
744
+ hasChangeSet,
745
+ selectedFolder,
746
+ selectedId,
747
+ ])
748
+
609
749
  const addBlueprintFunction = useCallback(
610
750
  (fileId: string, rawName: string) => {
611
- if (!intent.creationMode || fileId.startsWith('draft:')) return false
751
+ if (!creationMode || fileId.startsWith('draft:')) return false
612
752
  const name = rawName.trim()
613
753
  if (!isBlueprintSymbolName(name)) return false
614
754
  const exists =
@@ -631,7 +771,7 @@ function Explorer({
631
771
  blueprintImports,
632
772
  blueprintVariables,
633
773
  displayGraph.files,
634
- intent.creationMode,
774
+ creationMode,
635
775
  persistBlueprint,
636
776
  userBlocks,
637
777
  userIslands,
@@ -640,7 +780,7 @@ function Explorer({
640
780
 
641
781
  const addBlueprintVariable = useCallback(
642
782
  (fileId: string, rawName: string) => {
643
- if (!intent.creationMode || fileId.startsWith('draft:')) return false
783
+ if (!creationMode || fileId.startsWith('draft:')) return false
644
784
  const name = rawName.trim()
645
785
  if (!isBlueprintSymbolName(name)) return false
646
786
  const exists =
@@ -663,7 +803,7 @@ function Explorer({
663
803
  blueprintImports,
664
804
  blueprintVariables,
665
805
  displayGraph.files,
666
- intent.creationMode,
806
+ creationMode,
667
807
  persistBlueprint,
668
808
  userBlocks,
669
809
  userIslands,
@@ -672,7 +812,7 @@ function Explorer({
672
812
 
673
813
  const addBlueprintImport = useCallback(
674
814
  (fileId: string, raw: string) => {
675
- if (!intent.creationMode || fileId.startsWith('draft:')) return false
815
+ if (!creationMode || fileId.startsWith('draft:')) return false
676
816
  const parsed = parseBlueprintImport(
677
817
  raw,
678
818
  fileId,
@@ -702,7 +842,7 @@ function Explorer({
702
842
  blueprintImports,
703
843
  blueprintVariables,
704
844
  displayGraph.files,
705
- intent.creationMode,
845
+ creationMode,
706
846
  persistBlueprint,
707
847
  userBlocks,
708
848
  userIslands,
@@ -711,7 +851,7 @@ function Explorer({
711
851
 
712
852
  const removeBlueprintFunction = useCallback(
713
853
  (fileId: string, name: string) => {
714
- if (!intent.creationMode) return
854
+ if (!creationMode) return
715
855
  const next = blueprintFunctions.filter(
716
856
  (item) => !(item.file === fileId && item.name === name),
717
857
  )
@@ -722,7 +862,7 @@ function Explorer({
722
862
  blueprintFunctions,
723
863
  blueprintImports,
724
864
  blueprintVariables,
725
- intent.creationMode,
865
+ creationMode,
726
866
  persistBlueprint,
727
867
  userBlocks,
728
868
  userIslands,
@@ -731,7 +871,7 @@ function Explorer({
731
871
 
732
872
  const removeBlueprintVariable = useCallback(
733
873
  (fileId: string, name: string) => {
734
- if (!intent.creationMode) return
874
+ if (!creationMode) return
735
875
  const next = blueprintVariables.filter(
736
876
  (item) => !(item.file === fileId && item.name === name),
737
877
  )
@@ -742,7 +882,7 @@ function Explorer({
742
882
  blueprintFunctions,
743
883
  blueprintImports,
744
884
  blueprintVariables,
745
- intent.creationMode,
885
+ creationMode,
746
886
  persistBlueprint,
747
887
  userBlocks,
748
888
  userIslands,
@@ -751,7 +891,7 @@ function Explorer({
751
891
 
752
892
  const removeBlueprintImport = useCallback(
753
893
  (fileId: string, name: string, from: string) => {
754
- if (!intent.creationMode) return
894
+ if (!creationMode) return
755
895
  const next = blueprintImports.filter(
756
896
  (item) =>
757
897
  !(item.file === fileId && item.name === name && item.from === from),
@@ -769,7 +909,7 @@ function Explorer({
769
909
  blueprintFunctions,
770
910
  blueprintImports,
771
911
  blueprintVariables,
772
- intent.creationMode,
912
+ creationMode,
773
913
  persistBlueprint,
774
914
  userBlocks,
775
915
  userIslands,
@@ -820,6 +960,11 @@ function Explorer({
820
960
  setImportedBy((current) => !current)
821
961
  }, [])
822
962
 
963
+ const toggleChangePathsOnly = useCallback(() => {
964
+ if (!hasChangeSet) return
965
+ setChangePathsOnly((current) => !current)
966
+ }, [hasChangeSet])
967
+
823
968
  useEffect(() => {
824
969
  const onKey = (event: KeyboardEvent) => {
825
970
  if (event.repeat || event.code !== 'KeyK') return
@@ -840,19 +985,70 @@ function Explorer({
840
985
  return () => window.removeEventListener('keydown', onKey)
841
986
  }, [toggleImportedBy])
842
987
 
988
+ useEffect(() => {
989
+ if (mode !== 'map' || !hasChangeSet) return
990
+ const onKey = (event: KeyboardEvent) => {
991
+ if (event.repeat || event.code !== 'KeyC') return
992
+ const target = event.target
993
+ if (
994
+ target instanceof HTMLElement &&
995
+ (target.tagName === 'TEXTAREA' ||
996
+ target.tagName === 'INPUT' ||
997
+ target.tagName === 'SELECT' ||
998
+ target.isContentEditable)
999
+ ) {
1000
+ return
1001
+ }
1002
+ event.preventDefault()
1003
+ toggleChangePathsOnly()
1004
+ }
1005
+ window.addEventListener('keydown', onKey)
1006
+ return () => window.removeEventListener('keydown', onKey)
1007
+ }, [hasChangeSet, mode, toggleChangePathsOnly])
1008
+
843
1009
  useEffect(() => {
844
1010
  let cancelled = false
845
1011
  const poll = async () => {
846
1012
  try {
847
- const next = await fetchAgentIntent(
848
- browsingHistory.current ? viewedDiffId.current ?? undefined : undefined,
849
- )
850
- const signature = intentSignature(next)
1013
+ const bundle = await fetchAgentIntents()
1014
+ const merged: AgentIntent[] = []
1015
+ for (const next of bundle.intents) {
1016
+ const sessionId = next.sessionId
1017
+ if (
1018
+ sessionId &&
1019
+ browsingHistory.current[sessionId] &&
1020
+ viewedDiffId.current[sessionId]
1021
+ ) {
1022
+ merged.push(
1023
+ await fetchAgentIntent(sessionId, viewedDiffId.current[sessionId] ?? undefined),
1024
+ )
1025
+ } else {
1026
+ merged.push(next)
1027
+ }
1028
+ }
1029
+ const signature = JSON.stringify(merged.map(intentSignature))
851
1030
  if (cancelled || signature === lastIntentSig.current) {
852
1031
  return
853
1032
  }
854
1033
  lastIntentSig.current = signature
855
- applyIntent(next)
1034
+ setIntents(merged)
1035
+ for (const next of merged) {
1036
+ if (next.sessionId && !browsingHistory.current[next.sessionId]) {
1037
+ viewedDiffId.current[next.sessionId] = next.diffId
1038
+ }
1039
+ }
1040
+ setFocusedSessionId((current) => {
1041
+ if (current && merged.some((item) => item.sessionId === current)) {
1042
+ return current
1043
+ }
1044
+ if (
1045
+ bundle.focusedSessionId &&
1046
+ merged.some((item) => item.sessionId === bundle.focusedSessionId)
1047
+ ) {
1048
+ return bundle.focusedSessionId
1049
+ }
1050
+ return merged[0]?.sessionId ?? null
1051
+ })
856
1052
  } catch {
857
1053
  // Explorer may be running without the intent endpoint yet.
858
1054
  }
@@ -865,7 +1061,7 @@ function Explorer({
865
1061
  cancelled = true
866
1062
  window.clearInterval(timer)
867
1063
  }
868
- }, [applyIntent])
1064
+ }, [])
869
1065
 
870
1066
  const plannedIds = previewing ? [...intent.files, ...intent.creates] : []
871
1067
  const blueprintImportEdges = blueprintImports.flatMap((item) => {
@@ -883,6 +1079,7 @@ function Explorer({
883
1079
  <div className="stage">
884
1080
  <Canvas
885
1081
  shadows={false}
1082
+ dpr={[1, 1.5]}
886
1083
  gl={{ antialias: true, toneMappingExposure: 1.25 }}
887
1084
  camera={{
888
1085
  position: layout.spawn,
@@ -903,7 +1100,7 @@ function Explorer({
903
1100
  onSelectFolder={selectFolder}
904
1101
  onLockedChange={setLocked}
905
1102
  onFolderChange={setCurrentFolder}
906
- onLand={land}
1103
+ onLand={landFromMap}
907
1104
  onWalkPosition={rememberWalk}
908
1105
  onContext={persistUserContext}
909
1106
  plannedIds={plannedIds}
@@ -915,29 +1112,44 @@ function Explorer({
915
1112
  flyTo={flyTo}
916
1113
  aimedRelation={aimedRelation}
917
1114
  onAimRelation={setAimedRelation}
1115
+ onAimFile={setAimedFileId}
1116
+ onInspect={inspectBlock}
918
1117
  onTravelTo={flyAlongRelation}
919
1118
  importedBy={importedBy}
920
1119
  namingId={namingId}
921
1120
  namingIslandId={namingIslandId}
922
- onPlaceBlock={intent.creationMode ? placeBlock : undefined}
923
- onPlaceIsland={intent.creationMode ? placeIsland : undefined}
1121
+ onPlaceBlock={creationMode ? placeBlock : undefined}
1122
+ onPlaceIsland={creationMode ? placeIsland : undefined}
924
1123
  onCommitName={commitBlockName}
925
1124
  onCancelName={cancelBlockName}
926
1125
  userCreatedBlocks={userBlocks}
927
1126
  userCreatedIslands={userIslands}
1127
+ mapGraph={
1128
+ changePathsOnly && hasChangeSet ? changePathGraph : null
1129
+ }
1130
+ mapLayout={
1131
+ changePathsOnly && hasChangeSet ? changePathLayout : null
1132
+ }
928
1133
  />
929
1134
  </Canvas>
930
1135
  </div>
931
1136
  <HUD
932
1137
  graph={displayGraph}
1138
+ layout={layout}
933
1139
  mode={mode}
934
1140
  locked={locked}
935
1141
  selectedId={selectedId}
936
1142
  selectedTick={selectedTick}
1143
+ inspectTick={inspectTick}
937
1144
  selectedFolder={selectedFolder}
1145
+ landAt={landAt}
938
1146
  aimedRelation={aimedRelation}
1147
+ aimedFileId={aimedFileId}
939
1148
  currentFolder={currentFolder}
940
1149
  intent={intent}
1150
+ intents={intents}
1151
+ focusedSessionId={focusedSessionId}
1152
+ onFocusSession={setFocusedSessionId}
941
1153
  onWorkflowAction={runWorkflowAction}
942
1154
  onNavigateDiff={navigateDiff}
943
1155
  onOpenMap={openMap}
@@ -946,6 +1158,9 @@ function Explorer({
946
1158
  onToggleFollowLook={toggleFollowLook}
947
1159
  importedBy={importedBy}
948
1160
  onToggleImportedBy={toggleImportedBy}
1161
+ changePathsOnly={changePathsOnly}
1162
+ hasChangeSet={hasChangeSet}
1163
+ onToggleChangePathsOnly={toggleChangePathsOnly}
949
1164
  naming={naming}
950
1165
  namingIsland={Boolean(namingIslandId)}
951
1166
  onCommitIslandName={(name) => {
@@ -966,6 +1181,10 @@ function Explorer({
966
1181
  onMapAddFile={placeBlockOnFolder}
967
1182
  onMapAddFolder={placeIslandOnFolder}
968
1183
  onInspectFile={inspectFile}
1184
+ onInspectBlock={inspectBlock}
1185
+ plannedIds={plannedIds}
1186
+ createdIds={plannedCreates}
1187
+ deletedIds={deletedIds}
969
1188
  />
970
1189
  </>
971
1190
  )