@jkwd/inbase 0.1.12 → 0.1.13
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/README.md +1 -1
- package/apps/explorer/scripts/session-store.d.ts +2 -0
- package/apps/explorer/scripts/session-store.mjs +72 -10
- package/apps/explorer/src/App.tsx +6 -0
- package/apps/explorer/src/agentIntent.ts +7 -1
- package/apps/explorer/src/index.css +106 -1
- package/apps/explorer/src/scene/DistantFileBlocks.tsx +44 -0
- package/apps/explorer/src/scene/FileBlock.tsx +28 -37
- package/apps/explorer/src/scene/FolderArea.tsx +4 -2
- package/apps/explorer/src/scene/MapView.tsx +117 -36
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +9 -1
- package/apps/explorer/src/scene/WalkLodTracker.tsx +92 -0
- package/apps/explorer/src/scene/World.tsx +109 -24
- package/apps/explorer/src/scene/walkLod.ts +201 -0
- package/apps/explorer/src/types.ts +1 -0
- package/apps/explorer/src/ui/HUD.tsx +53 -27
- package/apps/explorer/vite.config.ts +2 -0
- package/bin/session.mjs +1 -1
- package/package.json +1 -1
- package/skill/commands/inbase.md +2 -2
- package/skill/inbase/SKILL.md +2 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { CONFIG } from '../theme'
|
|
2
|
+
import type { PlacedBridge, PlacedFile, PlacedFolder } from '../types'
|
|
3
|
+
|
|
4
|
+
export const WALK_MESH_RADIUS = 120
|
|
5
|
+
export const WALK_MESH_KEEP = 148
|
|
6
|
+
export const WALK_LABEL_RADIUS = 28
|
|
7
|
+
export const WALK_LABEL_KEEP = 36
|
|
8
|
+
export const WALK_FOLDER_PAD = 160
|
|
9
|
+
export const WALK_FOLDER_LABEL = 18
|
|
10
|
+
export const WALK_AROUND = 14
|
|
11
|
+
export const WALK_BEHIND_DOT = -0.18
|
|
12
|
+
|
|
13
|
+
export type WalkLod = {
|
|
14
|
+
files: Set<string>
|
|
15
|
+
labels: Set<string>
|
|
16
|
+
folders: Set<string>
|
|
17
|
+
folderLabels: Set<string>
|
|
18
|
+
bridges: Set<string>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function folderDistance(
|
|
22
|
+
px: number,
|
|
23
|
+
pz: number,
|
|
24
|
+
folder: PlacedFolder,
|
|
25
|
+
): number {
|
|
26
|
+
const halfW = folder.width / 2
|
|
27
|
+
const minX = folder.x - halfW
|
|
28
|
+
const maxX = folder.x + halfW
|
|
29
|
+
const minZ = folder.z
|
|
30
|
+
const maxZ = folder.z + folder.depth
|
|
31
|
+
const cx = Math.min(maxX, Math.max(minX, px))
|
|
32
|
+
const cz = Math.min(maxZ, Math.max(minZ, pz))
|
|
33
|
+
return Math.hypot(px - cx, pz - cz)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function distanceToSegment(
|
|
37
|
+
px: number,
|
|
38
|
+
pz: number,
|
|
39
|
+
ax: number,
|
|
40
|
+
az: number,
|
|
41
|
+
bx: number,
|
|
42
|
+
bz: number,
|
|
43
|
+
) {
|
|
44
|
+
const dx = bx - ax
|
|
45
|
+
const dz = bz - az
|
|
46
|
+
const lengthSq = dx * dx + dz * dz
|
|
47
|
+
if (lengthSq === 0) return Math.hypot(px - ax, pz - az)
|
|
48
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (pz - az) * dz) / lengthSq))
|
|
49
|
+
return Math.hypot(px - (ax + dx * t), pz - (az + dz * t))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function bridgeDistance(
|
|
53
|
+
px: number,
|
|
54
|
+
pz: number,
|
|
55
|
+
bridge: PlacedBridge,
|
|
56
|
+
): number {
|
|
57
|
+
let nearest = Infinity
|
|
58
|
+
for (let i = 1; i < bridge.points.length; i += 1) {
|
|
59
|
+
const from = bridge.points[i - 1]
|
|
60
|
+
const to = bridge.points[i]
|
|
61
|
+
nearest = Math.min(
|
|
62
|
+
nearest,
|
|
63
|
+
distanceToSegment(px, pz, from[0], from[1], to[0], to[1]),
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
if (!Number.isFinite(nearest) && bridge.points[0]) {
|
|
67
|
+
return Math.hypot(px - bridge.points[0][0], pz - bridge.points[0][1])
|
|
68
|
+
}
|
|
69
|
+
return nearest
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function inFrontOrNearby(
|
|
73
|
+
dx: number,
|
|
74
|
+
dz: number,
|
|
75
|
+
lookX: number,
|
|
76
|
+
lookZ: number,
|
|
77
|
+
): boolean {
|
|
78
|
+
const distSq = dx * dx + dz * dz
|
|
79
|
+
if (distSq <= WALK_AROUND * WALK_AROUND) return true
|
|
80
|
+
const dist = Math.sqrt(distSq)
|
|
81
|
+
return (dx / dist) * lookX + (dz / dist) * lookZ >= WALK_BEHIND_DOT
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function walkLodCell(
|
|
85
|
+
x: number,
|
|
86
|
+
z: number,
|
|
87
|
+
y: number,
|
|
88
|
+
lookX: number,
|
|
89
|
+
lookZ: number,
|
|
90
|
+
) {
|
|
91
|
+
const angle = Math.atan2(lookX, lookZ)
|
|
92
|
+
const bucket = Math.round(angle / (Math.PI / 10))
|
|
93
|
+
const heightBand = Math.round(Math.max(0, y - CONFIG.eyeHeight) / 6)
|
|
94
|
+
return `${Math.round(x / 4)}:${Math.round(z / 4)}:${heightBand}:${bucket}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function sameSet(left: Set<string>, right: Set<string>) {
|
|
98
|
+
if (left.size !== right.size) return false
|
|
99
|
+
for (const id of left) {
|
|
100
|
+
if (!right.has(id)) return false
|
|
101
|
+
}
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function sameWalkLod(left: WalkLod | null, right: WalkLod) {
|
|
106
|
+
if (!left) return false
|
|
107
|
+
return (
|
|
108
|
+
sameSet(left.files, right.files) &&
|
|
109
|
+
sameSet(left.labels, right.labels) &&
|
|
110
|
+
sameSet(left.folders, right.folders) &&
|
|
111
|
+
sameSet(left.folderLabels, right.folderLabels) &&
|
|
112
|
+
sameSet(left.bridges, right.bridges)
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function heightBoost(y: number) {
|
|
117
|
+
return Math.max(0, y - CONFIG.eyeHeight)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function computeWalkLod({
|
|
121
|
+
x,
|
|
122
|
+
z,
|
|
123
|
+
y = CONFIG.eyeHeight,
|
|
124
|
+
lookX,
|
|
125
|
+
lookZ,
|
|
126
|
+
files,
|
|
127
|
+
folders,
|
|
128
|
+
bridges,
|
|
129
|
+
keepFileIds,
|
|
130
|
+
keepFolderPaths,
|
|
131
|
+
prev,
|
|
132
|
+
}: {
|
|
133
|
+
x: number
|
|
134
|
+
z: number
|
|
135
|
+
y?: number
|
|
136
|
+
lookX: number
|
|
137
|
+
lookZ: number
|
|
138
|
+
files: Record<string, PlacedFile>
|
|
139
|
+
folders: Record<string, PlacedFolder>
|
|
140
|
+
bridges: PlacedBridge[]
|
|
141
|
+
keepFileIds: Iterable<string>
|
|
142
|
+
keepFolderPaths: Iterable<string>
|
|
143
|
+
prev: WalkLod | null
|
|
144
|
+
}): WalkLod {
|
|
145
|
+
const boost = heightBoost(y)
|
|
146
|
+
const meshRadius = WALK_MESH_RADIUS + boost * 2.4
|
|
147
|
+
const meshKeep = WALK_MESH_KEEP + boost * 2.4
|
|
148
|
+
const labelRadius = WALK_LABEL_RADIUS + boost * 0.6
|
|
149
|
+
const labelKeep = WALK_LABEL_KEEP + boost * 0.6
|
|
150
|
+
const folderPad = WALK_FOLDER_PAD + boost * 3
|
|
151
|
+
const lookLen = Math.hypot(lookX, lookZ) || 1
|
|
152
|
+
const nx = lookX / lookLen
|
|
153
|
+
const nz = lookZ / lookLen
|
|
154
|
+
|
|
155
|
+
const nextFiles = new Set<string>()
|
|
156
|
+
const nextLabels = new Set<string>()
|
|
157
|
+
for (const id of keepFileIds) nextFiles.add(id)
|
|
158
|
+
|
|
159
|
+
for (const [id, placed] of Object.entries(files)) {
|
|
160
|
+
const dx = placed.position[0] - x
|
|
161
|
+
const dz = placed.position[2] - z
|
|
162
|
+
const dist = Math.hypot(dx, dz)
|
|
163
|
+
const kept = Boolean(prev?.files.has(id))
|
|
164
|
+
const labeled = Boolean(prev?.labels.has(id))
|
|
165
|
+
const meshLimit = kept ? meshKeep : meshRadius
|
|
166
|
+
const labelLimit = labeled ? labelKeep : labelRadius
|
|
167
|
+
if (dist > meshLimit) continue
|
|
168
|
+
if (!inFrontOrNearby(dx, dz, nx, nz) && dist > WALK_AROUND) continue
|
|
169
|
+
nextFiles.add(id)
|
|
170
|
+
if (dist <= labelLimit) nextLabels.add(id)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const nextFolders = new Set<string>()
|
|
174
|
+
const nextFolderLabels = new Set<string>()
|
|
175
|
+
for (const path of keepFolderPaths) nextFolders.add(path)
|
|
176
|
+
|
|
177
|
+
for (const folder of Object.values(folders)) {
|
|
178
|
+
const dist = folderDistance(x, z, folder)
|
|
179
|
+
const kept = Boolean(prev?.folders.has(folder.path))
|
|
180
|
+
const limit = kept ? folderPad + 18 : folderPad
|
|
181
|
+
if (dist > limit && !nextFolders.has(folder.path)) continue
|
|
182
|
+
nextFolders.add(folder.path)
|
|
183
|
+
if (dist <= WALK_FOLDER_LABEL) nextFolderLabels.add(folder.path)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const nextBridges = new Set<string>()
|
|
187
|
+
for (const bridge of bridges) {
|
|
188
|
+
const dist = bridgeDistance(x, z, bridge)
|
|
189
|
+
const kept = Boolean(prev?.bridges.has(bridge.id))
|
|
190
|
+
const limit = kept ? folderPad + 18 : folderPad
|
|
191
|
+
if (dist <= limit) nextBridges.add(bridge.id)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
files: nextFiles,
|
|
196
|
+
labels: nextLabels,
|
|
197
|
+
folders: nextFolders,
|
|
198
|
+
folderLabels: nextFolderLabels,
|
|
199
|
+
bridges: nextBridges,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -214,9 +214,23 @@ function AddIntentRow({
|
|
|
214
214
|
)
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
function AttachStateBadge({ attached }: { attached: boolean }) {
|
|
218
|
+
return (
|
|
219
|
+
<span
|
|
220
|
+
className="hud-attach-badge"
|
|
221
|
+
data-attached={attached}
|
|
222
|
+
aria-label={attached ? 'LLM attached' : 'Waiting for LLM'}
|
|
223
|
+
>
|
|
224
|
+
<span className="hud-attach-dot" aria-hidden="true" />
|
|
225
|
+
{attached ? 'Attached' : 'Waiting'}
|
|
226
|
+
</span>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
217
230
|
function PanelChrome({
|
|
218
231
|
title,
|
|
219
232
|
subtitle,
|
|
233
|
+
badge,
|
|
220
234
|
minimized = false,
|
|
221
235
|
onMinimize,
|
|
222
236
|
onClose,
|
|
@@ -225,6 +239,7 @@ function PanelChrome({
|
|
|
225
239
|
}: {
|
|
226
240
|
title: ReactNode
|
|
227
241
|
subtitle?: ReactNode
|
|
242
|
+
badge?: ReactNode
|
|
228
243
|
minimized?: boolean
|
|
229
244
|
onMinimize?: () => void
|
|
230
245
|
onClose?: () => void
|
|
@@ -234,7 +249,10 @@ function PanelChrome({
|
|
|
234
249
|
return (
|
|
235
250
|
<div className="hud-panel-chrome">
|
|
236
251
|
<div className="hud-panel-chrome-heading">
|
|
237
|
-
<div className="hud-panel-chrome-title">
|
|
252
|
+
<div className="hud-panel-chrome-title-row">
|
|
253
|
+
<div className="hud-panel-chrome-title">{title}</div>
|
|
254
|
+
{badge}
|
|
255
|
+
</div>
|
|
238
256
|
{subtitle ? (
|
|
239
257
|
<div className="hud-panel-chrome-subtitle">{subtitle}</div>
|
|
240
258
|
) : null}
|
|
@@ -305,13 +323,13 @@ function HandshakeSetup({
|
|
|
305
323
|
onInstructionChange,
|
|
306
324
|
blueprintDefined,
|
|
307
325
|
awaitingAttach,
|
|
308
|
-
|
|
326
|
+
nextAttachLabel,
|
|
309
327
|
}: {
|
|
310
328
|
instruction: string
|
|
311
329
|
onInstructionChange: (value: string) => void
|
|
312
330
|
blueprintDefined: boolean
|
|
313
331
|
awaitingAttach: boolean
|
|
314
|
-
|
|
332
|
+
nextAttachLabel: string | null
|
|
315
333
|
}) {
|
|
316
334
|
return (
|
|
317
335
|
<div className="hud-setup">
|
|
@@ -339,10 +357,10 @@ function HandshakeSetup({
|
|
|
339
357
|
</section>
|
|
340
358
|
<section className="hud-setup-section">
|
|
341
359
|
<h2 className="hud-setup-heading">Start</h2>
|
|
342
|
-
{
|
|
360
|
+
{awaitingAttach && nextAttachLabel ? (
|
|
343
361
|
<p>
|
|
344
|
-
|
|
345
|
-
|
|
362
|
+
<kbd>/inbase</kbd> attaches {nextAttachLabel} first. This session
|
|
363
|
+
stays in the queue.
|
|
346
364
|
</p>
|
|
347
365
|
) : awaitingAttach ? (
|
|
348
366
|
<p>
|
|
@@ -350,7 +368,7 @@ function HandshakeSetup({
|
|
|
350
368
|
</p>
|
|
351
369
|
) : (
|
|
352
370
|
<p>
|
|
353
|
-
Starting from <kbd>/inbase</kbd>…
|
|
371
|
+
This window is attached. Starting from <kbd>/inbase</kbd>…
|
|
354
372
|
</p>
|
|
355
373
|
)}
|
|
356
374
|
</section>
|
|
@@ -462,7 +480,7 @@ type SessionPanelProps = {
|
|
|
462
480
|
intent: AgentIntent
|
|
463
481
|
focused: boolean
|
|
464
482
|
naming: boolean
|
|
465
|
-
|
|
483
|
+
nextAttachSession?: AgentIntent | null
|
|
466
484
|
onFocus: () => void
|
|
467
485
|
onWorkflowAction: (
|
|
468
486
|
sessionId: string,
|
|
@@ -476,7 +494,7 @@ function SessionPanel({
|
|
|
476
494
|
intent,
|
|
477
495
|
focused,
|
|
478
496
|
naming,
|
|
479
|
-
|
|
497
|
+
nextAttachSession = null,
|
|
480
498
|
onFocus,
|
|
481
499
|
onWorkflowAction,
|
|
482
500
|
onNavigateDiff,
|
|
@@ -563,11 +581,11 @@ function SessionPanel({
|
|
|
563
581
|
const llmConnected = intent.awaitingAttach === false
|
|
564
582
|
const showConnectedProgress =
|
|
565
583
|
llmConnected && (askingBlueprint || sendingBlueprint || preparing)
|
|
566
|
-
const
|
|
584
|
+
const queuedBehind =
|
|
567
585
|
intent.awaitingAttach &&
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
? sessionLabel(
|
|
586
|
+
nextAttachSession &&
|
|
587
|
+
nextAttachSession.sessionId !== sessionId
|
|
588
|
+
? sessionLabel(nextAttachSession) || 'a newer session'
|
|
571
589
|
: null
|
|
572
590
|
|
|
573
591
|
const act = (
|
|
@@ -584,6 +602,7 @@ function SessionPanel({
|
|
|
584
602
|
}
|
|
585
603
|
data-minimized={minimized}
|
|
586
604
|
data-focused={focused}
|
|
605
|
+
data-attached={llmConnected}
|
|
587
606
|
onPointerDown={onFocus}
|
|
588
607
|
>
|
|
589
608
|
<PanelChrome
|
|
@@ -598,6 +617,7 @@ function SessionPanel({
|
|
|
598
617
|
: reviewTitle(intent.status)
|
|
599
618
|
: undefined
|
|
600
619
|
}
|
|
620
|
+
badge={<AttachStateBadge attached={llmConnected} />}
|
|
601
621
|
minimized={minimized}
|
|
602
622
|
onMinimize={() => setMinimized((current) => !current)}
|
|
603
623
|
onClose={() => act('stop')}
|
|
@@ -637,20 +657,18 @@ function SessionPanel({
|
|
|
637
657
|
onInstructionChange={updateInitialInstruction}
|
|
638
658
|
blueprintDefined={blueprintIsDefined(intent)}
|
|
639
659
|
awaitingAttach={Boolean(intent.awaitingAttach)}
|
|
640
|
-
|
|
660
|
+
nextAttachLabel={queuedBehind}
|
|
641
661
|
/>
|
|
642
662
|
) : intent.awaitingAttach ? (
|
|
643
|
-
|
|
644
|
-
attachedSession.sessionId !== sessionId ? (
|
|
663
|
+
queuedBehind ? (
|
|
645
664
|
<p className="hud-mode-hint">
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
session before running <kbd>/inbase</kbd>.
|
|
665
|
+
<kbd>/inbase</kbd> attaches {queuedBehind} first. This session
|
|
666
|
+
stays in the queue.
|
|
649
667
|
</p>
|
|
650
668
|
) : (
|
|
651
669
|
<p className="hud-mode-hint">
|
|
652
670
|
No LLM is attached. Open a Cursor chat and run{' '}
|
|
653
|
-
<kbd>/inbase</kbd>. It connects to
|
|
671
|
+
<kbd>/inbase</kbd>. It connects to the next waiting session.
|
|
654
672
|
</p>
|
|
655
673
|
)
|
|
656
674
|
) : null}
|
|
@@ -1207,7 +1225,7 @@ function explorerInstructions({
|
|
|
1207
1225
|
{
|
|
1208
1226
|
id: 'setup-session',
|
|
1209
1227
|
keys: ['Setup LLM session'],
|
|
1210
|
-
label: 'Open a session; /inbase
|
|
1228
|
+
label: 'Open a session; /inbase attaches the newest waiting one',
|
|
1211
1229
|
},
|
|
1212
1230
|
{ id: 'toggle-map', keys: ['M'], label: 'Toggle map' },
|
|
1213
1231
|
{
|
|
@@ -1294,7 +1312,7 @@ function explorerInstructions({
|
|
|
1294
1312
|
{
|
|
1295
1313
|
id: 'setup-session',
|
|
1296
1314
|
keys: ['Setup LLM session'],
|
|
1297
|
-
label: 'Open a session; /inbase
|
|
1315
|
+
label: 'Open a session; /inbase attaches the newest waiting one',
|
|
1298
1316
|
},
|
|
1299
1317
|
{ id: 'map-walk', keys: ['M'], label: 'Back to walk' },
|
|
1300
1318
|
...stop,
|
|
@@ -1318,6 +1336,7 @@ type HUDProps = {
|
|
|
1318
1336
|
intent: AgentIntent
|
|
1319
1337
|
intents?: AgentIntent[]
|
|
1320
1338
|
focusedSessionId?: string | null
|
|
1339
|
+
nextAttachSessionId?: string | null
|
|
1321
1340
|
onFocusSession?: (sessionId: string) => void
|
|
1322
1341
|
onSetupSession?: () => Promise<unknown>
|
|
1323
1342
|
onWorkflowAction: (
|
|
@@ -1383,6 +1402,7 @@ export function HUD({
|
|
|
1383
1402
|
intent,
|
|
1384
1403
|
intents,
|
|
1385
1404
|
focusedSessionId = null,
|
|
1405
|
+
nextAttachSessionId = null,
|
|
1386
1406
|
onFocusSession,
|
|
1387
1407
|
onSetupSession,
|
|
1388
1408
|
onWorkflowAction,
|
|
@@ -1444,8 +1464,10 @@ export function HUD({
|
|
|
1444
1464
|
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
1445
1465
|
)
|
|
1446
1466
|
const canStop = canStopSession(intent)
|
|
1447
|
-
const
|
|
1448
|
-
sessions.find((session) => session.
|
|
1467
|
+
const nextAttachSession =
|
|
1468
|
+
sessions.find((session) => session.sessionId === nextAttachSessionId) ??
|
|
1469
|
+
[...sessions].reverse().find((session) => session.awaitingAttach) ??
|
|
1470
|
+
null
|
|
1449
1471
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
1450
1472
|
const walkIntroSeen = useRef(false)
|
|
1451
1473
|
const [instructionsOpen, setInstructionsOpen] = useState(false)
|
|
@@ -1783,6 +1805,8 @@ export function HUD({
|
|
|
1783
1805
|
{sessions.map((session) => {
|
|
1784
1806
|
const active =
|
|
1785
1807
|
session.sessionId === (focusedSessionId ?? intent.sessionId)
|
|
1808
|
+
const attached = session.awaitingAttach === false
|
|
1809
|
+
const label = sessionTabLabel(session, sessions)
|
|
1786
1810
|
return (
|
|
1787
1811
|
<button
|
|
1788
1812
|
className="hud-button hud-session-tab"
|
|
@@ -1790,13 +1814,15 @@ export function HUD({
|
|
|
1790
1814
|
role="tab"
|
|
1791
1815
|
aria-selected={active}
|
|
1792
1816
|
data-active={active}
|
|
1817
|
+
data-attached={attached}
|
|
1793
1818
|
key={session.sessionId}
|
|
1794
|
-
title={
|
|
1819
|
+
title={attached ? `${label} · Attached` : `${label} · Waiting`}
|
|
1795
1820
|
onClick={() => {
|
|
1796
1821
|
if (session.sessionId) onFocusSession?.(session.sessionId)
|
|
1797
1822
|
}}
|
|
1798
1823
|
>
|
|
1799
|
-
|
|
1824
|
+
<span className="hud-attach-dot" aria-hidden="true" />
|
|
1825
|
+
<span className="hud-session-tab-label">{label}</span>
|
|
1800
1826
|
</button>
|
|
1801
1827
|
)
|
|
1802
1828
|
})}
|
|
@@ -1807,7 +1833,7 @@ export function HUD({
|
|
|
1807
1833
|
intent={intent}
|
|
1808
1834
|
focused
|
|
1809
1835
|
naming={naming}
|
|
1810
|
-
|
|
1836
|
+
nextAttachSession={nextAttachSession}
|
|
1811
1837
|
onFocus={() => {
|
|
1812
1838
|
if (intent.sessionId) onFocusSession?.(intent.sessionId)
|
|
1813
1839
|
}}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
inspectTargetFile,
|
|
18
18
|
invokeStep,
|
|
19
19
|
listSessionIntents,
|
|
20
|
+
nextAttachSessionId,
|
|
20
21
|
readActiveSession,
|
|
21
22
|
requestReplan,
|
|
22
23
|
sendBlueprint,
|
|
@@ -142,6 +143,7 @@ function jsonFilePlugin(): Plugin {
|
|
|
142
143
|
}
|
|
143
144
|
sendJson(res, 200, {
|
|
144
145
|
focusedSessionId: readActiveSession(dataDir),
|
|
146
|
+
nextAttachSessionId: nextAttachSessionId(dataDir),
|
|
145
147
|
intents: listSessionIntents(dataDir, knownFileIds()),
|
|
146
148
|
})
|
|
147
149
|
return
|
package/bin/session.mjs
CHANGED
|
@@ -179,7 +179,7 @@ export async function attachSession(args) {
|
|
|
179
179
|
console.log(`VISUAL_CODER_SESSION ${manifest.sessionId}`)
|
|
180
180
|
printAck('attached', manifest.name || manifest.sessionId)
|
|
181
181
|
console.log(
|
|
182
|
-
`VISUAL_CODER_ATTACHED Attached to the
|
|
182
|
+
`VISUAL_CODER_ATTACHED Attached to the next waiting visualizer session ${manifest.name || manifest.sessionId} (${manifest.phase}). Use --session ${manifest.sessionId} for every later command. Run inbase wait-for-blueprint --session ${manifest.sessionId} to read the optional blueprint and instruction; it does not wait.`,
|
|
183
183
|
)
|
|
184
184
|
}
|
|
185
185
|
|
package/package.json
CHANGED
package/skill/commands/inbase.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Attach this chat to the
|
|
2
|
+
description: Attach this chat to the next waiting Inbase visualizer session
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
The user invoked `/inbase`. This is how a chat joins the visualizer session that **Setup LLM session** already created.
|
|
6
6
|
|
|
7
7
|
Do **not** ask for a session id. Do **not** run `inbase start-session`. Do **not** print the "direct chat interaction not allowed" message.
|
|
8
8
|
|
|
9
|
-
1. Attach to the
|
|
9
|
+
1. Attach to the next waiting visualizer session (newest first; skip sessions that already have an LLM):
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx inbase attach
|
package/skill/inbase/SKILL.md
CHANGED
|
@@ -57,7 +57,8 @@ If this chat is not yet attached, run:
|
|
|
57
57
|
npx inbase attach
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
That attaches this chat to the
|
|
60
|
+
That attaches this chat to the next waiting visualizer session (newest first).
|
|
61
|
+
Already-attached sessions are skipped. Window focus does not matter. No id is
|
|
61
62
|
passed in; read `VISUAL_CODER_SESSION` from the output and use that
|
|
62
63
|
`--session` value for every later command. Then continue from
|
|
63
64
|
`wait-for-blueprint` below. Do **not** run `start-session`. Do **not** wait
|