@jkwd/inbase 0.1.11 → 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/apps/explorer/src/App.tsx +38 -5
- package/apps/explorer/src/index.css +29 -0
- package/apps/explorer/src/scene/MapView.tsx +32 -1
- package/apps/explorer/src/scene/World.tsx +6 -1
- package/apps/explorer/src/ui/HUD.tsx +14 -11
- package/apps/explorer/src/ui/MapContextMenu.tsx +89 -0
- package/apps/explorer/src/userCreated.ts +50 -0
- package/bin/session.mjs +20 -4
- package/package.json +1 -1
- package/skill/inbase/SKILL.md +7 -5
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
} from './layout'
|
|
15
15
|
import { World } from './scene/World'
|
|
16
16
|
import { HUD } from './ui/HUD'
|
|
17
|
+
import {
|
|
18
|
+
MapContextMenu,
|
|
19
|
+
type MapContextMenuState,
|
|
20
|
+
} from './ui/MapContextMenu'
|
|
17
21
|
import {
|
|
18
22
|
fetchUserContext,
|
|
19
23
|
persistFollowLook,
|
|
@@ -222,7 +226,9 @@ function Explorer({
|
|
|
222
226
|
],
|
|
223
227
|
)
|
|
224
228
|
const layout = useMemo(() => {
|
|
225
|
-
const world = layoutWorld(
|
|
229
|
+
const world = layoutWorld(
|
|
230
|
+
withUserCreatedGraph(previewGraph, [], userIslands),
|
|
231
|
+
)
|
|
226
232
|
if (previewing) markCreatedFolders(world, changeSet.createFolders ?? [])
|
|
227
233
|
return withUserCreatedLayout(world, userBlocks, userIslands)
|
|
228
234
|
}, [
|
|
@@ -294,6 +300,7 @@ function Explorer({
|
|
|
294
300
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
295
301
|
const [selectedTick, setSelectedTick] = useState(0)
|
|
296
302
|
const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
|
|
303
|
+
const [mapMenu, setMapMenu] = useState<MapContextMenuState | null>(null)
|
|
297
304
|
const [aimedRelation, setAimedRelation] = useState<AimedRelation | null>(null)
|
|
298
305
|
const [aimedFileId, setAimedFileId] = useState<string | null>(null)
|
|
299
306
|
const [inspectTick, setInspectTick] = useState(0)
|
|
@@ -725,12 +732,14 @@ function Explorer({
|
|
|
725
732
|
const placeIsland = useCallback(
|
|
726
733
|
(parent: string) => {
|
|
727
734
|
if (!canPlace) return
|
|
735
|
+
let placedId: string | null = null
|
|
728
736
|
setUserIslands((current) => {
|
|
729
737
|
if (current.some((island) => island.naming)) return current
|
|
738
|
+
placedId = `draft:${Date.now()}`
|
|
730
739
|
return [
|
|
731
740
|
...current,
|
|
732
741
|
{
|
|
733
|
-
id:
|
|
742
|
+
id: placedId,
|
|
734
743
|
name: '',
|
|
735
744
|
path: '',
|
|
736
745
|
parent,
|
|
@@ -738,6 +747,10 @@ function Explorer({
|
|
|
738
747
|
},
|
|
739
748
|
]
|
|
740
749
|
})
|
|
750
|
+
if (placedId) {
|
|
751
|
+
setSelectedFolder(placedId)
|
|
752
|
+
setSelectedId(null)
|
|
753
|
+
}
|
|
741
754
|
document.exitPointerLock()
|
|
742
755
|
},
|
|
743
756
|
[canPlace],
|
|
@@ -772,14 +785,23 @@ function Explorer({
|
|
|
772
785
|
)
|
|
773
786
|
setUserIslands(next)
|
|
774
787
|
persistBlueprint(userBlocks, next)
|
|
788
|
+
setSelectedFolder(resolved.path)
|
|
789
|
+
setSelectedId(null)
|
|
775
790
|
return true
|
|
776
791
|
},
|
|
777
792
|
[graph.folders, persistBlueprint, userBlocks, userIslands],
|
|
778
793
|
)
|
|
779
794
|
|
|
780
|
-
const cancelIslandName = useCallback(
|
|
781
|
-
|
|
782
|
-
|
|
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
|
+
)
|
|
783
805
|
|
|
784
806
|
const deleteSelectedCreatedBlock = useCallback(() => {
|
|
785
807
|
if (!canPlace || !selectedId) return false
|
|
@@ -1019,6 +1041,10 @@ function Explorer({
|
|
|
1019
1041
|
return () => window.removeEventListener('keydown', onKey)
|
|
1020
1042
|
}, [cancelBlockName, cancelIslandName, namingId, namingIslandId])
|
|
1021
1043
|
|
|
1044
|
+
useEffect(() => {
|
|
1045
|
+
if (mode !== 'map' || naming) setMapMenu(null)
|
|
1046
|
+
}, [mode, naming])
|
|
1047
|
+
|
|
1022
1048
|
useEffect(() => {
|
|
1023
1049
|
const onKey = (event: KeyboardEvent) => {
|
|
1024
1050
|
if (event.repeat || event.code !== 'Backspace') return
|
|
@@ -1273,6 +1299,7 @@ function Explorer({
|
|
|
1273
1299
|
namingIslandId={namingIslandId}
|
|
1274
1300
|
onPlaceBlock={canPlace ? placeBlock : undefined}
|
|
1275
1301
|
onPlaceIsland={canPlace ? placeIsland : undefined}
|
|
1302
|
+
onBlueprintMenu={canPlace ? setMapMenu : undefined}
|
|
1276
1303
|
onCommitName={commitBlockName}
|
|
1277
1304
|
onCancelName={cancelBlockName}
|
|
1278
1305
|
userCreatedBlocks={userBlocks}
|
|
@@ -1346,6 +1373,12 @@ function Explorer({
|
|
|
1346
1373
|
createdIds={plannedCreates}
|
|
1347
1374
|
deletedIds={deletedIds}
|
|
1348
1375
|
/>
|
|
1376
|
+
<MapContextMenu
|
|
1377
|
+
menu={mapMenu}
|
|
1378
|
+
onAddFile={placeBlockOnFolder}
|
|
1379
|
+
onAddFolder={placeIslandOnFolder}
|
|
1380
|
+
onClose={() => setMapMenu(null)}
|
|
1381
|
+
/>
|
|
1349
1382
|
</>
|
|
1350
1383
|
)
|
|
1351
1384
|
}
|
|
@@ -1112,6 +1112,35 @@ button {
|
|
|
1112
1112
|
margin-top: 14px;
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
+
.map-context-menu {
|
|
1116
|
+
position: fixed;
|
|
1117
|
+
z-index: 40;
|
|
1118
|
+
display: flex;
|
|
1119
|
+
min-width: 168px;
|
|
1120
|
+
flex-direction: column;
|
|
1121
|
+
gap: 2px;
|
|
1122
|
+
padding: 4px;
|
|
1123
|
+
color: #e7ebf2;
|
|
1124
|
+
background: rgba(25, 28, 34, 0.96);
|
|
1125
|
+
border: 1px solid var(--vc-border);
|
|
1126
|
+
pointer-events: auto;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.map-context-menu button {
|
|
1130
|
+
cursor: pointer;
|
|
1131
|
+
padding: 8px 10px;
|
|
1132
|
+
color: inherit;
|
|
1133
|
+
text-align: left;
|
|
1134
|
+
background: transparent;
|
|
1135
|
+
border: 0;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
.map-context-menu button:hover,
|
|
1139
|
+
.map-context-menu button:focus-visible {
|
|
1140
|
+
background: var(--vc-surface);
|
|
1141
|
+
outline: none;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1115
1144
|
.hud-decide {
|
|
1116
1145
|
display: flex;
|
|
1117
1146
|
flex-wrap: wrap;
|
|
@@ -6,6 +6,12 @@ import { folderAt, worldBounds } from '../layout'
|
|
|
6
6
|
import type { ChangeKind } from '../theme'
|
|
7
7
|
import type { WorldLayout } from '../types'
|
|
8
8
|
|
|
9
|
+
export type MapBlueprintMenu = {
|
|
10
|
+
x: number
|
|
11
|
+
y: number
|
|
12
|
+
folder: string
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
type MapViewProps = {
|
|
10
16
|
layout: WorldLayout
|
|
11
17
|
enabled: boolean
|
|
@@ -16,6 +22,7 @@ type MapViewProps = {
|
|
|
16
22
|
onSelect: (fileId: string | null) => void
|
|
17
23
|
onSelectFolder: (folderPath: string | null) => void
|
|
18
24
|
onTravelTo: (fromId: string, toId: string) => void
|
|
25
|
+
onBlueprintMenu?: (menu: MapBlueprintMenu) => void
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export function MapView({
|
|
@@ -28,6 +35,7 @@ export function MapView({
|
|
|
28
35
|
onSelect,
|
|
29
36
|
onSelectFolder,
|
|
30
37
|
onTravelTo,
|
|
38
|
+
onBlueprintMenu,
|
|
31
39
|
}: MapViewProps) {
|
|
32
40
|
const size = useThree((state) => state.size)
|
|
33
41
|
const { camera, gl, invalidate, scene } = useThree()
|
|
@@ -172,7 +180,28 @@ export function MapView({
|
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
const onContextMenu = (event: MouseEvent) => {
|
|
175
|
-
if (event.ctrlKey)
|
|
183
|
+
if (event.ctrlKey) {
|
|
184
|
+
event.preventDefault()
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if (!onBlueprintMenu) return
|
|
188
|
+
event.preventDefault()
|
|
189
|
+
const pick = pickAt(event.clientX, event.clientY)
|
|
190
|
+
if (!pick) return
|
|
191
|
+
const hit = new THREE.Vector3()
|
|
192
|
+
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
193
|
+
if (!pick.raycaster.ray.intersectPlane(plane, hit)) return
|
|
194
|
+
const folder =
|
|
195
|
+
folderAt(hit.x, hit.z, layout) ??
|
|
196
|
+
(selectedFolder ? layout.folders[selectedFolder] : undefined) ??
|
|
197
|
+
layout.folders['.']
|
|
198
|
+
if (!folder) return
|
|
199
|
+
onSelectFolder(folder.path)
|
|
200
|
+
onBlueprintMenu({
|
|
201
|
+
x: event.clientX,
|
|
202
|
+
y: event.clientY,
|
|
203
|
+
folder: folder.path,
|
|
204
|
+
})
|
|
176
205
|
}
|
|
177
206
|
|
|
178
207
|
const ground = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
@@ -250,10 +279,12 @@ export function MapView({
|
|
|
250
279
|
invalidate,
|
|
251
280
|
layout,
|
|
252
281
|
onLand,
|
|
282
|
+
onBlueprintMenu,
|
|
253
283
|
onSelect,
|
|
254
284
|
onSelectFolder,
|
|
255
285
|
onTravelTo,
|
|
256
286
|
scene,
|
|
287
|
+
selectedFolder,
|
|
257
288
|
])
|
|
258
289
|
|
|
259
290
|
return (
|
|
@@ -3,7 +3,7 @@ import { FileBlock } from './FileBlock'
|
|
|
3
3
|
import { Bridge } from './Bridge'
|
|
4
4
|
import { RelationLines } from './RelationLines'
|
|
5
5
|
import { Player } from './Player'
|
|
6
|
-
import { MapView } from './MapView'
|
|
6
|
+
import { MapView, type MapBlueprintMenu } from './MapView'
|
|
7
7
|
import { SelectionController } from './SelectionController'
|
|
8
8
|
import { UserContextTracker } from './UserContextTracker'
|
|
9
9
|
import { BlockPlacer } from './BlockPlacer'
|
|
@@ -60,6 +60,7 @@ type WorldProps = {
|
|
|
60
60
|
namingId?: string | null
|
|
61
61
|
onPlaceBlock?: (spot: { x: number; z: number; folder: string }) => void
|
|
62
62
|
onPlaceIsland?: (parent: string) => void
|
|
63
|
+
onBlueprintMenu?: (menu: MapBlueprintMenu) => void
|
|
63
64
|
onCommitName?: (id: string, name: string) => boolean
|
|
64
65
|
onCancelName?: (id: string) => void
|
|
65
66
|
userCreatedBlocks?: UserCreatedBlock[]
|
|
@@ -100,6 +101,7 @@ export function World({
|
|
|
100
101
|
namingIslandId = null,
|
|
101
102
|
onPlaceBlock,
|
|
102
103
|
onPlaceIsland,
|
|
104
|
+
onBlueprintMenu,
|
|
103
105
|
onCommitName,
|
|
104
106
|
onCancelName,
|
|
105
107
|
userCreatedBlocks = [],
|
|
@@ -171,6 +173,9 @@ export function World({
|
|
|
171
173
|
onSelect={onSelect}
|
|
172
174
|
onSelectFolder={onSelectFolder}
|
|
173
175
|
onTravelTo={onTravelTo}
|
|
176
|
+
onBlueprintMenu={
|
|
177
|
+
mapping && !placing && onBlueprintMenu ? onBlueprintMenu : undefined
|
|
178
|
+
}
|
|
174
179
|
/>
|
|
175
180
|
|
|
176
181
|
{Object.values(viewLayout.folders).map((folder) => (
|
|
@@ -334,7 +334,7 @@ function HandshakeSetup({
|
|
|
334
334
|
</h2>
|
|
335
335
|
<p>
|
|
336
336
|
Press <kbd>Space</kbd> for a file and <kbd>B</kbd> for an island.
|
|
337
|
-
Optional.
|
|
337
|
+
On the map, right-click to add a file or folder. Optional.
|
|
338
338
|
</p>
|
|
339
339
|
</section>
|
|
340
340
|
<section className="hud-setup-section">
|
|
@@ -373,7 +373,7 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
373
373
|
return { text: 'LLM wait timed out', busy: false }
|
|
374
374
|
}
|
|
375
375
|
if (intent.awaitingAttach) {
|
|
376
|
-
return { text: 'Waiting for /inbase in Cursor', busy:
|
|
376
|
+
return { text: 'Waiting for /inbase in Cursor', busy: false }
|
|
377
377
|
}
|
|
378
378
|
if (kind === 'execute') {
|
|
379
379
|
return { text: `LLM received ${detail}`, busy: true }
|
|
@@ -606,11 +606,13 @@ function SessionPanel({
|
|
|
606
606
|
/>
|
|
607
607
|
{!minimized && (
|
|
608
608
|
<>
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
609
|
+
{!intent.awaitingAttach && (
|
|
610
|
+
<LiveStatus
|
|
611
|
+
intent={intent}
|
|
612
|
+
showStop={showConnectedProgress || working}
|
|
613
|
+
onStop={() => act('stop')}
|
|
614
|
+
/>
|
|
615
|
+
)}
|
|
614
616
|
<label className="hud-mode-switch">
|
|
615
617
|
<span>Step by step</span>
|
|
616
618
|
<button
|
|
@@ -700,8 +702,9 @@ function SessionPanel({
|
|
|
700
702
|
<>
|
|
701
703
|
{canPlace && !intent.working && (
|
|
702
704
|
<p>
|
|
703
|
-
|
|
704
|
-
|
|
705
|
+
On the map, right-click to add a file or folder.{' '}
|
|
706
|
+
<kbd>Space</kbd> places a file, <kbd>B</kbd> an island while
|
|
707
|
+
walking.
|
|
705
708
|
</p>
|
|
706
709
|
)}
|
|
707
710
|
{intent.steps?.length > 0 && (
|
|
@@ -1251,8 +1254,8 @@ function explorerInstructions({
|
|
|
1251
1254
|
{ id: 'select-island', keys: ['Click'], label: 'Select an island' },
|
|
1252
1255
|
{
|
|
1253
1256
|
id: 'add-file-folder',
|
|
1254
|
-
keys: [],
|
|
1255
|
-
label: 'Add file
|
|
1257
|
+
keys: ['Right-click'],
|
|
1258
|
+
label: 'Add file or folder',
|
|
1256
1259
|
},
|
|
1257
1260
|
]
|
|
1258
1261
|
: []),
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
|
|
4
|
+
export type MapContextMenuState = {
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
folder: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type MapContextMenuProps = {
|
|
11
|
+
menu: MapContextMenuState | null
|
|
12
|
+
onAddFile: (folder: string) => void
|
|
13
|
+
onAddFolder: (folder: string) => void
|
|
14
|
+
onClose: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function MapContextMenu({
|
|
18
|
+
menu,
|
|
19
|
+
onAddFile,
|
|
20
|
+
onAddFolder,
|
|
21
|
+
onClose,
|
|
22
|
+
}: MapContextMenuProps) {
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!menu) return
|
|
25
|
+
const onKey = (event: KeyboardEvent) => {
|
|
26
|
+
if (event.code !== 'Escape') return
|
|
27
|
+
event.preventDefault()
|
|
28
|
+
onClose()
|
|
29
|
+
}
|
|
30
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
31
|
+
const target = event.target
|
|
32
|
+
if (target instanceof Element && target.closest('.map-context-menu')) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
onClose()
|
|
36
|
+
}
|
|
37
|
+
window.addEventListener('keydown', onKey)
|
|
38
|
+
window.addEventListener('pointerdown', onPointerDown, true)
|
|
39
|
+
return () => {
|
|
40
|
+
window.removeEventListener('keydown', onKey)
|
|
41
|
+
window.removeEventListener('pointerdown', onPointerDown, true)
|
|
42
|
+
}
|
|
43
|
+
}, [menu, onClose])
|
|
44
|
+
|
|
45
|
+
if (!menu) return null
|
|
46
|
+
|
|
47
|
+
const pad = 8
|
|
48
|
+
const width = 176
|
|
49
|
+
const height = 84
|
|
50
|
+
const left = Math.min(
|
|
51
|
+
Math.max(pad, menu.x),
|
|
52
|
+
window.innerWidth - width - pad,
|
|
53
|
+
)
|
|
54
|
+
const top = Math.min(
|
|
55
|
+
Math.max(pad, menu.y),
|
|
56
|
+
window.innerHeight - height - pad,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return createPortal(
|
|
60
|
+
<div
|
|
61
|
+
className="map-context-menu"
|
|
62
|
+
role="menu"
|
|
63
|
+
style={{ left, top }}
|
|
64
|
+
onContextMenu={(event) => event.preventDefault()}
|
|
65
|
+
>
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
role="menuitem"
|
|
69
|
+
onClick={() => {
|
|
70
|
+
onAddFile(menu.folder)
|
|
71
|
+
onClose()
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
Add file
|
|
75
|
+
</button>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
role="menuitem"
|
|
79
|
+
onClick={() => {
|
|
80
|
+
onAddFolder(menu.folder)
|
|
81
|
+
onClose()
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
Add folder
|
|
85
|
+
</button>
|
|
86
|
+
</div>,
|
|
87
|
+
document.body,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
FileNode,
|
|
6
6
|
PatchImportAddition,
|
|
7
7
|
PatchSymbolAddition,
|
|
8
|
+
PlacedFolder,
|
|
8
9
|
UserCreatedBlock,
|
|
9
10
|
UserCreatedIsland,
|
|
10
11
|
WorldLayout,
|
|
@@ -193,6 +194,54 @@ export function withUserCreatedGraph(
|
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
function placedFolderParent(
|
|
198
|
+
folder: PlacedFolder,
|
|
199
|
+
islands: UserCreatedIsland[],
|
|
200
|
+
) {
|
|
201
|
+
const created = islands.find((item) => islandKey(item) === folder.path)
|
|
202
|
+
if (created) return created.parent
|
|
203
|
+
return folderParent(folder.path)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function expandParentsToChildren(
|
|
207
|
+
folders: Record<string, PlacedFolder>,
|
|
208
|
+
islands: UserCreatedIsland[],
|
|
209
|
+
) {
|
|
210
|
+
const parentPaths = new Set<string>()
|
|
211
|
+
for (const island of islands) {
|
|
212
|
+
let current: string | null = island.parent
|
|
213
|
+
while (current) {
|
|
214
|
+
parentPaths.add(current)
|
|
215
|
+
current = folderParent(current)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const ordered = [...parentPaths].sort(
|
|
219
|
+
(left, right) =>
|
|
220
|
+
right.split('/').filter(Boolean).length -
|
|
221
|
+
left.split('/').filter(Boolean).length,
|
|
222
|
+
)
|
|
223
|
+
for (const parentPath of ordered) {
|
|
224
|
+
const parent = folders[parentPath]
|
|
225
|
+
if (!parent) continue
|
|
226
|
+
const children = Object.values(folders).filter(
|
|
227
|
+
(folder) =>
|
|
228
|
+
folder.path !== parentPath &&
|
|
229
|
+
placedFolderParent(folder, islands) === parentPath,
|
|
230
|
+
)
|
|
231
|
+
if (children.length === 0) continue
|
|
232
|
+
let extent = parent.width / 2
|
|
233
|
+
for (const child of children) {
|
|
234
|
+
extent = Math.max(
|
|
235
|
+
extent,
|
|
236
|
+
Math.abs(child.x - child.width / 2 - parent.x),
|
|
237
|
+
Math.abs(child.x + child.width / 2 - parent.x),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
const width = extent * 2
|
|
241
|
+
if (width > parent.width) folders[parentPath] = { ...parent, width }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
196
245
|
function overlayIslands(
|
|
197
246
|
layout: WorldLayout,
|
|
198
247
|
islands: UserCreatedIsland[],
|
|
@@ -245,6 +294,7 @@ function overlayIslands(
|
|
|
245
294
|
})
|
|
246
295
|
}
|
|
247
296
|
|
|
297
|
+
expandParentsToChildren(folders, islands)
|
|
248
298
|
return { ...layout, folders, bridges }
|
|
249
299
|
}
|
|
250
300
|
|
package/bin/session.mjs
CHANGED
|
@@ -30,15 +30,31 @@ function printAck(kind, detail) {
|
|
|
30
30
|
console.log(`VISUAL_CODER_ACK ${kind}: ${detail}`)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function createManifestGate(manifestPath) {
|
|
33
|
+
export function createManifestGate(manifestPath, watch = fs.watch) {
|
|
34
34
|
const dir = path.dirname(manifestPath)
|
|
35
35
|
let wake = () => {}
|
|
36
36
|
let watcher = null
|
|
37
|
+
|
|
38
|
+
const dropWatcher = () => {
|
|
39
|
+
try {
|
|
40
|
+
watcher?.close()
|
|
41
|
+
} catch {
|
|
42
|
+
// Already closed after an error, or never opened.
|
|
43
|
+
}
|
|
44
|
+
watcher = null
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
try {
|
|
38
|
-
watcher =
|
|
48
|
+
watcher = watch(dir, () => wake())
|
|
49
|
+
watcher?.on?.('error', () => {
|
|
50
|
+
// EMFILE and sandbox limits must not crash wait-for-approval. Poll instead.
|
|
51
|
+
dropWatcher()
|
|
52
|
+
wake()
|
|
53
|
+
})
|
|
39
54
|
} catch {
|
|
40
|
-
|
|
55
|
+
dropWatcher()
|
|
41
56
|
}
|
|
57
|
+
|
|
42
58
|
return {
|
|
43
59
|
wait(ms) {
|
|
44
60
|
return new Promise((resolve) => {
|
|
@@ -50,7 +66,7 @@ function createManifestGate(manifestPath) {
|
|
|
50
66
|
})
|
|
51
67
|
},
|
|
52
68
|
close() {
|
|
53
|
-
|
|
69
|
+
dropWatcher()
|
|
54
70
|
},
|
|
55
71
|
}
|
|
56
72
|
}
|
package/package.json
CHANGED
package/skill/inbase/SKILL.md
CHANGED
|
@@ -144,11 +144,13 @@ npx inbase report-plan \
|
|
|
144
144
|
npx inbase wait-for-approval --session "<session-id>"
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
step.
|
|
147
|
+
Describe this tool call as `inbase wait-for-approval`, not "waiting for the
|
|
148
|
+
user to invoke". The command keeps running until they invoke; when it
|
|
149
|
+
returns, they already did. Do not edit project files until this prints
|
|
150
|
+
`VISUAL_CODER_ACK execute` / `VISUAL_CODER_EXECUTE`. If Step by step is off,
|
|
151
|
+
this returns immediately for each remaining step. First reply in chat
|
|
152
|
+
acknowledging the ack, then re-read this session's `blueprint.json`; the
|
|
153
|
+
user can place files and islands on any step.
|
|
152
154
|
9. Implement only the invoked step by editing the live project files (Write,
|
|
153
155
|
StrReplace, Delete). Paths are the same ids as `codebase.json`. Then record
|
|
154
156
|
the step — Inbase diffs the working tree against the snapshot taken at
|