@jkwd/inbase 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint } from './agentIntent'
3
+ import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint, persistSessionFocus } from './agentIntent'
4
4
  import { fetchCodebase, updateCodebase } from './codebase'
5
5
  import {
6
6
  layoutWorld,
@@ -52,6 +52,7 @@ function intentSignature(intent: AgentIntent) {
52
52
  status: intent.status,
53
53
  phase: intent.phase,
54
54
  stalledWait: intent.stalledWait,
55
+ llmIdle: intent.llmIdle,
55
56
  sessionId: intent.sessionId,
56
57
  creationMode: intent.creationMode,
57
58
  diffId: intent.diffId,
@@ -140,11 +141,7 @@ function Explorer({
140
141
  intents.find((item) => item.sessionId === focusedSessionId) ??
141
142
  intents[0] ??
142
143
  emptyIntent
143
- const creationIntent =
144
- intents.find((item) => item.creationMode) ??
145
- intents.find((item) => item.status === 'blueprint') ??
146
- null
147
- const creationMode = Boolean(creationIntent?.creationMode)
144
+ const canPlace = Boolean(intent.sessionId && intent.creationMode)
148
145
  const previewing = intent.preview || isPatchPreview(intent.status)
149
146
  const plannedCreates = previewing ? intent.creates : []
150
147
  const [userBlocks, setUserBlocks] = useState<UserCreatedBlock[]>([])
@@ -273,6 +270,8 @@ function Explorer({
273
270
  const lastIntentSig = useRef<string | null>(null)
274
271
  const viewedDiffId = useRef<Record<string, string | null>>({})
275
272
  const browsingHistory = useRef<Record<string, boolean>>({})
273
+ const loadedBlueprintSession = useRef<string | null>(null)
274
+ const seenSessionIds = useRef<Set<string>>(new Set())
276
275
 
277
276
  const applyIntent = useCallback((next: AgentIntent, sessionId?: string) => {
278
277
  const targetId = next.sessionId ?? sessionId ?? null
@@ -297,6 +296,11 @@ function Explorer({
297
296
  if (targetId && next.sessionId) viewedDiffId.current[targetId] = next.diffId
298
297
  }, [])
299
298
 
299
+ const focusSessionPanel = useCallback((sessionId: string) => {
300
+ setFocusedSessionId(sessionId)
301
+ persistSessionFocus(sessionId)
302
+ }, [])
303
+
300
304
  const rememberWalk = useCallback((x: number, z: number) => {
301
305
  walkPos.current = [x, z]
302
306
  }, [])
@@ -512,7 +516,8 @@ function Explorer({
512
516
  }, [])
513
517
 
514
518
  useEffect(() => {
515
- if (!creationIntent?.sessionId) {
519
+ if (!intent.sessionId) {
520
+ loadedBlueprintSession.current = null
516
521
  setUserBlocks([])
517
522
  setUserIslands([])
518
523
  setBlueprintFunctions([])
@@ -522,13 +527,23 @@ function Explorer({
522
527
  }
523
528
  const knownFiles = new Set(graph.files.map((file) => file.id))
524
529
  const knownFolders = new Set(graph.folders.map((folder) => folder.path))
525
- const nextBlocks = parseUserCreatedBlocks(creationIntent.userCreatedBlocks).filter(
530
+ const nextBlocks = parseUserCreatedBlocks(intent.userCreatedBlocks).filter(
526
531
  (block) => !knownFiles.has(block.id),
527
532
  )
528
- const nextIslands = parseUserCreatedIslands(creationIntent.userCreatedIslands).filter(
533
+ const nextIslands = parseUserCreatedIslands(intent.userCreatedIslands).filter(
529
534
  (island) => !knownFolders.has(island.path),
530
535
  )
531
- if (creationIntent.creationMode) {
536
+ const switched = loadedBlueprintSession.current !== intent.sessionId
537
+ if (switched) {
538
+ loadedBlueprintSession.current = intent.sessionId
539
+ setUserBlocks(nextBlocks)
540
+ setUserIslands(nextIslands)
541
+ setBlueprintFunctions(intent.blueprintFunctions)
542
+ setBlueprintVariables(intent.blueprintVariables)
543
+ setBlueprintImports(intent.blueprintImports)
544
+ return
545
+ }
546
+ if (intent.creationMode) {
532
547
  setUserBlocks((current) =>
533
548
  current.some((block) => block.naming) || current.length > 0
534
549
  ? current
@@ -540,29 +555,29 @@ function Explorer({
540
555
  : nextIslands,
541
556
  )
542
557
  setBlueprintFunctions((current) =>
543
- current.length > 0 ? current : creationIntent.blueprintFunctions,
558
+ current.length > 0 ? current : intent.blueprintFunctions,
544
559
  )
545
560
  setBlueprintVariables((current) =>
546
- current.length > 0 ? current : creationIntent.blueprintVariables,
561
+ current.length > 0 ? current : intent.blueprintVariables,
547
562
  )
548
563
  setBlueprintImports((current) =>
549
- current.length > 0 ? current : creationIntent.blueprintImports,
564
+ current.length > 0 ? current : intent.blueprintImports,
550
565
  )
551
566
  return
552
567
  }
553
568
  setUserBlocks(nextBlocks)
554
569
  setUserIslands(nextIslands)
555
- setBlueprintFunctions(creationIntent.blueprintFunctions)
556
- setBlueprintVariables(creationIntent.blueprintVariables)
557
- setBlueprintImports(creationIntent.blueprintImports)
570
+ setBlueprintFunctions(intent.blueprintFunctions)
571
+ setBlueprintVariables(intent.blueprintVariables)
572
+ setBlueprintImports(intent.blueprintImports)
558
573
  }, [
559
- creationIntent?.blueprintFunctions,
560
- creationIntent?.blueprintImports,
561
- creationIntent?.blueprintVariables,
562
- creationIntent?.creationMode,
563
- creationIntent?.sessionId,
564
- creationIntent?.userCreatedBlocks,
565
- creationIntent?.userCreatedIslands,
574
+ intent.blueprintFunctions,
575
+ intent.blueprintImports,
576
+ intent.blueprintVariables,
577
+ intent.creationMode,
578
+ intent.sessionId,
579
+ intent.userCreatedBlocks,
580
+ intent.userCreatedIslands,
566
581
  graph,
567
582
  ])
568
583
 
@@ -574,8 +589,8 @@ function Explorer({
574
589
  variables: PatchSymbolAddition[] = blueprintVariables,
575
590
  imports: PatchImportAddition[] = blueprintImports,
576
591
  ) => {
577
- if (!creationIntent?.sessionId || !creationIntent.creationMode) return
578
- persistSessionBlueprint(creationIntent.sessionId, {
592
+ if (!intent.sessionId || !intent.creationMode) return
593
+ persistSessionBlueprint(intent.sessionId, {
579
594
  userCreatedBlocks: namedCreatedBlocks(blocks),
580
595
  userCreatedIslands: namedCreatedIslands(islands),
581
596
  addedFunctions: functions,
@@ -587,8 +602,8 @@ function Explorer({
587
602
  blueprintFunctions,
588
603
  blueprintImports,
589
604
  blueprintVariables,
590
- creationIntent?.creationMode,
591
- creationIntent?.sessionId,
605
+ intent.creationMode,
606
+ intent.sessionId,
592
607
  userBlocks,
593
608
  userIslands,
594
609
  ],
@@ -596,7 +611,7 @@ function Explorer({
596
611
 
597
612
  const placeBlock = useCallback(
598
613
  (spot: { x: number; z: number; folder: string }) => {
599
- if (!creationMode) return
614
+ if (!canPlace) return
600
615
  setUserBlocks((current) => {
601
616
  if (current.some((block) => block.naming)) return current
602
617
  return [
@@ -614,12 +629,12 @@ function Explorer({
614
629
  })
615
630
  document.exitPointerLock()
616
631
  },
617
- [creationMode],
632
+ [canPlace],
618
633
  )
619
634
 
620
635
  const placeBlockOnFolder = useCallback(
621
636
  (folderPath: string) => {
622
- if (!creationMode) return
637
+ if (!canPlace) return
623
638
  const fileCount = displayGraph.files.filter(
624
639
  (file) => file.folder === folderPath,
625
640
  ).length
@@ -627,7 +642,7 @@ function Explorer({
627
642
  if (!spot) return
628
643
  placeBlock(spot)
629
644
  },
630
- [displayGraph.files, creationMode, layout, placeBlock],
645
+ [displayGraph.files, canPlace, layout, placeBlock],
631
646
  )
632
647
 
633
648
  const commitBlockName = useCallback(
@@ -664,7 +679,7 @@ function Explorer({
664
679
 
665
680
  const placeIsland = useCallback(
666
681
  (parent: string) => {
667
- if (!creationMode) return
682
+ if (!canPlace) return
668
683
  setUserIslands((current) => {
669
684
  if (current.some((island) => island.naming)) return current
670
685
  return [
@@ -680,15 +695,15 @@ function Explorer({
680
695
  })
681
696
  document.exitPointerLock()
682
697
  },
683
- [creationMode],
698
+ [canPlace],
684
699
  )
685
700
 
686
701
  const placeIslandOnFolder = useCallback(
687
702
  (parent: string) => {
688
- if (!creationMode) return
703
+ if (!canPlace) return
689
704
  placeIsland(parent)
690
705
  },
691
- [creationMode, placeIsland],
706
+ [canPlace, placeIsland],
692
707
  )
693
708
 
694
709
  const commitIslandName = useCallback(
@@ -722,7 +737,7 @@ function Explorer({
722
737
  }, [])
723
738
 
724
739
  const deleteSelectedCreatedBlock = useCallback(() => {
725
- if (!creationMode || !selectedId) return false
740
+ if (!canPlace || !selectedId) return false
726
741
  const selected = userBlocks.find((block) => block.id === selectedId)
727
742
  if (!selected || selected.naming) return false
728
743
  const next = userBlocks.filter((block) => block.id !== selectedId)
@@ -730,7 +745,7 @@ function Explorer({
730
745
  persistBlueprint(next, userIslands)
731
746
  setSelectedId(null)
732
747
  return true
733
- }, [creationMode, persistBlueprint, selectedId, userBlocks, userIslands])
748
+ }, [canPlace, persistBlueprint, selectedId, userBlocks, userIslands])
734
749
 
735
750
  const selectFile = useCallback(
736
751
  (fileId: string | null) => {
@@ -739,9 +754,9 @@ function Explorer({
739
754
  setSelectedFolder(null)
740
755
  setSelectedTick((tick) => tick + 1)
741
756
  }
742
- if (fileId && creationMode) document.exitPointerLock()
757
+ if (fileId && canPlace) document.exitPointerLock()
743
758
  },
744
- [creationMode],
759
+ [canPlace],
745
760
  )
746
761
 
747
762
  const inspectBlock = useCallback(
@@ -779,7 +794,7 @@ function Explorer({
779
794
 
780
795
  const addBlueprintFunction = useCallback(
781
796
  (fileId: string, rawName: string) => {
782
- if (!creationMode || fileId.startsWith('draft:')) return false
797
+ if (!canPlace || fileId.startsWith('draft:')) return false
783
798
  const name = rawName.trim()
784
799
  if (!isBlueprintSymbolName(name)) return false
785
800
  const exists =
@@ -802,7 +817,7 @@ function Explorer({
802
817
  blueprintImports,
803
818
  blueprintVariables,
804
819
  displayGraph.files,
805
- creationMode,
820
+ canPlace,
806
821
  persistBlueprint,
807
822
  userBlocks,
808
823
  userIslands,
@@ -811,7 +826,7 @@ function Explorer({
811
826
 
812
827
  const addBlueprintVariable = useCallback(
813
828
  (fileId: string, rawName: string) => {
814
- if (!creationMode || fileId.startsWith('draft:')) return false
829
+ if (!canPlace || fileId.startsWith('draft:')) return false
815
830
  const name = rawName.trim()
816
831
  if (!isBlueprintSymbolName(name)) return false
817
832
  const exists =
@@ -834,7 +849,7 @@ function Explorer({
834
849
  blueprintImports,
835
850
  blueprintVariables,
836
851
  displayGraph.files,
837
- creationMode,
852
+ canPlace,
838
853
  persistBlueprint,
839
854
  userBlocks,
840
855
  userIslands,
@@ -843,7 +858,7 @@ function Explorer({
843
858
 
844
859
  const addBlueprintImport = useCallback(
845
860
  (fileId: string, raw: string) => {
846
- if (!creationMode || fileId.startsWith('draft:')) return false
861
+ if (!canPlace || fileId.startsWith('draft:')) return false
847
862
  const parsed = parseBlueprintImport(
848
863
  raw,
849
864
  fileId,
@@ -873,7 +888,7 @@ function Explorer({
873
888
  blueprintImports,
874
889
  blueprintVariables,
875
890
  displayGraph.files,
876
- creationMode,
891
+ canPlace,
877
892
  persistBlueprint,
878
893
  userBlocks,
879
894
  userIslands,
@@ -882,7 +897,7 @@ function Explorer({
882
897
 
883
898
  const removeBlueprintFunction = useCallback(
884
899
  (fileId: string, name: string) => {
885
- if (!creationMode) return
900
+ if (!canPlace) return
886
901
  const next = blueprintFunctions.filter(
887
902
  (item) => !(item.file === fileId && item.name === name),
888
903
  )
@@ -893,7 +908,7 @@ function Explorer({
893
908
  blueprintFunctions,
894
909
  blueprintImports,
895
910
  blueprintVariables,
896
- creationMode,
911
+ canPlace,
897
912
  persistBlueprint,
898
913
  userBlocks,
899
914
  userIslands,
@@ -902,7 +917,7 @@ function Explorer({
902
917
 
903
918
  const removeBlueprintVariable = useCallback(
904
919
  (fileId: string, name: string) => {
905
- if (!creationMode) return
920
+ if (!canPlace) return
906
921
  const next = blueprintVariables.filter(
907
922
  (item) => !(item.file === fileId && item.name === name),
908
923
  )
@@ -913,7 +928,7 @@ function Explorer({
913
928
  blueprintFunctions,
914
929
  blueprintImports,
915
930
  blueprintVariables,
916
- creationMode,
931
+ canPlace,
917
932
  persistBlueprint,
918
933
  userBlocks,
919
934
  userIslands,
@@ -922,7 +937,7 @@ function Explorer({
922
937
 
923
938
  const removeBlueprintImport = useCallback(
924
939
  (fileId: string, name: string, from: string) => {
925
- if (!creationMode) return
940
+ if (!canPlace) return
926
941
  const next = blueprintImports.filter(
927
942
  (item) =>
928
943
  !(item.file === fileId && item.name === name && item.from === from),
@@ -940,7 +955,7 @@ function Explorer({
940
955
  blueprintFunctions,
941
956
  blueprintImports,
942
957
  blueprintVariables,
943
- creationMode,
958
+ canPlace,
944
959
  persistBlueprint,
945
960
  userBlocks,
946
961
  userIslands,
@@ -1068,15 +1083,24 @@ function Explorer({
1068
1083
  viewedDiffId.current[next.sessionId] = next.diffId
1069
1084
  }
1070
1085
  }
1086
+ const nextIds = new Set(
1087
+ merged
1088
+ .map((item) => item.sessionId)
1089
+ .filter((id): id is string => Boolean(id)),
1090
+ )
1091
+ const serverFocus = bundle.focusedSessionId
1092
+ const appeared =
1093
+ serverFocus != null && !seenSessionIds.current.has(serverFocus)
1094
+ seenSessionIds.current = nextIds
1071
1095
  setFocusedSessionId((current) => {
1072
- if (current && merged.some((item) => item.sessionId === current)) {
1096
+ if (appeared && serverFocus && nextIds.has(serverFocus)) {
1097
+ return serverFocus
1098
+ }
1099
+ if (current && nextIds.has(current)) {
1073
1100
  return current
1074
1101
  }
1075
- if (
1076
- bundle.focusedSessionId &&
1077
- merged.some((item) => item.sessionId === bundle.focusedSessionId)
1078
- ) {
1079
- return bundle.focusedSessionId
1102
+ if (serverFocus && nextIds.has(serverFocus)) {
1103
+ return serverFocus
1080
1104
  }
1081
1105
  return merged[0]?.sessionId ?? null
1082
1106
  })
@@ -1148,8 +1172,8 @@ function Explorer({
1148
1172
  importedBy={importedBy}
1149
1173
  namingId={namingId}
1150
1174
  namingIslandId={namingIslandId}
1151
- onPlaceBlock={creationMode ? placeBlock : undefined}
1152
- onPlaceIsland={creationMode ? placeIsland : undefined}
1175
+ onPlaceBlock={canPlace ? placeBlock : undefined}
1176
+ onPlaceIsland={canPlace ? placeIsland : undefined}
1153
1177
  onCommitName={commitBlockName}
1154
1178
  onCancelName={cancelBlockName}
1155
1179
  userCreatedBlocks={userBlocks}
@@ -1178,7 +1202,7 @@ function Explorer({
1178
1202
  intent={intent}
1179
1203
  intents={intents}
1180
1204
  focusedSessionId={focusedSessionId}
1181
- onFocusSession={setFocusedSessionId}
1205
+ onFocusSession={focusSessionPanel}
1182
1206
  onWorkflowAction={runWorkflowAction}
1183
1207
  onNavigateDiff={navigateDiff}
1184
1208
  onOpenMap={openMap}
@@ -75,6 +75,7 @@ export const emptyIntent: AgentIntent = {
75
75
  phase: null,
76
76
  working: false,
77
77
  stalledWait: false,
78
+ llmIdle: false,
78
79
  creationMode: false,
79
80
  canEnterBlueprint: false,
80
81
  blueprintSessionId: null,
@@ -119,6 +120,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
119
120
  phase: data?.phase ?? null,
120
121
  working: Boolean(data?.working),
121
122
  stalledWait: Boolean(data?.stalledWait),
123
+ llmIdle: Boolean(data?.llmIdle),
122
124
  creationMode: Boolean(data?.creationMode),
123
125
  canEnterBlueprint: Boolean(data?.canEnterBlueprint),
124
126
  blueprintSessionId:
@@ -222,6 +224,19 @@ export function persistSessionBlueprint(
222
224
  })
223
225
  }
224
226
 
227
+ export function persistSessionFocus(sessionId: string) {
228
+ fetch('/api/agent-intent', {
229
+ method: 'POST',
230
+ headers: { 'Content-Type': 'application/json' },
231
+ body: JSON.stringify({
232
+ action: 'focus',
233
+ sessionId,
234
+ }),
235
+ }).catch(() => {
236
+ // Keep the local focused session if the server could not record it.
237
+ })
238
+ }
239
+
225
240
  export async function inspectTargetFile(payload: {
226
241
  sessionId?: string | null
227
242
  diffId?: string | null
@@ -533,6 +533,24 @@ button {
533
533
  overflow-y: auto;
534
534
  }
535
535
 
536
+ .hud-session-tabs {
537
+ display: flex;
538
+ gap: 6px;
539
+ flex: 0 0 auto;
540
+ pointer-events: auto;
541
+ overflow-x: auto;
542
+ max-width: 100%;
543
+ }
544
+
545
+ .hud-session-tab {
546
+ flex: 1 1 0;
547
+ min-width: 0;
548
+ overflow: hidden;
549
+ text-overflow: ellipsis;
550
+ white-space: nowrap;
551
+ font-size: 13px;
552
+ }
553
+
536
554
  .hud-left-stack .hud-panel-planned {
537
555
  position: relative;
538
556
  top: auto;
@@ -553,10 +571,6 @@ button {
553
571
  flex: 0 0 auto;
554
572
  }
555
573
 
556
- .hud-left-stack .hud-panel-planned[data-focused='false'] {
557
- opacity: 0.86;
558
- }
559
-
560
574
  .hud-left-stack .hud-panel-planned[data-focused='true'] {
561
575
  box-shadow: 0 0 0 1px #9ad8ff;
562
576
  }
@@ -874,24 +888,28 @@ button {
874
888
  min-width: 0;
875
889
  }
876
890
 
877
- .hud-steps li[data-next='true'] {
891
+ .hud-steps li[data-active='true']:not([data-done='true']) {
878
892
  color: #ffffff;
879
893
  }
880
894
 
881
- .hud-steps li[data-next='true'] .hud-step-title {
895
+ .hud-steps li[data-active='true']:not([data-done='true']) .hud-step-title {
882
896
  color: #ffffff;
883
897
  text-decoration: underline;
884
898
  text-underline-offset: 3px;
885
899
  }
886
900
 
887
- .hud-steps li[data-current='true']:not([data-next='true']):not([data-done='true']) {
888
- color: #9ad8ff;
889
- }
890
-
891
901
  .hud-steps li[data-done='true'],
892
902
  .hud-steps li[data-done='true'] .hud-step-title {
893
903
  color: #3dff78;
894
904
  font-weight: 600;
905
+ }
906
+
907
+ .hud-steps li[data-done='true'][data-active='true'] .hud-step-title {
908
+ text-decoration: underline;
909
+ text-underline-offset: 3px;
910
+ }
911
+
912
+ .hud-steps li[data-done='true']:not([data-active='true']) .hud-step-title {
895
913
  text-decoration: none;
896
914
  }
897
915
 
@@ -215,6 +215,7 @@ export type WorkflowAction =
215
215
  | 'blueprint_no'
216
216
  | 'blueprint_send'
217
217
  | 'blueprint_update'
218
+ | 'focus'
218
219
  | 'set_step_by_step'
219
220
 
220
221
  export type AgentIntentBundle = {
@@ -252,6 +253,7 @@ export type AgentIntent = {
252
253
  phase: WorkflowPhase | null
253
254
  working: boolean
254
255
  stalledWait: boolean
256
+ llmIdle?: boolean
255
257
  creationMode: boolean
256
258
  canEnterBlueprint: boolean
257
259
  blueprintSessionId: string | null