@jkwd/inbase 0.1.10 → 0.1.12
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.
- package/LICENSE +21 -0
- package/README.md +22 -9
- package/apps/explorer/package.json +1 -0
- package/apps/explorer/scripts/branch-changes.d.ts +24 -0
- package/apps/explorer/scripts/branch-changes.mjs +237 -0
- package/apps/explorer/scripts/js-source.mjs +12 -15
- package/apps/explorer/scripts/patch-lib.d.ts +5 -0
- package/apps/explorer/scripts/patch-lib.mjs +5 -0
- package/apps/explorer/scripts/scan-target.mjs +46 -13
- package/apps/explorer/scripts/session-store.d.ts +58 -1
- package/apps/explorer/scripts/session-store.mjs +276 -52
- package/apps/explorer/scripts/tree-diff.mjs +121 -0
- package/apps/explorer/src/App.tsx +170 -32
- package/apps/explorer/src/agentIntent.ts +63 -0
- package/apps/explorer/src/branchChanges.ts +54 -0
- package/apps/explorer/src/index.css +153 -0
- package/apps/explorer/src/scene/FileBlock.tsx +55 -5
- package/apps/explorer/src/scene/MapView.tsx +32 -1
- package/apps/explorer/src/scene/World.tsx +6 -1
- package/apps/explorer/src/types.ts +44 -0
- package/apps/explorer/src/ui/HUD.tsx +583 -96
- package/apps/explorer/src/ui/MapContextMenu.tsx +89 -0
- package/apps/explorer/src/userContext.ts +11 -0
- package/apps/explorer/src/userCreated.ts +50 -0
- package/apps/explorer/vite.config.ts +61 -7
- package/bin/inbase.mjs +23 -4
- package/bin/project.mjs +62 -4
- package/bin/session.mjs +231 -125
- package/package.json +20 -2
- package/skill/commands/inbase.md +17 -0
- package/skill/commands/skipinbase.md +9 -0
- package/skill/inbase/SKILL.md +143 -112
|
@@ -1,6 +1,7 @@
|
|
|
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, persistSessionFocus } from './agentIntent'
|
|
3
|
+
import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint, persistSessionFocus, setupVisualizerSession } from './agentIntent'
|
|
4
|
+
import { emptyBranchChanges, fetchBranchChanges } from './branchChanges'
|
|
4
5
|
import { fetchCodebase, updateCodebase } from './codebase'
|
|
5
6
|
import {
|
|
6
7
|
layoutWorld,
|
|
@@ -13,9 +14,14 @@ import {
|
|
|
13
14
|
} from './layout'
|
|
14
15
|
import { World } from './scene/World'
|
|
15
16
|
import { HUD } from './ui/HUD'
|
|
17
|
+
import {
|
|
18
|
+
MapContextMenu,
|
|
19
|
+
type MapContextMenuState,
|
|
20
|
+
} from './ui/MapContextMenu'
|
|
16
21
|
import {
|
|
17
22
|
fetchUserContext,
|
|
18
23
|
persistFollowLook,
|
|
24
|
+
persistShowBranchChanges,
|
|
19
25
|
persistUserContext,
|
|
20
26
|
} from './userContext'
|
|
21
27
|
import {
|
|
@@ -34,6 +40,7 @@ import {
|
|
|
34
40
|
} from './userCreated'
|
|
35
41
|
import {
|
|
36
42
|
isPatchPreview,
|
|
43
|
+
llmIsMakingChanges,
|
|
37
44
|
type AgentIntent,
|
|
38
45
|
type AimedRelation,
|
|
39
46
|
type CodebaseGraph,
|
|
@@ -53,7 +60,12 @@ function intentSignature(intent: AgentIntent) {
|
|
|
53
60
|
phase: intent.phase,
|
|
54
61
|
stalledWait: intent.stalledWait,
|
|
55
62
|
llmIdle: intent.llmIdle,
|
|
63
|
+
awaitingAttach: intent.awaitingAttach,
|
|
64
|
+
listening: intent.listening,
|
|
65
|
+
lastAck: intent.lastAck,
|
|
56
66
|
sessionId: intent.sessionId,
|
|
67
|
+
name: intent.name,
|
|
68
|
+
feature: intent.feature,
|
|
57
69
|
creationMode: intent.creationMode,
|
|
58
70
|
diffId: intent.diffId,
|
|
59
71
|
chain: intent.chain,
|
|
@@ -76,8 +88,15 @@ export default function App() {
|
|
|
76
88
|
const [updatingModel, setUpdatingModel] = useState(false)
|
|
77
89
|
const updatingModelRef = useRef(false)
|
|
78
90
|
|
|
91
|
+
const graphSig = useRef<string | null>(null)
|
|
79
92
|
const applyGraph = useCallback((next: CodebaseGraph | null, failed: string) => {
|
|
80
93
|
if (next) {
|
|
94
|
+
const signature = JSON.stringify(next)
|
|
95
|
+
if (signature === graphSig.current) {
|
|
96
|
+
setLoadError(null)
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
graphSig.current = signature
|
|
81
100
|
setGraph(next)
|
|
82
101
|
setLoadError(null)
|
|
83
102
|
return true
|
|
@@ -137,13 +156,27 @@ function Explorer({
|
|
|
137
156
|
}) {
|
|
138
157
|
const [intents, setIntents] = useState<AgentIntent[]>([])
|
|
139
158
|
const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
|
|
159
|
+
const [wantBranchChanges, setWantBranchChanges] = useState(false)
|
|
160
|
+
const [branchChanges, setBranchChanges] = useState(emptyBranchChanges)
|
|
140
161
|
const intent =
|
|
141
162
|
intents.find((item) => item.sessionId === focusedSessionId) ??
|
|
142
163
|
intents[0] ??
|
|
143
164
|
emptyIntent
|
|
144
165
|
const canPlace = Boolean(intent.sessionId && intent.creationMode)
|
|
145
|
-
const
|
|
146
|
-
const
|
|
166
|
+
const llmBusy = intents.some(llmIsMakingChanges)
|
|
167
|
+
const llmPreviewing = intent.preview || isPatchPreview(intent.status)
|
|
168
|
+
const showingBranchChanges =
|
|
169
|
+
wantBranchChanges &&
|
|
170
|
+
!llmBusy &&
|
|
171
|
+
!llmPreviewing &&
|
|
172
|
+
branchChanges.available
|
|
173
|
+
const changeSet = llmPreviewing
|
|
174
|
+
? intent
|
|
175
|
+
: showingBranchChanges
|
|
176
|
+
? branchChanges
|
|
177
|
+
: emptyIntent
|
|
178
|
+
const previewing = llmPreviewing || showingBranchChanges
|
|
179
|
+
const plannedCreates = previewing ? changeSet.creates : []
|
|
147
180
|
const [userBlocks, setUserBlocks] = useState<UserCreatedBlock[]>([])
|
|
148
181
|
const [userIslands, setUserIslands] = useState<UserCreatedIsland[]>([])
|
|
149
182
|
const [blueprintFunctions, setBlueprintFunctions] = useState<
|
|
@@ -163,15 +196,15 @@ function Explorer({
|
|
|
163
196
|
return withPreviewGraph(
|
|
164
197
|
graph,
|
|
165
198
|
plannedCreates,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
199
|
+
changeSet.createLines ?? {},
|
|
200
|
+
changeSet.createFolders ?? [],
|
|
201
|
+
changeSet.imports ?? [],
|
|
169
202
|
)
|
|
170
203
|
}, [
|
|
204
|
+
changeSet.createFolders,
|
|
205
|
+
changeSet.createLines,
|
|
206
|
+
changeSet.imports,
|
|
171
207
|
graph,
|
|
172
|
-
intent.createFolders,
|
|
173
|
-
intent.createLines,
|
|
174
|
-
intent.imports,
|
|
175
208
|
plannedCreates,
|
|
176
209
|
previewing,
|
|
177
210
|
])
|
|
@@ -193,15 +226,23 @@ function Explorer({
|
|
|
193
226
|
],
|
|
194
227
|
)
|
|
195
228
|
const layout = useMemo(() => {
|
|
196
|
-
const world = layoutWorld(
|
|
197
|
-
|
|
229
|
+
const world = layoutWorld(
|
|
230
|
+
withUserCreatedGraph(previewGraph, [], userIslands),
|
|
231
|
+
)
|
|
232
|
+
if (previewing) markCreatedFolders(world, changeSet.createFolders ?? [])
|
|
198
233
|
return withUserCreatedLayout(world, userBlocks, userIslands)
|
|
199
|
-
}, [
|
|
234
|
+
}, [
|
|
235
|
+
changeSet.createFolders,
|
|
236
|
+
previewGraph,
|
|
237
|
+
previewing,
|
|
238
|
+
userBlocks,
|
|
239
|
+
userIslands,
|
|
240
|
+
])
|
|
200
241
|
const changeFileIds = useMemo(() => {
|
|
201
242
|
const ids = new Set<string>()
|
|
202
|
-
for (const id of
|
|
203
|
-
for (const id of
|
|
204
|
-
for (const id of
|
|
243
|
+
for (const id of changeSet.files) ids.add(id)
|
|
244
|
+
for (const id of changeSet.creates) ids.add(id)
|
|
245
|
+
for (const id of changeSet.deletes) ids.add(id)
|
|
205
246
|
for (const block of userBlocks) ids.add(block.id)
|
|
206
247
|
for (const item of blueprintFunctions) ids.add(item.file)
|
|
207
248
|
for (const item of blueprintVariables) ids.add(item.file)
|
|
@@ -211,20 +252,20 @@ function Explorer({
|
|
|
211
252
|
blueprintFunctions,
|
|
212
253
|
blueprintImports,
|
|
213
254
|
blueprintVariables,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
255
|
+
changeSet.creates,
|
|
256
|
+
changeSet.deletes,
|
|
257
|
+
changeSet.files,
|
|
217
258
|
userBlocks,
|
|
218
259
|
])
|
|
219
260
|
const changeFolderPaths = useMemo(() => {
|
|
220
261
|
const paths = new Set<string>()
|
|
221
|
-
for (const path of
|
|
262
|
+
for (const path of changeSet.createFolders ?? []) paths.add(path)
|
|
222
263
|
for (const island of userIslands) {
|
|
223
264
|
if (island.path) paths.add(island.path)
|
|
224
265
|
else if (island.id) paths.add(island.id)
|
|
225
266
|
}
|
|
226
267
|
return [...paths]
|
|
227
|
-
}, [
|
|
268
|
+
}, [changeSet.createFolders, userIslands])
|
|
228
269
|
const hasChangeSet =
|
|
229
270
|
changeFileIds.length > 0 || changeFolderPaths.length > 0
|
|
230
271
|
const changePathGraph = useMemo(() => {
|
|
@@ -239,14 +280,14 @@ function Explorer({
|
|
|
239
280
|
if (!hasChangeSet) return layout
|
|
240
281
|
const world = layoutWorld(changePathGraph)
|
|
241
282
|
markCreatedFolders(world, [
|
|
242
|
-
...(
|
|
283
|
+
...(changeSet.createFolders ?? []),
|
|
243
284
|
...userIslands.map((island) => island.path || island.id),
|
|
244
285
|
])
|
|
245
286
|
return world
|
|
246
287
|
}, [
|
|
247
288
|
changePathGraph,
|
|
289
|
+
changeSet.createFolders,
|
|
248
290
|
hasChangeSet,
|
|
249
|
-
intent.createFolders,
|
|
250
291
|
layout,
|
|
251
292
|
userIslands,
|
|
252
293
|
])
|
|
@@ -259,6 +300,7 @@ function Explorer({
|
|
|
259
300
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
260
301
|
const [selectedTick, setSelectedTick] = useState(0)
|
|
261
302
|
const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
|
|
303
|
+
const [mapMenu, setMapMenu] = useState<MapContextMenuState | null>(null)
|
|
262
304
|
const [aimedRelation, setAimedRelation] = useState<AimedRelation | null>(null)
|
|
263
305
|
const [aimedFileId, setAimedFileId] = useState<string | null>(null)
|
|
264
306
|
const [inspectTick, setInspectTick] = useState(0)
|
|
@@ -301,6 +343,14 @@ function Explorer({
|
|
|
301
343
|
persistSessionFocus(sessionId)
|
|
302
344
|
}, [])
|
|
303
345
|
|
|
346
|
+
const setupLlmSession = useCallback(async () => {
|
|
347
|
+
const next = await setupVisualizerSession()
|
|
348
|
+
lastIntentSig.current = null
|
|
349
|
+
applyIntent(next, next.sessionId ?? undefined)
|
|
350
|
+
if (next.sessionId) setFocusedSessionId(next.sessionId)
|
|
351
|
+
return next
|
|
352
|
+
}, [applyIntent])
|
|
353
|
+
|
|
304
354
|
const rememberWalk = useCallback((x: number, z: number) => {
|
|
305
355
|
walkPos.current = [x, z]
|
|
306
356
|
}, [])
|
|
@@ -418,7 +468,6 @@ function Explorer({
|
|
|
418
468
|
lastIntentSig.current = null
|
|
419
469
|
applyIntent(next, sessionId)
|
|
420
470
|
if (
|
|
421
|
-
action === 'invoke' ||
|
|
422
471
|
action === 'continue' ||
|
|
423
472
|
action === 'stop' ||
|
|
424
473
|
action === 'set_step_by_step'
|
|
@@ -509,6 +558,9 @@ function Explorer({
|
|
|
509
558
|
if (typeof context?.followLook === 'boolean') {
|
|
510
559
|
setFollowLook(context.followLook)
|
|
511
560
|
}
|
|
561
|
+
if (typeof context?.showBranchChanges === 'boolean') {
|
|
562
|
+
setWantBranchChanges(context.showBranchChanges)
|
|
563
|
+
}
|
|
512
564
|
})
|
|
513
565
|
return () => {
|
|
514
566
|
cancelled = true
|
|
@@ -680,12 +732,14 @@ function Explorer({
|
|
|
680
732
|
const placeIsland = useCallback(
|
|
681
733
|
(parent: string) => {
|
|
682
734
|
if (!canPlace) return
|
|
735
|
+
let placedId: string | null = null
|
|
683
736
|
setUserIslands((current) => {
|
|
684
737
|
if (current.some((island) => island.naming)) return current
|
|
738
|
+
placedId = `draft:${Date.now()}`
|
|
685
739
|
return [
|
|
686
740
|
...current,
|
|
687
741
|
{
|
|
688
|
-
id:
|
|
742
|
+
id: placedId,
|
|
689
743
|
name: '',
|
|
690
744
|
path: '',
|
|
691
745
|
parent,
|
|
@@ -693,6 +747,10 @@ function Explorer({
|
|
|
693
747
|
},
|
|
694
748
|
]
|
|
695
749
|
})
|
|
750
|
+
if (placedId) {
|
|
751
|
+
setSelectedFolder(placedId)
|
|
752
|
+
setSelectedId(null)
|
|
753
|
+
}
|
|
696
754
|
document.exitPointerLock()
|
|
697
755
|
},
|
|
698
756
|
[canPlace],
|
|
@@ -727,14 +785,23 @@ function Explorer({
|
|
|
727
785
|
)
|
|
728
786
|
setUserIslands(next)
|
|
729
787
|
persistBlueprint(userBlocks, next)
|
|
788
|
+
setSelectedFolder(resolved.path)
|
|
789
|
+
setSelectedId(null)
|
|
730
790
|
return true
|
|
731
791
|
},
|
|
732
792
|
[graph.folders, persistBlueprint, userBlocks, userIslands],
|
|
733
793
|
)
|
|
734
794
|
|
|
735
|
-
const cancelIslandName = useCallback(
|
|
736
|
-
|
|
737
|
-
|
|
795
|
+
const cancelIslandName = useCallback(
|
|
796
|
+
(id: string) => {
|
|
797
|
+
const draft = userIslands.find((island) => island.id === id)
|
|
798
|
+
setUserIslands((current) => current.filter((island) => island.id !== id))
|
|
799
|
+
setSelectedFolder((selected) =>
|
|
800
|
+
selected === id ? draft?.parent ?? null : selected,
|
|
801
|
+
)
|
|
802
|
+
},
|
|
803
|
+
[userIslands],
|
|
804
|
+
)
|
|
738
805
|
|
|
739
806
|
const deleteSelectedCreatedBlock = useCallback(() => {
|
|
740
807
|
if (!canPlace || !selectedId) return false
|
|
@@ -974,6 +1041,10 @@ function Explorer({
|
|
|
974
1041
|
return () => window.removeEventListener('keydown', onKey)
|
|
975
1042
|
}, [cancelBlockName, cancelIslandName, namingId, namingIslandId])
|
|
976
1043
|
|
|
1044
|
+
useEffect(() => {
|
|
1045
|
+
if (mode !== 'map' || naming) setMapMenu(null)
|
|
1046
|
+
}, [mode, naming])
|
|
1047
|
+
|
|
977
1048
|
useEffect(() => {
|
|
978
1049
|
const onKey = (event: KeyboardEvent) => {
|
|
979
1050
|
if (event.repeat || event.code !== 'Backspace') return
|
|
@@ -1002,6 +1073,18 @@ function Explorer({
|
|
|
1002
1073
|
})
|
|
1003
1074
|
}, [])
|
|
1004
1075
|
|
|
1076
|
+
const canToggleBranchChanges = !llmBusy && (wantBranchChanges || branchChanges.available)
|
|
1077
|
+
|
|
1078
|
+
const toggleShowBranchChanges = useCallback(() => {
|
|
1079
|
+
if (llmBusy) return
|
|
1080
|
+
if (!wantBranchChanges && !branchChanges.available) return
|
|
1081
|
+
setWantBranchChanges((current) => {
|
|
1082
|
+
const next = !current
|
|
1083
|
+
persistShowBranchChanges(next)
|
|
1084
|
+
return next
|
|
1085
|
+
})
|
|
1086
|
+
}, [branchChanges.available, llmBusy, wantBranchChanges])
|
|
1087
|
+
|
|
1005
1088
|
const toggleImportedBy = useCallback(() => {
|
|
1006
1089
|
setImportedBy((current) => !current)
|
|
1007
1090
|
}, [])
|
|
@@ -1031,6 +1114,48 @@ function Explorer({
|
|
|
1031
1114
|
return () => window.removeEventListener('keydown', onKey)
|
|
1032
1115
|
}, [toggleImportedBy])
|
|
1033
1116
|
|
|
1117
|
+
useEffect(() => {
|
|
1118
|
+
const onKey = (event: KeyboardEvent) => {
|
|
1119
|
+
if (event.repeat || event.code !== 'KeyG') return
|
|
1120
|
+
const target = event.target
|
|
1121
|
+
if (
|
|
1122
|
+
target instanceof HTMLElement &&
|
|
1123
|
+
(target.tagName === 'TEXTAREA' ||
|
|
1124
|
+
target.tagName === 'INPUT' ||
|
|
1125
|
+
target.tagName === 'SELECT' ||
|
|
1126
|
+
target.isContentEditable)
|
|
1127
|
+
) {
|
|
1128
|
+
return
|
|
1129
|
+
}
|
|
1130
|
+
if (!canToggleBranchChanges) return
|
|
1131
|
+
event.preventDefault()
|
|
1132
|
+
toggleShowBranchChanges()
|
|
1133
|
+
}
|
|
1134
|
+
window.addEventListener('keydown', onKey)
|
|
1135
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
1136
|
+
}, [canToggleBranchChanges, toggleShowBranchChanges])
|
|
1137
|
+
|
|
1138
|
+
useEffect(() => {
|
|
1139
|
+
let cancelled = false
|
|
1140
|
+
const load = async () => {
|
|
1141
|
+
const next = await fetchBranchChanges()
|
|
1142
|
+
if (!cancelled) setBranchChanges(next)
|
|
1143
|
+
}
|
|
1144
|
+
void load()
|
|
1145
|
+
if (!wantBranchChanges || llmBusy) {
|
|
1146
|
+
return () => {
|
|
1147
|
+
cancelled = true
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
const timer = window.setInterval(() => {
|
|
1151
|
+
void load()
|
|
1152
|
+
}, 2000)
|
|
1153
|
+
return () => {
|
|
1154
|
+
cancelled = true
|
|
1155
|
+
window.clearInterval(timer)
|
|
1156
|
+
}
|
|
1157
|
+
}, [llmBusy, updatingModel, wantBranchChanges])
|
|
1158
|
+
|
|
1034
1159
|
useEffect(() => {
|
|
1035
1160
|
if (mode !== 'map' || !hasChangeSet) return
|
|
1036
1161
|
const onKey = (event: KeyboardEvent) => {
|
|
@@ -1111,23 +1236,23 @@ function Explorer({
|
|
|
1111
1236
|
void poll()
|
|
1112
1237
|
const timer = window.setInterval(() => {
|
|
1113
1238
|
void poll()
|
|
1114
|
-
},
|
|
1239
|
+
}, 250)
|
|
1115
1240
|
return () => {
|
|
1116
1241
|
cancelled = true
|
|
1117
1242
|
window.clearInterval(timer)
|
|
1118
1243
|
}
|
|
1119
1244
|
}, [])
|
|
1120
1245
|
|
|
1121
|
-
const plannedIds = previewing ? [...
|
|
1246
|
+
const plannedIds = previewing ? [...changeSet.files, ...changeSet.creates] : []
|
|
1122
1247
|
const blueprintImportEdges = blueprintImports.flatMap((item) => {
|
|
1123
1248
|
if (!displayGraph.files.some((file) => file.id === item.from)) return []
|
|
1124
1249
|
return [{ from: item.file, to: item.from }]
|
|
1125
1250
|
})
|
|
1126
1251
|
const plannedImports = [
|
|
1127
|
-
...(previewing ? (
|
|
1252
|
+
...(previewing ? (changeSet.imports ?? []) : []),
|
|
1128
1253
|
...blueprintImportEdges,
|
|
1129
1254
|
]
|
|
1130
|
-
const deletedIds = previewing ?
|
|
1255
|
+
const deletedIds = previewing ? changeSet.deletes : []
|
|
1131
1256
|
|
|
1132
1257
|
return (
|
|
1133
1258
|
<>
|
|
@@ -1162,7 +1287,7 @@ function Explorer({
|
|
|
1162
1287
|
plannedImports={plannedImports}
|
|
1163
1288
|
createdIds={plannedCreates}
|
|
1164
1289
|
deletedIds={deletedIds}
|
|
1165
|
-
createLines={
|
|
1290
|
+
createLines={changeSet.createLines ?? {}}
|
|
1166
1291
|
flyTo={flyTo}
|
|
1167
1292
|
aimedRelation={aimedRelation}
|
|
1168
1293
|
onAimRelation={setAimedRelation}
|
|
@@ -1174,6 +1299,7 @@ function Explorer({
|
|
|
1174
1299
|
namingIslandId={namingIslandId}
|
|
1175
1300
|
onPlaceBlock={canPlace ? placeBlock : undefined}
|
|
1176
1301
|
onPlaceIsland={canPlace ? placeIsland : undefined}
|
|
1302
|
+
onBlueprintMenu={canPlace ? setMapMenu : undefined}
|
|
1177
1303
|
onCommitName={commitBlockName}
|
|
1178
1304
|
onCancelName={cancelBlockName}
|
|
1179
1305
|
userCreatedBlocks={userBlocks}
|
|
@@ -1203,12 +1329,18 @@ function Explorer({
|
|
|
1203
1329
|
intents={intents}
|
|
1204
1330
|
focusedSessionId={focusedSessionId}
|
|
1205
1331
|
onFocusSession={focusSessionPanel}
|
|
1332
|
+
onSetupSession={setupLlmSession}
|
|
1206
1333
|
onWorkflowAction={runWorkflowAction}
|
|
1207
1334
|
onNavigateDiff={navigateDiff}
|
|
1208
1335
|
onOpenMap={openMap}
|
|
1209
1336
|
onWalk={openWalk}
|
|
1210
1337
|
followLook={followLook}
|
|
1211
1338
|
onToggleFollowLook={toggleFollowLook}
|
|
1339
|
+
showBranchChanges={showingBranchChanges}
|
|
1340
|
+
branchChanges={branchChanges}
|
|
1341
|
+
canShowBranchChanges={canToggleBranchChanges}
|
|
1342
|
+
llmMakingChanges={llmBusy}
|
|
1343
|
+
onToggleShowBranchChanges={toggleShowBranchChanges}
|
|
1212
1344
|
onUpdateModel={onUpdateModel}
|
|
1213
1345
|
updatingModel={updatingModel}
|
|
1214
1346
|
importedBy={importedBy}
|
|
@@ -1241,6 +1373,12 @@ function Explorer({
|
|
|
1241
1373
|
createdIds={plannedCreates}
|
|
1242
1374
|
deletedIds={deletedIds}
|
|
1243
1375
|
/>
|
|
1376
|
+
<MapContextMenu
|
|
1377
|
+
menu={mapMenu}
|
|
1378
|
+
onAddFile={placeBlockOnFolder}
|
|
1379
|
+
onAddFolder={placeIslandOnFolder}
|
|
1380
|
+
onClose={() => setMapMenu(null)}
|
|
1381
|
+
/>
|
|
1244
1382
|
</>
|
|
1245
1383
|
)
|
|
1246
1384
|
}
|
|
@@ -45,10 +45,26 @@ function normalizeImportAdditions(value: unknown): PatchImportAddition[] {
|
|
|
45
45
|
})
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function normalizeAck(
|
|
49
|
+
value: unknown,
|
|
50
|
+
): AgentIntent['lastAck'] {
|
|
51
|
+
if (!value || typeof value !== 'object') return null
|
|
52
|
+
const kind = (value as { kind?: unknown }).kind
|
|
53
|
+
if (typeof kind !== 'string' || kind.trim() === '') return null
|
|
54
|
+
const detail = (value as { detail?: unknown }).detail
|
|
55
|
+
const at = (value as { at?: unknown }).at
|
|
56
|
+
return {
|
|
57
|
+
kind,
|
|
58
|
+
detail: typeof detail === 'string' ? detail : '',
|
|
59
|
+
at: typeof at === 'string' ? at : null,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
export const emptyIntent: AgentIntent = {
|
|
49
64
|
updatedAt: null,
|
|
50
65
|
showMap: false,
|
|
51
66
|
status: 'idle',
|
|
67
|
+
name: null,
|
|
52
68
|
feature: null,
|
|
53
69
|
steps: [],
|
|
54
70
|
step: null,
|
|
@@ -76,6 +92,10 @@ export const emptyIntent: AgentIntent = {
|
|
|
76
92
|
working: false,
|
|
77
93
|
stalledWait: false,
|
|
78
94
|
llmIdle: false,
|
|
95
|
+
awaitingAttach: false,
|
|
96
|
+
listening: false,
|
|
97
|
+
lastAck: null,
|
|
98
|
+
initialInstruction: null,
|
|
79
99
|
creationMode: false,
|
|
80
100
|
canEnterBlueprint: false,
|
|
81
101
|
blueprintSessionId: null,
|
|
@@ -91,6 +111,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
|
|
|
91
111
|
updatedAt: data?.updatedAt ?? null,
|
|
92
112
|
showMap: Boolean(data?.showMap),
|
|
93
113
|
status: data?.status ?? 'idle',
|
|
114
|
+
name: data?.name ?? null,
|
|
94
115
|
feature: data?.feature ?? null,
|
|
95
116
|
steps: Array.isArray(data?.steps) ? data.steps : [],
|
|
96
117
|
step: typeof data?.step === 'number' ? data.step : null,
|
|
@@ -121,6 +142,11 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
|
|
|
121
142
|
working: Boolean(data?.working),
|
|
122
143
|
stalledWait: Boolean(data?.stalledWait),
|
|
123
144
|
llmIdle: Boolean(data?.llmIdle),
|
|
145
|
+
awaitingAttach: Boolean(data?.awaitingAttach),
|
|
146
|
+
listening: Boolean(data?.listening),
|
|
147
|
+
lastAck: normalizeAck(data?.lastAck),
|
|
148
|
+
initialInstruction:
|
|
149
|
+
typeof data?.initialInstruction === 'string' ? data.initialInstruction : null,
|
|
124
150
|
creationMode: Boolean(data?.creationMode),
|
|
125
151
|
canEnterBlueprint: Boolean(data?.canEnterBlueprint),
|
|
126
152
|
blueprintSessionId:
|
|
@@ -224,6 +250,20 @@ export function persistSessionBlueprint(
|
|
|
224
250
|
})
|
|
225
251
|
}
|
|
226
252
|
|
|
253
|
+
export function persistInitialInstruction(sessionId: string, instruction: string) {
|
|
254
|
+
fetch('/api/agent-intent', {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
headers: { 'Content-Type': 'application/json' },
|
|
257
|
+
body: JSON.stringify({
|
|
258
|
+
action: 'set_initial_instruction',
|
|
259
|
+
sessionId,
|
|
260
|
+
instruction,
|
|
261
|
+
}),
|
|
262
|
+
}).catch(() => {
|
|
263
|
+
// Keep the local instruction if the session handshake is no longer open.
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
227
267
|
export function persistSessionFocus(sessionId: string) {
|
|
228
268
|
fetch('/api/agent-intent', {
|
|
229
269
|
method: 'POST',
|
|
@@ -237,6 +277,29 @@ export function persistSessionFocus(sessionId: string) {
|
|
|
237
277
|
})
|
|
238
278
|
}
|
|
239
279
|
|
|
280
|
+
export async function setupVisualizerSession(name?: string) {
|
|
281
|
+
const response = await fetch('/api/agent-intent', {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
284
|
+
body: JSON.stringify({
|
|
285
|
+
action: 'setup_session',
|
|
286
|
+
name,
|
|
287
|
+
}),
|
|
288
|
+
})
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const detail = await response.text()
|
|
291
|
+
let message = detail || 'Could not set up the LLM session'
|
|
292
|
+
try {
|
|
293
|
+
const parsed = JSON.parse(detail) as { error?: string }
|
|
294
|
+
if (parsed?.error) message = parsed.error
|
|
295
|
+
} catch {
|
|
296
|
+
// Use the raw body when it is not JSON.
|
|
297
|
+
}
|
|
298
|
+
throw new Error(message)
|
|
299
|
+
}
|
|
300
|
+
return normalize((await response.json()) as AgentIntent)
|
|
301
|
+
}
|
|
302
|
+
|
|
240
303
|
export async function inspectTargetFile(payload: {
|
|
241
304
|
sessionId?: string | null
|
|
242
305
|
diffId?: string | null
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BranchChanges } from './types'
|
|
2
|
+
|
|
3
|
+
export const emptyBranchChanges: BranchChanges = {
|
|
4
|
+
available: false,
|
|
5
|
+
branch: null,
|
|
6
|
+
base: null,
|
|
7
|
+
files: [],
|
|
8
|
+
creates: [],
|
|
9
|
+
deletes: [],
|
|
10
|
+
createFolders: [],
|
|
11
|
+
createLines: {},
|
|
12
|
+
imports: [],
|
|
13
|
+
addedFunctions: [],
|
|
14
|
+
addedVariables: [],
|
|
15
|
+
addedImports: [],
|
|
16
|
+
changedFunctions: [],
|
|
17
|
+
changedVariables: [],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalize(data: Partial<BranchChanges> | null | undefined): BranchChanges {
|
|
21
|
+
return {
|
|
22
|
+
available: Boolean(data?.available),
|
|
23
|
+
branch: typeof data?.branch === 'string' ? data.branch : null,
|
|
24
|
+
base: typeof data?.base === 'string' ? data.base : null,
|
|
25
|
+
files: Array.isArray(data?.files) ? data.files : [],
|
|
26
|
+
creates: Array.isArray(data?.creates) ? data.creates : [],
|
|
27
|
+
deletes: Array.isArray(data?.deletes) ? data.deletes : [],
|
|
28
|
+
createFolders: Array.isArray(data?.createFolders) ? data.createFolders : [],
|
|
29
|
+
createLines:
|
|
30
|
+
data?.createLines && typeof data.createLines === 'object'
|
|
31
|
+
? data.createLines
|
|
32
|
+
: {},
|
|
33
|
+
imports: Array.isArray(data?.imports) ? data.imports : [],
|
|
34
|
+
addedFunctions: Array.isArray(data?.addedFunctions) ? data.addedFunctions : [],
|
|
35
|
+
addedVariables: Array.isArray(data?.addedVariables) ? data.addedVariables : [],
|
|
36
|
+
addedImports: Array.isArray(data?.addedImports) ? data.addedImports : [],
|
|
37
|
+
changedFunctions: Array.isArray(data?.changedFunctions)
|
|
38
|
+
? data.changedFunctions
|
|
39
|
+
: [],
|
|
40
|
+
changedVariables: Array.isArray(data?.changedVariables)
|
|
41
|
+
? data.changedVariables
|
|
42
|
+
: [],
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function fetchBranchChanges(): Promise<BranchChanges> {
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(`/api/branch-changes?t=${Date.now()}`)
|
|
49
|
+
if (!response.ok) return emptyBranchChanges
|
|
50
|
+
return normalize((await response.json()) as BranchChanges)
|
|
51
|
+
} catch {
|
|
52
|
+
return emptyBranchChanges
|
|
53
|
+
}
|
|
54
|
+
}
|