@jkwd/inbase 0.1.9 → 0.1.11
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 +23 -7
- 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 +6 -0
- package/apps/explorer/scripts/patch-lib.mjs +6 -0
- package/apps/explorer/scripts/scan-target.mjs +46 -13
- package/apps/explorer/scripts/session-store.d.ts +64 -1
- package/apps/explorer/scripts/session-store.mjs +369 -88
- package/apps/explorer/scripts/tree-diff.mjs +121 -0
- package/apps/explorer/src/App.tsx +215 -86
- package/apps/explorer/src/agentIntent.ts +78 -0
- package/apps/explorer/src/branchChanges.ts +54 -0
- package/apps/explorer/src/index.css +152 -10
- package/apps/explorer/src/scene/FileBlock.tsx +55 -5
- package/apps/explorer/src/types.ts +46 -0
- package/apps/explorer/src/ui/HUD.tsx +670 -158
- package/apps/explorer/src/userContext.ts +11 -0
- package/apps/explorer/vite.config.ts +65 -5
- package/bin/inbase.mjs +23 -4
- package/bin/project.mjs +62 -4
- package/bin/session.mjs +215 -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 +142 -89
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
|
+
import { persistInitialInstruction } from '../agentIntent'
|
|
2
3
|
import { NameInput } from './NameInput'
|
|
3
4
|
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
4
5
|
import {
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
type AgentIntent,
|
|
8
9
|
type AgentIntentStatus,
|
|
9
10
|
type AimedRelation,
|
|
11
|
+
type BranchChanges,
|
|
10
12
|
type CodebaseGraph,
|
|
11
13
|
type PatchImportAddition,
|
|
12
14
|
type PatchSymbolAddition,
|
|
@@ -29,6 +31,20 @@ function reviewTitle(status: AgentIntentStatus) {
|
|
|
29
31
|
return 'Visual workflow'
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
function sessionLabel(intent: AgentIntent) {
|
|
35
|
+
return intent.name?.trim() || intent.feature?.trim() || ''
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function sessionTabLabel(intent: AgentIntent, sessions: AgentIntent[]) {
|
|
39
|
+
const title = sessionLabel(intent) || reviewTitle(intent.status)
|
|
40
|
+
const same = sessions.filter(
|
|
41
|
+
(item) => (sessionLabel(item) || reviewTitle(item.status)) === title,
|
|
42
|
+
)
|
|
43
|
+
if (same.length < 2) return title
|
|
44
|
+
const id = intent.sessionId ?? ''
|
|
45
|
+
return `${title} · ${id.slice(-4)}`
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
function fileBase(id: string) {
|
|
33
49
|
return id.split('/').pop() ?? id
|
|
34
50
|
}
|
|
@@ -200,18 +216,29 @@ function AddIntentRow({
|
|
|
200
216
|
|
|
201
217
|
function PanelChrome({
|
|
202
218
|
title,
|
|
219
|
+
subtitle,
|
|
203
220
|
minimized = false,
|
|
204
221
|
onMinimize,
|
|
205
222
|
onClose,
|
|
223
|
+
closeLabel = 'Close',
|
|
224
|
+
closeReject = false,
|
|
206
225
|
}: {
|
|
207
226
|
title: ReactNode
|
|
227
|
+
subtitle?: ReactNode
|
|
208
228
|
minimized?: boolean
|
|
209
229
|
onMinimize?: () => void
|
|
210
230
|
onClose?: () => void
|
|
231
|
+
closeLabel?: string
|
|
232
|
+
closeReject?: boolean
|
|
211
233
|
}) {
|
|
212
234
|
return (
|
|
213
235
|
<div className="hud-panel-chrome">
|
|
214
|
-
<div className="hud-panel-chrome-
|
|
236
|
+
<div className="hud-panel-chrome-heading">
|
|
237
|
+
<div className="hud-panel-chrome-title">{title}</div>
|
|
238
|
+
{subtitle ? (
|
|
239
|
+
<div className="hud-panel-chrome-subtitle">{subtitle}</div>
|
|
240
|
+
) : null}
|
|
241
|
+
</div>
|
|
215
242
|
<div className="hud-panel-controls">
|
|
216
243
|
{onMinimize && (
|
|
217
244
|
<button
|
|
@@ -225,9 +252,13 @@ function PanelChrome({
|
|
|
225
252
|
)}
|
|
226
253
|
{onClose && (
|
|
227
254
|
<button
|
|
228
|
-
className=
|
|
255
|
+
className={
|
|
256
|
+
closeReject
|
|
257
|
+
? 'hud-button hud-icon-button hud-panel-control hud-button-reject'
|
|
258
|
+
: 'hud-button hud-icon-button hud-panel-control'
|
|
259
|
+
}
|
|
229
260
|
type="button"
|
|
230
|
-
aria-label=
|
|
261
|
+
aria-label={closeLabel}
|
|
231
262
|
onClick={onClose}
|
|
232
263
|
>
|
|
233
264
|
×
|
|
@@ -238,10 +269,200 @@ function PanelChrome({
|
|
|
238
269
|
)
|
|
239
270
|
}
|
|
240
271
|
|
|
272
|
+
function InitialInstructionField({
|
|
273
|
+
value,
|
|
274
|
+
onChange,
|
|
275
|
+
}: {
|
|
276
|
+
value: string
|
|
277
|
+
onChange: (value: string) => void
|
|
278
|
+
}) {
|
|
279
|
+
return (
|
|
280
|
+
<label className="hud-instruction">
|
|
281
|
+
<textarea
|
|
282
|
+
value={value}
|
|
283
|
+
maxLength={4000}
|
|
284
|
+
rows={4}
|
|
285
|
+
placeholder="What should the LLM build?"
|
|
286
|
+
onChange={(event) => onChange(event.target.value)}
|
|
287
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
288
|
+
/>
|
|
289
|
+
</label>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function blueprintIsDefined(intent: AgentIntent) {
|
|
294
|
+
return (
|
|
295
|
+
(intent.userCreatedBlocks?.length ?? 0) > 0 ||
|
|
296
|
+
(intent.userCreatedIslands?.length ?? 0) > 0 ||
|
|
297
|
+
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
298
|
+
(intent.blueprintVariables?.length ?? 0) > 0 ||
|
|
299
|
+
(intent.blueprintImports?.length ?? 0) > 0
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function HandshakeSetup({
|
|
304
|
+
instruction,
|
|
305
|
+
onInstructionChange,
|
|
306
|
+
blueprintDefined,
|
|
307
|
+
awaitingAttach,
|
|
308
|
+
attachBlockedBy,
|
|
309
|
+
}: {
|
|
310
|
+
instruction: string
|
|
311
|
+
onInstructionChange: (value: string) => void
|
|
312
|
+
blueprintDefined: boolean
|
|
313
|
+
awaitingAttach: boolean
|
|
314
|
+
attachBlockedBy: string | null
|
|
315
|
+
}) {
|
|
316
|
+
return (
|
|
317
|
+
<div className="hud-setup">
|
|
318
|
+
<section className="hud-setup-section">
|
|
319
|
+
<h2 className="hud-setup-heading">Instructions</h2>
|
|
320
|
+
<InitialInstructionField
|
|
321
|
+
value={instruction}
|
|
322
|
+
onChange={onInstructionChange}
|
|
323
|
+
/>
|
|
324
|
+
</section>
|
|
325
|
+
<section className="hud-setup-section">
|
|
326
|
+
<h2 className="hud-setup-heading">
|
|
327
|
+
Blueprint
|
|
328
|
+
<span
|
|
329
|
+
className="hud-setup-tag"
|
|
330
|
+
data-ready={blueprintDefined ? 'true' : 'false'}
|
|
331
|
+
>
|
|
332
|
+
{blueprintDefined ? 'blueprint defined' : 'no blueprint'}
|
|
333
|
+
</span>
|
|
334
|
+
</h2>
|
|
335
|
+
<p>
|
|
336
|
+
Press <kbd>Space</kbd> for a file and <kbd>B</kbd> for an island.
|
|
337
|
+
Optional.
|
|
338
|
+
</p>
|
|
339
|
+
</section>
|
|
340
|
+
<section className="hud-setup-section">
|
|
341
|
+
<h2 className="hud-setup-heading">Start</h2>
|
|
342
|
+
{attachBlockedBy ? (
|
|
343
|
+
<p>
|
|
344
|
+
An LLM is already attached to {attachBlockedBy}. Stop that session
|
|
345
|
+
before running <kbd>/inbase</kbd>.
|
|
346
|
+
</p>
|
|
347
|
+
) : awaitingAttach ? (
|
|
348
|
+
<p>
|
|
349
|
+
Run <kbd>/inbase</kbd> in a Cursor chat to connect and start.
|
|
350
|
+
</p>
|
|
351
|
+
) : (
|
|
352
|
+
<p>
|
|
353
|
+
Starting from <kbd>/inbase</kbd>…
|
|
354
|
+
</p>
|
|
355
|
+
)}
|
|
356
|
+
</section>
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function sessionLiveStatus(intent: AgentIntent) {
|
|
362
|
+
const ack = intent.lastAck
|
|
363
|
+
const kind = ack?.kind
|
|
364
|
+
const detail = ack?.detail?.trim() || ''
|
|
365
|
+
|
|
366
|
+
if (intent.status === 'finished' || kind === 'finished') {
|
|
367
|
+
return { text: 'Finished', busy: false }
|
|
368
|
+
}
|
|
369
|
+
if (kind === 'stopped' || intent.status === 'rejected') {
|
|
370
|
+
return { text: 'Stopped', busy: false }
|
|
371
|
+
}
|
|
372
|
+
if (kind === 'timeout') {
|
|
373
|
+
return { text: 'LLM wait timed out', busy: false }
|
|
374
|
+
}
|
|
375
|
+
if (intent.awaitingAttach) {
|
|
376
|
+
return { text: 'Waiting for /inbase in Cursor', busy: true }
|
|
377
|
+
}
|
|
378
|
+
if (kind === 'execute') {
|
|
379
|
+
return { text: `LLM received ${detail}`, busy: true }
|
|
380
|
+
}
|
|
381
|
+
if (kind === 'invoke') {
|
|
382
|
+
return { text: `Sent ${detail} — waiting for LLM`, busy: true }
|
|
383
|
+
}
|
|
384
|
+
if (kind === 'replan') {
|
|
385
|
+
return { text: 'LLM received a new instruction', busy: true }
|
|
386
|
+
}
|
|
387
|
+
if (intent.status === 'working') {
|
|
388
|
+
return {
|
|
389
|
+
text: intent.reason
|
|
390
|
+
? `LLM is working on ${intent.reason}`
|
|
391
|
+
: 'LLM is working',
|
|
392
|
+
busy: true,
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (intent.status === 'replanning') {
|
|
396
|
+
return { text: 'LLM is revising the plan', busy: true }
|
|
397
|
+
}
|
|
398
|
+
if (intent.status === 'preparing' || kind === 'blueprint') {
|
|
399
|
+
return { text: 'LLM is drafting the plan', busy: true }
|
|
400
|
+
}
|
|
401
|
+
if (kind === 'plan' || intent.status === 'planned') {
|
|
402
|
+
return intent.listening
|
|
403
|
+
? { text: 'LLM is listening — run the next step', busy: false }
|
|
404
|
+
: { text: 'Plan ready', busy: false }
|
|
405
|
+
}
|
|
406
|
+
if (intent.status === 'pending') {
|
|
407
|
+
return intent.listening
|
|
408
|
+
? { text: 'LLM is listening — accept or send an instruction', busy: false }
|
|
409
|
+
: { text: 'Review this step', busy: false }
|
|
410
|
+
}
|
|
411
|
+
if (
|
|
412
|
+
kind === 'attached' ||
|
|
413
|
+
intent.status === 'blueprint' ||
|
|
414
|
+
intent.status === 'blueprint_ask'
|
|
415
|
+
) {
|
|
416
|
+
return { text: 'LLM attached', busy: true }
|
|
417
|
+
}
|
|
418
|
+
if (intent.llmIdle) {
|
|
419
|
+
return { text: 'LLM is idle', busy: false }
|
|
420
|
+
}
|
|
421
|
+
return { text: 'LLM connected', busy: true }
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function LiveStatus({
|
|
425
|
+
intent,
|
|
426
|
+
showStop = false,
|
|
427
|
+
onStop,
|
|
428
|
+
}: {
|
|
429
|
+
intent: AgentIntent
|
|
430
|
+
showStop?: boolean
|
|
431
|
+
onStop?: () => void
|
|
432
|
+
}) {
|
|
433
|
+
const status = sessionLiveStatus(intent)
|
|
434
|
+
const [flash, setFlash] = useState(false)
|
|
435
|
+
const lastAt = intent.lastAck?.at
|
|
436
|
+
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
if (!lastAt) return
|
|
439
|
+
setFlash(true)
|
|
440
|
+
const timer = window.setTimeout(() => setFlash(false), 700)
|
|
441
|
+
return () => window.clearTimeout(timer)
|
|
442
|
+
}, [lastAt])
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<div className="hud-live" data-busy={status.busy} data-flash={flash}>
|
|
446
|
+
{status.busy ? <span className="hud-spinner" aria-hidden="true" /> : null}
|
|
447
|
+
<span>{status.text}</span>
|
|
448
|
+
{showStop && onStop ? (
|
|
449
|
+
<button
|
|
450
|
+
className="hud-button hud-button-reject"
|
|
451
|
+
type="button"
|
|
452
|
+
onClick={onStop}
|
|
453
|
+
>
|
|
454
|
+
Stop
|
|
455
|
+
</button>
|
|
456
|
+
) : null}
|
|
457
|
+
</div>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
241
461
|
type SessionPanelProps = {
|
|
242
462
|
intent: AgentIntent
|
|
243
463
|
focused: boolean
|
|
244
464
|
naming: boolean
|
|
465
|
+
attachedSession?: AgentIntent | null
|
|
245
466
|
onFocus: () => void
|
|
246
467
|
onWorkflowAction: (
|
|
247
468
|
sessionId: string,
|
|
@@ -255,16 +476,21 @@ function SessionPanel({
|
|
|
255
476
|
intent,
|
|
256
477
|
focused,
|
|
257
478
|
naming,
|
|
479
|
+
attachedSession = null,
|
|
258
480
|
onFocus,
|
|
259
481
|
onWorkflowAction,
|
|
260
482
|
onNavigateDiff,
|
|
261
483
|
}: SessionPanelProps) {
|
|
262
484
|
const [minimized, setMinimized] = useState(false)
|
|
263
485
|
const [instruction, setInstruction] = useState('')
|
|
486
|
+
const [initialInstruction, setInitialInstruction] = useState(
|
|
487
|
+
() => intent.initialInstruction ?? '',
|
|
488
|
+
)
|
|
264
489
|
const sessionId = intent.sessionId
|
|
265
490
|
const pending = intent.status === 'pending' && intent.isActiveDiff
|
|
266
491
|
const askingBlueprint = intent.status === 'blueprint_ask'
|
|
267
|
-
const
|
|
492
|
+
const sendingBlueprint = intent.status === 'blueprint'
|
|
493
|
+
const canPlace = Boolean(intent.creationMode)
|
|
268
494
|
const preparing = intent.status === 'preparing'
|
|
269
495
|
const planReady = intent.status === 'planned'
|
|
270
496
|
const working = intent.status === 'working' || intent.status === 'replanning'
|
|
@@ -286,7 +512,7 @@ function SessionPanel({
|
|
|
286
512
|
const addedImports = intent.addedImports ?? []
|
|
287
513
|
const changedFunctions = intent.changedFunctions ?? []
|
|
288
514
|
const changedVariables = intent.changedVariables ?? []
|
|
289
|
-
const
|
|
515
|
+
const acceptedSteps = new Set(
|
|
290
516
|
intent.status === 'finished'
|
|
291
517
|
? intent.steps.map((step) => step.index)
|
|
292
518
|
: intent.chain
|
|
@@ -297,29 +523,53 @@ function SessionPanel({
|
|
|
297
523
|
.map((entry) => entry.step),
|
|
298
524
|
)
|
|
299
525
|
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
if (pending && typeof intent.step === 'number' && !lastStep) {
|
|
303
|
-
doneSteps.add(intent.step)
|
|
526
|
+
acceptedSteps.add(intent.step)
|
|
304
527
|
}
|
|
305
|
-
const
|
|
306
|
-
intent.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
528
|
+
const proposalStep =
|
|
529
|
+
pending && typeof intent.step === 'number' ? intent.step : null
|
|
530
|
+
const processingStep =
|
|
531
|
+
working && typeof intent.step === 'number' ? intent.step : null
|
|
532
|
+
const invokeStep =
|
|
533
|
+
planReady && !working
|
|
534
|
+
? (intent.steps.find((step) => !acceptedSteps.has(step.index)) ?? null)
|
|
535
|
+
: null
|
|
536
|
+
const canRunNext = stepByStep && Boolean(invokeStep)
|
|
537
|
+
const canAcceptProposal = proposalStep !== null
|
|
312
538
|
const panelDone =
|
|
313
539
|
intent.status === 'finished' ||
|
|
314
540
|
intent.status === 'approved' ||
|
|
315
|
-
|
|
541
|
+
acceptedSteps.size > 0 ||
|
|
542
|
+
canAcceptProposal
|
|
316
543
|
|
|
317
544
|
useEffect(() => {
|
|
318
545
|
setInstruction('')
|
|
319
546
|
}, [intent.diffId])
|
|
320
547
|
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
setInitialInstruction(intent.initialInstruction ?? '')
|
|
550
|
+
}, [sessionId])
|
|
551
|
+
|
|
321
552
|
if (!sessionId || !isReviewingIntent(intent.status)) return null
|
|
322
553
|
|
|
554
|
+
const updateInitialInstruction = (value: string) => {
|
|
555
|
+
setInitialInstruction(value)
|
|
556
|
+
persistInitialInstruction(sessionId, value)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const showInitialInstruction =
|
|
560
|
+
Boolean(intent.awaitingAttach) &&
|
|
561
|
+
(askingBlueprint || sendingBlueprint || preparing)
|
|
562
|
+
const handshakeSetup = showInitialInstruction
|
|
563
|
+
const llmConnected = intent.awaitingAttach === false
|
|
564
|
+
const showConnectedProgress =
|
|
565
|
+
llmConnected && (askingBlueprint || sendingBlueprint || preparing)
|
|
566
|
+
const attachBlockedBy =
|
|
567
|
+
intent.awaitingAttach &&
|
|
568
|
+
attachedSession &&
|
|
569
|
+
attachedSession.sessionId !== sessionId
|
|
570
|
+
? sessionLabel(attachedSession) || 'another session'
|
|
571
|
+
: null
|
|
572
|
+
|
|
323
573
|
const act = (
|
|
324
574
|
action: WorkflowAction,
|
|
325
575
|
options?: { instruction?: string; step?: number; stepByStep?: boolean },
|
|
@@ -337,12 +587,30 @@ function SessionPanel({
|
|
|
337
587
|
onPointerDown={onFocus}
|
|
338
588
|
>
|
|
339
589
|
<PanelChrome
|
|
340
|
-
title={
|
|
590
|
+
title={
|
|
591
|
+
sessionLabel(intent) ||
|
|
592
|
+
(showConnectedProgress ? 'LLM connected' : reviewTitle(intent.status))
|
|
593
|
+
}
|
|
594
|
+
subtitle={
|
|
595
|
+
sessionLabel(intent)
|
|
596
|
+
? showConnectedProgress
|
|
597
|
+
? 'LLM connected'
|
|
598
|
+
: reviewTitle(intent.status)
|
|
599
|
+
: undefined
|
|
600
|
+
}
|
|
341
601
|
minimized={minimized}
|
|
342
602
|
onMinimize={() => setMinimized((current) => !current)}
|
|
603
|
+
onClose={() => act('stop')}
|
|
604
|
+
closeLabel="Stop session"
|
|
605
|
+
closeReject
|
|
343
606
|
/>
|
|
344
607
|
{!minimized && (
|
|
345
608
|
<>
|
|
609
|
+
<LiveStatus
|
|
610
|
+
intent={intent}
|
|
611
|
+
showStop={showConnectedProgress || working}
|
|
612
|
+
onStop={() => act('stop')}
|
|
613
|
+
/>
|
|
346
614
|
<label className="hud-mode-switch">
|
|
347
615
|
<span>Step by step</span>
|
|
348
616
|
<button
|
|
@@ -358,22 +626,48 @@ function SessionPanel({
|
|
|
358
626
|
{!stepByStep && (
|
|
359
627
|
<p className="hud-mode-hint">
|
|
360
628
|
LLM implements the full plan. You can still walk the diffs, then
|
|
361
|
-
|
|
629
|
+
Accept proposal.
|
|
362
630
|
</p>
|
|
363
631
|
)}
|
|
364
|
-
{
|
|
365
|
-
|
|
632
|
+
{handshakeSetup ? (
|
|
633
|
+
<HandshakeSetup
|
|
634
|
+
instruction={initialInstruction}
|
|
635
|
+
onInstructionChange={updateInitialInstruction}
|
|
636
|
+
blueprintDefined={blueprintIsDefined(intent)}
|
|
637
|
+
awaitingAttach={Boolean(intent.awaitingAttach)}
|
|
638
|
+
attachBlockedBy={attachBlockedBy || null}
|
|
639
|
+
/>
|
|
640
|
+
) : intent.awaitingAttach ? (
|
|
641
|
+
attachedSession &&
|
|
642
|
+
attachedSession.sessionId !== sessionId ? (
|
|
643
|
+
<p className="hud-mode-hint">
|
|
644
|
+
An LLM is already attached to{' '}
|
|
645
|
+
{sessionLabel(attachedSession) || 'another session'}. Stop that
|
|
646
|
+
session before running <kbd>/inbase</kbd>.
|
|
647
|
+
</p>
|
|
648
|
+
) : (
|
|
649
|
+
<p className="hud-mode-hint">
|
|
650
|
+
No LLM is attached. Open a Cursor chat and run{' '}
|
|
651
|
+
<kbd>/inbase</kbd>. It connects to this focused session.
|
|
652
|
+
</p>
|
|
653
|
+
)
|
|
654
|
+
) : null}
|
|
655
|
+
{intent.feature &&
|
|
656
|
+
!handshakeSetup &&
|
|
657
|
+
intent.feature.trim() !== sessionLabel(intent) && (
|
|
658
|
+
<p className="hud-feature">{intent.feature}</p>
|
|
659
|
+
)}
|
|
660
|
+
{askingBlueprint && !handshakeSetup && !showConnectedProgress ? (
|
|
366
661
|
<>
|
|
367
662
|
<p>
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
663
|
+
Place files and folders for this chat, then send them as a
|
|
664
|
+
blueprint for the LLM? You can still add files and islands on
|
|
665
|
+
later steps.
|
|
371
666
|
</p>
|
|
372
667
|
<div className="hud-decide">
|
|
373
668
|
<button
|
|
374
669
|
className="hud-button hud-button-approve"
|
|
375
670
|
type="button"
|
|
376
|
-
disabled={!intent.canEnterBlueprint}
|
|
377
671
|
onClick={() => act('blueprint_yes')}
|
|
378
672
|
>
|
|
379
673
|
Create blueprint
|
|
@@ -394,113 +688,71 @@ function SessionPanel({
|
|
|
394
688
|
</button>
|
|
395
689
|
</div>
|
|
396
690
|
</>
|
|
397
|
-
) :
|
|
398
|
-
<>
|
|
399
|
-
<p>
|
|
400
|
-
Walk the map, press <kbd>Space</kbd> for a file and{' '}
|
|
401
|
-
<kbd>B</kbd> for an island. Send the blueprint when the layout
|
|
402
|
-
is ready.
|
|
403
|
-
</p>
|
|
404
|
-
<div className="hud-decide">
|
|
405
|
-
<button
|
|
406
|
-
className="hud-button hud-button-approve"
|
|
407
|
-
type="button"
|
|
408
|
-
disabled={naming}
|
|
409
|
-
onClick={() => act('blueprint_send')}
|
|
410
|
-
>
|
|
411
|
-
Send blueprint
|
|
412
|
-
</button>
|
|
413
|
-
<button
|
|
414
|
-
className="hud-button hud-button-reject"
|
|
415
|
-
type="button"
|
|
416
|
-
onClick={() => act('stop')}
|
|
417
|
-
>
|
|
418
|
-
Stop
|
|
419
|
-
</button>
|
|
420
|
-
</div>
|
|
421
|
-
</>
|
|
422
|
-
) : intent.status === 'finished' ? (
|
|
691
|
+
) : handshakeSetup ? null : intent.status === 'finished' ? (
|
|
423
692
|
<p>All plan steps were applied.</p>
|
|
424
|
-
) : preparing ? (
|
|
425
|
-
<div className="hud-working">
|
|
426
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
427
|
-
<span>LLM preparing…</span>
|
|
428
|
-
<button
|
|
429
|
-
className="hud-button hud-button-reject"
|
|
430
|
-
type="button"
|
|
431
|
-
onClick={() => act('stop')}
|
|
432
|
-
>
|
|
433
|
-
Stop
|
|
434
|
-
</button>
|
|
435
|
-
</div>
|
|
436
|
-
) : working ? (
|
|
437
|
-
<div className="hud-working">
|
|
438
|
-
<span className="hud-spinner" aria-hidden="true" />
|
|
439
|
-
<span>
|
|
440
|
-
{intent.status === 'replanning'
|
|
441
|
-
? 'Updating the remaining plan from your instruction…'
|
|
442
|
-
: intent.stalledWait
|
|
443
|
-
? 'The LLM is still waiting on this step…'
|
|
444
|
-
: `Implementing ${stepLabel.toLowerCase()}…`}
|
|
445
|
-
</span>
|
|
446
|
-
<button
|
|
447
|
-
className="hud-button hud-button-reject"
|
|
448
|
-
type="button"
|
|
449
|
-
onClick={() => act('stop')}
|
|
450
|
-
>
|
|
451
|
-
Stop
|
|
452
|
-
</button>
|
|
453
|
-
</div>
|
|
454
|
-
) : (
|
|
693
|
+
) : showConnectedProgress || preparing ? null : (
|
|
455
694
|
<p>
|
|
456
695
|
{stepLabel}
|
|
457
696
|
{intent.reason ? ` · ${intent.reason}` : ''}
|
|
458
697
|
</p>
|
|
459
698
|
)}
|
|
460
|
-
{!askingBlueprint && !
|
|
699
|
+
{!askingBlueprint && !sendingBlueprint && (
|
|
461
700
|
<>
|
|
701
|
+
{canPlace && !intent.working && (
|
|
702
|
+
<p>
|
|
703
|
+
<kbd>Space</kbd> places a file, <kbd>B</kbd> an island for
|
|
704
|
+
this session.
|
|
705
|
+
</p>
|
|
706
|
+
)}
|
|
462
707
|
{intent.steps?.length > 0 && (
|
|
463
708
|
<ol className="hud-steps">
|
|
464
|
-
{intent.steps.map((step) =>
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
<span className="hud-step-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
709
|
+
{intent.steps.map((step) => {
|
|
710
|
+
const proposed = proposalStep === step.index
|
|
711
|
+
const processing = processingStep === step.index
|
|
712
|
+
const accepted = acceptedSteps.has(step.index)
|
|
713
|
+
const creating = processing && !proposed
|
|
714
|
+
const showStepAction =
|
|
715
|
+
creating ||
|
|
716
|
+
(canRunNext && invokeStep?.index === step.index) ||
|
|
717
|
+
(canAcceptProposal && proposed)
|
|
718
|
+
return (
|
|
719
|
+
<li
|
|
720
|
+
key={step.index}
|
|
721
|
+
data-done={accepted || proposed}
|
|
722
|
+
data-active={processing || proposed}
|
|
723
|
+
>
|
|
724
|
+
<span className="hud-step-index">{step.index}.</span>
|
|
725
|
+
<span className="hud-step-main">
|
|
726
|
+
<span className="hud-step-title">{step.title}</span>
|
|
727
|
+
{showStepAction && (
|
|
728
|
+
<button
|
|
729
|
+
className="hud-button hud-button-approve hud-run-step"
|
|
730
|
+
type="button"
|
|
731
|
+
disabled={creating}
|
|
732
|
+
aria-busy={creating}
|
|
733
|
+
onClick={() =>
|
|
734
|
+
proposed
|
|
735
|
+
? lastStep
|
|
736
|
+
? act('continue')
|
|
737
|
+
: act('invoke', {
|
|
738
|
+
step: step.index + 1,
|
|
739
|
+
})
|
|
740
|
+
: act('invoke', {
|
|
741
|
+
step: step.index,
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
>
|
|
745
|
+
{proposed
|
|
746
|
+
? 'Accept proposal'
|
|
747
|
+
: creating
|
|
748
|
+
? 'Creating proposal…'
|
|
749
|
+
: 'Create proposal'}
|
|
750
|
+
</button>
|
|
751
|
+
)}
|
|
752
|
+
</span>
|
|
753
|
+
</li>
|
|
754
|
+
)
|
|
755
|
+
})}
|
|
504
756
|
</ol>
|
|
505
757
|
)}
|
|
506
758
|
{intent.chain.length > 0 && (
|
|
@@ -658,18 +910,10 @@ function SessionPanel({
|
|
|
658
910
|
rows={3}
|
|
659
911
|
placeholder="Describe what should change in the next diff…"
|
|
660
912
|
onChange={(event) => setInstruction(event.target.value)}
|
|
913
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
661
914
|
/>
|
|
662
915
|
</label>
|
|
663
916
|
<div className="hud-decide">
|
|
664
|
-
{lastStep && (
|
|
665
|
-
<button
|
|
666
|
-
className="hud-button hud-button-approve"
|
|
667
|
-
type="button"
|
|
668
|
-
onClick={() => act('continue')}
|
|
669
|
-
>
|
|
670
|
-
Complete
|
|
671
|
-
</button>
|
|
672
|
-
)}
|
|
673
917
|
<button
|
|
674
918
|
className="hud-button hud-button-extend"
|
|
675
919
|
type="button"
|
|
@@ -698,6 +942,131 @@ function SessionPanel({
|
|
|
698
942
|
)
|
|
699
943
|
}
|
|
700
944
|
|
|
945
|
+
function BranchChangesPanel({
|
|
946
|
+
changes,
|
|
947
|
+
}: {
|
|
948
|
+
changes: BranchChanges
|
|
949
|
+
}) {
|
|
950
|
+
const [minimized, setMinimized] = useState(false)
|
|
951
|
+
const addedFunctions = changes.addedFunctions ?? []
|
|
952
|
+
const addedVariables = changes.addedVariables ?? []
|
|
953
|
+
const addedImports = changes.addedImports ?? []
|
|
954
|
+
const changedFunctions = changes.changedFunctions ?? []
|
|
955
|
+
const changedVariables = changes.changedVariables ?? []
|
|
956
|
+
const subtitle =
|
|
957
|
+
changes.branch && changes.base
|
|
958
|
+
? `${changes.branch} vs ${changes.base}`
|
|
959
|
+
: changes.branch
|
|
960
|
+
? changes.branch
|
|
961
|
+
: null
|
|
962
|
+
const hasContent =
|
|
963
|
+
changes.files.length > 0 ||
|
|
964
|
+
(changes.createFolders ?? []).length > 0 ||
|
|
965
|
+
changes.creates.length > 0 ||
|
|
966
|
+
changes.deletes.length > 0 ||
|
|
967
|
+
addedFunctions.length > 0 ||
|
|
968
|
+
addedVariables.length > 0 ||
|
|
969
|
+
addedImports.length > 0 ||
|
|
970
|
+
changedFunctions.length > 0 ||
|
|
971
|
+
changedVariables.length > 0 ||
|
|
972
|
+
(changes.imports ?? []).length > 0
|
|
973
|
+
|
|
974
|
+
return (
|
|
975
|
+
<aside
|
|
976
|
+
className="hud-panel hud-panel-planned hud-panel-done"
|
|
977
|
+
data-minimized={minimized}
|
|
978
|
+
>
|
|
979
|
+
<PanelChrome
|
|
980
|
+
title="Branch changes"
|
|
981
|
+
minimized={minimized}
|
|
982
|
+
onMinimize={() => setMinimized((current) => !current)}
|
|
983
|
+
/>
|
|
984
|
+
{!minimized && (
|
|
985
|
+
<>
|
|
986
|
+
{subtitle && <p className="hud-feature">{subtitle}</p>}
|
|
987
|
+
{!hasContent ? (
|
|
988
|
+
<p>No file changes on this branch.</p>
|
|
989
|
+
) : (
|
|
990
|
+
<MutationFold hasContent>
|
|
991
|
+
{changes.files.length > 0 && (
|
|
992
|
+
<>
|
|
993
|
+
<div className="hud-section-title hud-section-title-edit">
|
|
994
|
+
Changed
|
|
995
|
+
</div>
|
|
996
|
+
<ul>
|
|
997
|
+
{changes.files.map((id) => (
|
|
998
|
+
<li className="hud-file-edit" key={id}>
|
|
999
|
+
{id}
|
|
1000
|
+
</li>
|
|
1001
|
+
))}
|
|
1002
|
+
</ul>
|
|
1003
|
+
</>
|
|
1004
|
+
)}
|
|
1005
|
+
{(changes.createFolders ?? []).length > 0 && (
|
|
1006
|
+
<>
|
|
1007
|
+
<div className="hud-section-title hud-section-title-add">
|
|
1008
|
+
Added islands
|
|
1009
|
+
</div>
|
|
1010
|
+
<ul>
|
|
1011
|
+
{changes.createFolders.map((id) => (
|
|
1012
|
+
<li className="hud-file-add" key={id}>
|
|
1013
|
+
{id}/
|
|
1014
|
+
</li>
|
|
1015
|
+
))}
|
|
1016
|
+
</ul>
|
|
1017
|
+
</>
|
|
1018
|
+
)}
|
|
1019
|
+
{changes.creates.length > 0 && (
|
|
1020
|
+
<>
|
|
1021
|
+
<div className="hud-section-title hud-section-title-add">
|
|
1022
|
+
Added
|
|
1023
|
+
</div>
|
|
1024
|
+
<ul>
|
|
1025
|
+
{changes.creates.map((id) => (
|
|
1026
|
+
<li className="hud-file-add" key={id}>
|
|
1027
|
+
{id}
|
|
1028
|
+
</li>
|
|
1029
|
+
))}
|
|
1030
|
+
</ul>
|
|
1031
|
+
</>
|
|
1032
|
+
)}
|
|
1033
|
+
{changes.deletes.length > 0 && (
|
|
1034
|
+
<>
|
|
1035
|
+
<div className="hud-section-title hud-section-title-remove">
|
|
1036
|
+
Removed
|
|
1037
|
+
</div>
|
|
1038
|
+
<ul>
|
|
1039
|
+
{changes.deletes.map((id) => (
|
|
1040
|
+
<li className="hud-file-remove" key={id}>
|
|
1041
|
+
{id}
|
|
1042
|
+
</li>
|
|
1043
|
+
))}
|
|
1044
|
+
</ul>
|
|
1045
|
+
</>
|
|
1046
|
+
)}
|
|
1047
|
+
<PatchSymbolChanges
|
|
1048
|
+
title="Functions"
|
|
1049
|
+
added={addedFunctions}
|
|
1050
|
+
changed={changedFunctions}
|
|
1051
|
+
/>
|
|
1052
|
+
<PatchSymbolChanges
|
|
1053
|
+
title="Vars"
|
|
1054
|
+
added={addedVariables}
|
|
1055
|
+
changed={changedVariables}
|
|
1056
|
+
/>
|
|
1057
|
+
<PanelList
|
|
1058
|
+
title="Imports"
|
|
1059
|
+
items={importLabels(addedImports)}
|
|
1060
|
+
tone="add"
|
|
1061
|
+
/>
|
|
1062
|
+
</MutationFold>
|
|
1063
|
+
)}
|
|
1064
|
+
</>
|
|
1065
|
+
)}
|
|
1066
|
+
</aside>
|
|
1067
|
+
)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
701
1070
|
type ExplorerInstruction = {
|
|
702
1071
|
id: string
|
|
703
1072
|
keys: string[]
|
|
@@ -732,7 +1101,7 @@ function InstructionList({ items }: { items: ExplorerInstruction[] }) {
|
|
|
732
1101
|
}
|
|
733
1102
|
|
|
734
1103
|
function explorerInstructions({
|
|
735
|
-
|
|
1104
|
+
canPlace,
|
|
736
1105
|
hasChangeSet,
|
|
737
1106
|
changePathsOnly,
|
|
738
1107
|
selectedUserCreated,
|
|
@@ -741,8 +1110,10 @@ function explorerInstructions({
|
|
|
741
1110
|
importedBy,
|
|
742
1111
|
canStop,
|
|
743
1112
|
sessionCount,
|
|
1113
|
+
showBranchChanges,
|
|
1114
|
+
canShowBranchChanges,
|
|
744
1115
|
}: {
|
|
745
|
-
|
|
1116
|
+
canPlace: boolean
|
|
746
1117
|
hasChangeSet: boolean
|
|
747
1118
|
changePathsOnly: boolean
|
|
748
1119
|
selectedUserCreated: boolean
|
|
@@ -751,9 +1122,11 @@ function explorerInstructions({
|
|
|
751
1122
|
importedBy: boolean
|
|
752
1123
|
canStop: boolean
|
|
753
1124
|
sessionCount: number
|
|
1125
|
+
showBranchChanges: boolean
|
|
1126
|
+
canShowBranchChanges: boolean
|
|
754
1127
|
}): ExplorerInstructionSection[] {
|
|
755
1128
|
const backspace: ExplorerInstruction[] =
|
|
756
|
-
selectedUserCreated &&
|
|
1129
|
+
selectedUserCreated && canPlace
|
|
757
1130
|
? [{ id: 'backspace', keys: ['Backspace'], label: 'Delete' }]
|
|
758
1131
|
: []
|
|
759
1132
|
const info: ExplorerInstruction[] = [
|
|
@@ -771,6 +1144,17 @@ function explorerInstructions({
|
|
|
771
1144
|
keys: ['K'],
|
|
772
1145
|
label: importedBy ? 'Show imports' : 'Show imported by',
|
|
773
1146
|
}
|
|
1147
|
+
const branch: ExplorerInstruction[] = canShowBranchChanges
|
|
1148
|
+
? [
|
|
1149
|
+
{
|
|
1150
|
+
id: 'branch-changes',
|
|
1151
|
+
keys: ['G'],
|
|
1152
|
+
label: showBranchChanges
|
|
1153
|
+
? 'Hide branch changes'
|
|
1154
|
+
: 'Show branch changes',
|
|
1155
|
+
},
|
|
1156
|
+
]
|
|
1157
|
+
: []
|
|
774
1158
|
const stop: ExplorerInstruction[] = canStop
|
|
775
1159
|
? [
|
|
776
1160
|
{
|
|
@@ -796,7 +1180,7 @@ function explorerInstructions({
|
|
|
796
1180
|
{ id: 'wasd', keys: ['W', 'A', 'S', 'D'], label: 'Walk' },
|
|
797
1181
|
{ id: 'mouse-look', keys: ['Mouse'], label: 'Look around' },
|
|
798
1182
|
{ id: 'shift', keys: ['Shift'], label: 'Sprint' },
|
|
799
|
-
...(
|
|
1183
|
+
...(canPlace
|
|
800
1184
|
? [
|
|
801
1185
|
{ id: 'space', keys: ['Space'], label: 'Place file' },
|
|
802
1186
|
{ id: 'b-island', keys: ['B'], label: 'Place island' },
|
|
@@ -811,11 +1195,17 @@ function explorerInstructions({
|
|
|
811
1195
|
{ id: 'aim-line', keys: ['Click'], label: 'Aim a line to fly' },
|
|
812
1196
|
...info,
|
|
813
1197
|
imported,
|
|
1198
|
+
...branch,
|
|
814
1199
|
{
|
|
815
1200
|
id: 'update-model',
|
|
816
1201
|
keys: ['Update model'],
|
|
817
1202
|
label: 'Rescan and rebuild the map',
|
|
818
1203
|
},
|
|
1204
|
+
{
|
|
1205
|
+
id: 'setup-session',
|
|
1206
|
+
keys: ['Setup LLM session'],
|
|
1207
|
+
label: 'Open a session; /inbase connects the focused one',
|
|
1208
|
+
},
|
|
819
1209
|
{ id: 'toggle-map', keys: ['M'], label: 'Toggle map' },
|
|
820
1210
|
{
|
|
821
1211
|
id: 'release',
|
|
@@ -856,7 +1246,7 @@ function explorerInstructions({
|
|
|
856
1246
|
keys: ['Click'],
|
|
857
1247
|
label: 'An island for its files',
|
|
858
1248
|
},
|
|
859
|
-
...(
|
|
1249
|
+
...(canPlace
|
|
860
1250
|
? [
|
|
861
1251
|
{ id: 'select-island', keys: ['Click'], label: 'Select an island' },
|
|
862
1252
|
{
|
|
@@ -892,11 +1282,17 @@ function explorerInstructions({
|
|
|
892
1282
|
...info,
|
|
893
1283
|
thumbnail,
|
|
894
1284
|
imported,
|
|
1285
|
+
...branch,
|
|
895
1286
|
{
|
|
896
1287
|
id: 'update-model',
|
|
897
1288
|
keys: ['Update model'],
|
|
898
1289
|
label: 'Rescan and rebuild the map',
|
|
899
1290
|
},
|
|
1291
|
+
{
|
|
1292
|
+
id: 'setup-session',
|
|
1293
|
+
keys: ['Setup LLM session'],
|
|
1294
|
+
label: 'Open a session; /inbase connects the focused one',
|
|
1295
|
+
},
|
|
900
1296
|
{ id: 'map-walk', keys: ['M'], label: 'Back to walk' },
|
|
901
1297
|
...stop,
|
|
902
1298
|
],
|
|
@@ -920,6 +1316,7 @@ type HUDProps = {
|
|
|
920
1316
|
intents?: AgentIntent[]
|
|
921
1317
|
focusedSessionId?: string | null
|
|
922
1318
|
onFocusSession?: (sessionId: string) => void
|
|
1319
|
+
onSetupSession?: () => Promise<unknown>
|
|
923
1320
|
onWorkflowAction: (
|
|
924
1321
|
sessionId: string,
|
|
925
1322
|
action: WorkflowAction,
|
|
@@ -930,6 +1327,11 @@ type HUDProps = {
|
|
|
930
1327
|
onWalk: () => void
|
|
931
1328
|
followLook: boolean
|
|
932
1329
|
onToggleFollowLook: () => void
|
|
1330
|
+
showBranchChanges?: boolean
|
|
1331
|
+
branchChanges?: BranchChanges
|
|
1332
|
+
canShowBranchChanges?: boolean
|
|
1333
|
+
llmMakingChanges?: boolean
|
|
1334
|
+
onToggleShowBranchChanges?: () => void
|
|
933
1335
|
onUpdateModel: () => void
|
|
934
1336
|
updatingModel?: boolean
|
|
935
1337
|
importedBy: boolean
|
|
@@ -979,12 +1381,18 @@ export function HUD({
|
|
|
979
1381
|
intents,
|
|
980
1382
|
focusedSessionId = null,
|
|
981
1383
|
onFocusSession,
|
|
1384
|
+
onSetupSession,
|
|
982
1385
|
onWorkflowAction,
|
|
983
1386
|
onNavigateDiff,
|
|
984
1387
|
onOpenMap,
|
|
985
1388
|
onWalk,
|
|
986
1389
|
followLook,
|
|
987
1390
|
onToggleFollowLook,
|
|
1391
|
+
showBranchChanges = false,
|
|
1392
|
+
branchChanges,
|
|
1393
|
+
canShowBranchChanges = false,
|
|
1394
|
+
llmMakingChanges = false,
|
|
1395
|
+
onToggleShowBranchChanges,
|
|
988
1396
|
onUpdateModel,
|
|
989
1397
|
updatingModel = false,
|
|
990
1398
|
importedBy,
|
|
@@ -1033,24 +1441,27 @@ export function HUD({
|
|
|
1033
1441
|
(item) => item.sessionId && isReviewingIntent(item.status),
|
|
1034
1442
|
)
|
|
1035
1443
|
const canStop = canStopSession(intent)
|
|
1444
|
+
const attachedSession =
|
|
1445
|
+
sessions.find((session) => session.awaitingAttach === false) ?? null
|
|
1036
1446
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
1037
1447
|
const walkIntroSeen = useRef(false)
|
|
1038
1448
|
const [instructionsOpen, setInstructionsOpen] = useState(false)
|
|
1449
|
+
const [setupError, setSetupError] = useState<string | null>(null)
|
|
1450
|
+
const [setupBusy, setSetupBusy] = useState(false)
|
|
1039
1451
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
1040
1452
|
const [infoMinimized, setInfoMinimized] = useState(false)
|
|
1041
1453
|
const [thumbnailVisible, setThumbnailVisible] = useState(true)
|
|
1042
1454
|
const [thumbnailMinimized, setThumbnailMinimized] = useState(false)
|
|
1043
1455
|
const [thumbnailMaximized, setThumbnailMaximized] = useState(false)
|
|
1044
1456
|
const infoPanelRef = useRef<HTMLDivElement>(null)
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
const
|
|
1052
|
-
const
|
|
1053
|
-
const changedVariables = intent.changedVariables ?? []
|
|
1457
|
+
const canPlace = Boolean(intent.creationMode)
|
|
1458
|
+
const overlay = showBranchChanges && branchChanges ? branchChanges : intent
|
|
1459
|
+
const previewing = intent.preview || showBranchChanges
|
|
1460
|
+
const addedFunctions = overlay.addedFunctions ?? []
|
|
1461
|
+
const addedVariables = overlay.addedVariables ?? []
|
|
1462
|
+
const addedImports = overlay.addedImports ?? []
|
|
1463
|
+
const changedFunctions = overlay.changedFunctions ?? []
|
|
1464
|
+
const changedVariables = overlay.changedVariables ?? []
|
|
1054
1465
|
const selectedAddedFunctions = selected
|
|
1055
1466
|
? addedFunctions.filter((item) => item.file === selected.id)
|
|
1056
1467
|
: []
|
|
@@ -1111,7 +1522,7 @@ export function HUD({
|
|
|
1111
1522
|
(item) => !selected?.imports.includes(item.from),
|
|
1112
1523
|
)
|
|
1113
1524
|
const canEditBlueprint =
|
|
1114
|
-
|
|
1525
|
+
canPlace && Boolean(selected) && !selected?.id.startsWith('draft:')
|
|
1115
1526
|
const canInspectFile = (fileId: string, userCreated = false) =>
|
|
1116
1527
|
Boolean(onInspectFile) &&
|
|
1117
1528
|
!fileId.startsWith('draft:') &&
|
|
@@ -1268,7 +1679,7 @@ export function HUD({
|
|
|
1268
1679
|
}, [instructionsOpen])
|
|
1269
1680
|
|
|
1270
1681
|
const instructionSections = explorerInstructions({
|
|
1271
|
-
|
|
1682
|
+
canPlace,
|
|
1272
1683
|
hasChangeSet,
|
|
1273
1684
|
changePathsOnly,
|
|
1274
1685
|
selectedUserCreated: Boolean(selected?.userCreated),
|
|
@@ -1277,6 +1688,8 @@ export function HUD({
|
|
|
1277
1688
|
importedBy,
|
|
1278
1689
|
canStop,
|
|
1279
1690
|
sessionCount: sessions.length,
|
|
1691
|
+
showBranchChanges,
|
|
1692
|
+
canShowBranchChanges,
|
|
1280
1693
|
})
|
|
1281
1694
|
const currentInstructionView: InstructionView = mapping
|
|
1282
1695
|
? thumbnailMaximized
|
|
@@ -1298,7 +1711,7 @@ export function HUD({
|
|
|
1298
1711
|
<p>
|
|
1299
1712
|
<kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> walk,{' '}
|
|
1300
1713
|
<kbd>Shift</kbd> sprint
|
|
1301
|
-
{
|
|
1714
|
+
{canPlace ? (
|
|
1302
1715
|
<>
|
|
1303
1716
|
, <kbd>Space</kbd> place a file, <kbd>B</kbd> place an island
|
|
1304
1717
|
</>
|
|
@@ -1360,21 +1773,48 @@ export function HUD({
|
|
|
1360
1773
|
{selected && <div className="hud-chip">{selected.path}</div>}
|
|
1361
1774
|
</div>
|
|
1362
1775
|
|
|
1363
|
-
{sessions.length > 0 && (
|
|
1776
|
+
{(sessions.length > 0 || showBranchChanges) && (
|
|
1364
1777
|
<div className="hud-left-stack">
|
|
1365
|
-
{sessions.
|
|
1778
|
+
{sessions.length > 1 && (
|
|
1779
|
+
<div className="hud-session-tabs" role="tablist" aria-label="LLM sessions">
|
|
1780
|
+
{sessions.map((session) => {
|
|
1781
|
+
const active =
|
|
1782
|
+
session.sessionId === (focusedSessionId ?? intent.sessionId)
|
|
1783
|
+
return (
|
|
1784
|
+
<button
|
|
1785
|
+
className="hud-button hud-session-tab"
|
|
1786
|
+
type="button"
|
|
1787
|
+
role="tab"
|
|
1788
|
+
aria-selected={active}
|
|
1789
|
+
data-active={active}
|
|
1790
|
+
key={session.sessionId}
|
|
1791
|
+
title={sessionTabLabel(session, sessions)}
|
|
1792
|
+
onClick={() => {
|
|
1793
|
+
if (session.sessionId) onFocusSession?.(session.sessionId)
|
|
1794
|
+
}}
|
|
1795
|
+
>
|
|
1796
|
+
{sessionTabLabel(session, sessions)}
|
|
1797
|
+
</button>
|
|
1798
|
+
)
|
|
1799
|
+
})}
|
|
1800
|
+
</div>
|
|
1801
|
+
)}
|
|
1802
|
+
{sessions.length > 0 && (
|
|
1366
1803
|
<SessionPanel
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
focused={session.sessionId === (focusedSessionId ?? intent.sessionId)}
|
|
1804
|
+
intent={intent}
|
|
1805
|
+
focused
|
|
1370
1806
|
naming={naming}
|
|
1807
|
+
attachedSession={attachedSession}
|
|
1371
1808
|
onFocus={() => {
|
|
1372
|
-
if (
|
|
1809
|
+
if (intent.sessionId) onFocusSession?.(intent.sessionId)
|
|
1373
1810
|
}}
|
|
1374
1811
|
onWorkflowAction={onWorkflowAction}
|
|
1375
1812
|
onNavigateDiff={onNavigateDiff}
|
|
1376
1813
|
/>
|
|
1377
|
-
)
|
|
1814
|
+
)}
|
|
1815
|
+
{showBranchChanges && branchChanges && (
|
|
1816
|
+
<BranchChangesPanel changes={branchChanges} />
|
|
1817
|
+
)}
|
|
1378
1818
|
</div>
|
|
1379
1819
|
)}
|
|
1380
1820
|
|
|
@@ -1416,7 +1856,7 @@ export function HUD({
|
|
|
1416
1856
|
selectedAddedImports.length > 0) && (
|
|
1417
1857
|
<>
|
|
1418
1858
|
<div className="hud-section-title hud-section-title-edit">
|
|
1419
|
-
LLM changes
|
|
1859
|
+
{showBranchChanges ? 'Branch changes' : 'LLM changes'}
|
|
1420
1860
|
</div>
|
|
1421
1861
|
<PatchSymbolChanges
|
|
1422
1862
|
title="Functions"
|
|
@@ -1675,7 +2115,7 @@ export function HUD({
|
|
|
1675
2115
|
))}
|
|
1676
2116
|
</ul>
|
|
1677
2117
|
)}
|
|
1678
|
-
{
|
|
2118
|
+
{canPlace && mapping && onMapAddFile && onMapAddFolder && (
|
|
1679
2119
|
<div className="hud-decide hud-map-blueprint">
|
|
1680
2120
|
<button
|
|
1681
2121
|
className="hud-button hud-button-approve"
|
|
@@ -1795,6 +2235,31 @@ export function HUD({
|
|
|
1795
2235
|
>
|
|
1796
2236
|
{updatingModel ? 'Updating…' : 'Update model'}
|
|
1797
2237
|
</button>
|
|
2238
|
+
{onSetupSession && (
|
|
2239
|
+
<button
|
|
2240
|
+
className="hud-button"
|
|
2241
|
+
type="button"
|
|
2242
|
+
aria-label="Start an LLM session without attaching a chat yet"
|
|
2243
|
+
title={setupError ?? 'Start an LLM session without attaching a chat yet'}
|
|
2244
|
+
disabled={setupBusy}
|
|
2245
|
+
onClick={() => {
|
|
2246
|
+
setInstructionsOpen(false)
|
|
2247
|
+
setSetupError(null)
|
|
2248
|
+
setSetupBusy(true)
|
|
2249
|
+
void onSetupSession()
|
|
2250
|
+
.catch((caught) => {
|
|
2251
|
+
setSetupError(
|
|
2252
|
+
caught instanceof Error
|
|
2253
|
+
? caught.message
|
|
2254
|
+
: 'Could not set up the session',
|
|
2255
|
+
)
|
|
2256
|
+
})
|
|
2257
|
+
.finally(() => setSetupBusy(false))
|
|
2258
|
+
}}
|
|
2259
|
+
>
|
|
2260
|
+
{setupBusy ? 'Starting…' : 'Setup LLM session'}
|
|
2261
|
+
</button>
|
|
2262
|
+
)}
|
|
1798
2263
|
</div>
|
|
1799
2264
|
<div className="hud-icon-row">
|
|
1800
2265
|
{mapping && hasChangeSet && onToggleChangePathsOnly && (
|
|
@@ -1908,6 +2373,53 @@ export function HUD({
|
|
|
1908
2373
|
{importedBy ? 'K show imports' : 'K show imported by'}
|
|
1909
2374
|
</span>
|
|
1910
2375
|
</button>
|
|
2376
|
+
<button
|
|
2377
|
+
className="hud-button hud-icon-button"
|
|
2378
|
+
data-active={showBranchChanges}
|
|
2379
|
+
aria-label={
|
|
2380
|
+
llmMakingChanges
|
|
2381
|
+
? 'Show branch changes unavailable while the LLM is making changes'
|
|
2382
|
+
: showBranchChanges
|
|
2383
|
+
? 'Hide branch changes'
|
|
2384
|
+
: 'Show branch changes'
|
|
2385
|
+
}
|
|
2386
|
+
aria-keyshortcuts="G"
|
|
2387
|
+
aria-pressed={showBranchChanges}
|
|
2388
|
+
aria-disabled={!canShowBranchChanges}
|
|
2389
|
+
type="button"
|
|
2390
|
+
onClick={() => {
|
|
2391
|
+
if (!canShowBranchChanges) return
|
|
2392
|
+
onToggleShowBranchChanges?.()
|
|
2393
|
+
}}
|
|
2394
|
+
>
|
|
2395
|
+
<svg
|
|
2396
|
+
viewBox="0 0 24 24"
|
|
2397
|
+
width="18"
|
|
2398
|
+
height="18"
|
|
2399
|
+
fill="none"
|
|
2400
|
+
stroke="currentColor"
|
|
2401
|
+
strokeWidth="2"
|
|
2402
|
+
strokeLinecap="round"
|
|
2403
|
+
strokeLinejoin="round"
|
|
2404
|
+
aria-hidden="true"
|
|
2405
|
+
>
|
|
2406
|
+
<circle cx="6" cy="5" r="2.4" />
|
|
2407
|
+
<circle cx="6" cy="19" r="2.4" />
|
|
2408
|
+
<circle cx="18" cy="12" r="2.4" />
|
|
2409
|
+
<path d="M6 7.4v9.2" />
|
|
2410
|
+
<path d="M6 12h7.2" />
|
|
2411
|
+
<path d="M13.2 12c2.2 0 2.2-4.6 4.4-4.6" />
|
|
2412
|
+
</svg>
|
|
2413
|
+
<span className="hud-tooltip">
|
|
2414
|
+
{llmMakingChanges
|
|
2415
|
+
? 'Unavailable while the LLM is making changes'
|
|
2416
|
+
: !canShowBranchChanges
|
|
2417
|
+
? 'No git branch to show'
|
|
2418
|
+
: showBranchChanges
|
|
2419
|
+
? 'G hide branch changes'
|
|
2420
|
+
: 'G show branch changes'}
|
|
2421
|
+
</span>
|
|
2422
|
+
</button>
|
|
1911
2423
|
<button
|
|
1912
2424
|
className="hud-button hud-icon-button"
|
|
1913
2425
|
data-active={followLook}
|