@jkwd/inbase 0.1.21 → 0.1.22
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 +13 -7
- package/apps/explorer/package.json +1 -0
- package/apps/explorer/scripts/explain-store.d.ts +135 -0
- package/apps/explorer/scripts/explain-store.mjs +666 -0
- package/apps/explorer/scripts/patch-lib.mjs +4 -0
- package/apps/explorer/scripts/scan-target.mjs +22 -5
- package/apps/explorer/scripts/session-store.d.ts +86 -11
- package/apps/explorer/scripts/session-store.mjs +371 -58
- package/apps/explorer/scripts/target-config.d.ts +38 -3
- package/apps/explorer/scripts/target-config.mjs +147 -3
- package/apps/explorer/src/App.tsx +1073 -158
- package/apps/explorer/src/agentIntent.ts +61 -7
- package/apps/explorer/src/codebase.ts +1 -1
- package/apps/explorer/src/devTargets.ts +66 -0
- package/apps/explorer/src/explain.ts +312 -0
- package/apps/explorer/src/index.css +874 -222
- package/apps/explorer/src/layout.ts +55 -0
- package/apps/explorer/src/scene/DistantFileBlocks.tsx +4 -2
- package/apps/explorer/src/scene/FileBlock.tsx +95 -72
- package/apps/explorer/src/scene/FolderArea.tsx +55 -32
- package/apps/explorer/src/scene/MapView.tsx +506 -33
- package/apps/explorer/src/scene/RelationLines.tsx +7 -0
- package/apps/explorer/src/scene/World.tsx +146 -32
- package/apps/explorer/src/speech.ts +228 -0
- package/apps/explorer/src/theme.ts +29 -0
- package/apps/explorer/src/types.ts +98 -8
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +1 -1
- package/apps/explorer/src/ui/ExplainAskCard.tsx +142 -0
- package/apps/explorer/src/ui/ExplainHud.tsx +524 -0
- package/apps/explorer/src/ui/ExplainInfoPanel.tsx +135 -0
- package/apps/explorer/src/ui/ExplainPointer.tsx +73 -0
- package/apps/explorer/src/ui/EyeIcon.tsx +38 -1
- package/apps/explorer/src/ui/HUD.tsx +1067 -795
- package/apps/explorer/src/ui/NameInput.tsx +114 -5
- package/apps/explorer/src/userContext.ts +0 -11
- package/apps/explorer/src/userCreated.ts +54 -1
- package/apps/explorer/vite.config.ts +173 -26
- package/bin/inbase.mjs +11 -2
- package/bin/project.mjs +1 -1
- package/bin/session.mjs +287 -38
- package/package.json +4 -1
- package/skill/commands/amber.md +23 -0
- package/skill/commands/blue.md +13 -0
- package/skill/commands/coral.md +23 -0
- package/skill/commands/explain.md +77 -0
- package/skill/commands/green.md +23 -0
- package/skill/commands/inbase.md +7 -5
- package/skill/commands/lime.md +23 -0
- package/skill/commands/orange.md +23 -0
- package/skill/commands/purple.md +23 -0
- package/skill/commands/red.md +23 -0
- package/skill/commands/skipinbase.md +1 -1
- package/skill/commands/violet.md +23 -0
- package/skill/commands/yellow.md +23 -0
- package/skill/inbase/SKILL.md +124 -76
- package/apps/explorer/src/scene/BlockPlacer.tsx +0 -78
- package/apps/explorer/src/scene/IslandPlacer.tsx +0 -31
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +0 -1069
|
@@ -16,6 +16,45 @@ import { diffSourceTrees, snapshotSourceTree } from './tree-diff.mjs'
|
|
|
16
16
|
const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
|
|
17
17
|
const CONNECTED_TTL_MS = 15_000
|
|
18
18
|
const STALLED_WAIT_MS = 2_000
|
|
19
|
+
export const SESSION_SLOT_COUNT = 5
|
|
20
|
+
export const SESSION_COLORS = [
|
|
21
|
+
{ id: 'coral', name: 'Coral', hex: '#f87171' },
|
|
22
|
+
{ id: 'amber', name: 'Amber', hex: '#fbbf24' },
|
|
23
|
+
{ id: 'lime', name: 'Lime', hex: '#a3e635' },
|
|
24
|
+
{ id: 'orange', name: 'Orange', hex: '#fb923c' },
|
|
25
|
+
{ id: 'violet', name: 'Violet', hex: '#c084fc' },
|
|
26
|
+
]
|
|
27
|
+
export const SESSION_COLOR_ALIASES = {
|
|
28
|
+
coral: 'coral',
|
|
29
|
+
red: 'coral',
|
|
30
|
+
amber: 'amber',
|
|
31
|
+
yellow: 'amber',
|
|
32
|
+
lime: 'lime',
|
|
33
|
+
green: 'lime',
|
|
34
|
+
orange: 'orange',
|
|
35
|
+
violet: 'violet',
|
|
36
|
+
purple: 'violet',
|
|
37
|
+
}
|
|
38
|
+
const GLOBAL_COLOR_QUERIES = new Set(['blue', 'global', 'sky'])
|
|
39
|
+
export const GLOBAL_BLUEPRINT_COLOR = {
|
|
40
|
+
id: 'global',
|
|
41
|
+
name: 'Global',
|
|
42
|
+
hex: '#38bdf8',
|
|
43
|
+
}
|
|
44
|
+
export const CHAT_LIMIT_MESSAGE =
|
|
45
|
+
'VISUAL_CODER_CHAT_LIMIT Only 5 Inbase chats can be connected at once. Finish or stop one in the map, then start a new chat.'
|
|
46
|
+
export const NOT_RUNNING_MESSAGE =
|
|
47
|
+
"VISUAL_CODER_NOT_RUNNING Inbase isn't running. Start it with `npx inbase run`, then send this request again."
|
|
48
|
+
export function colorUnknownMessage(query) {
|
|
49
|
+
const label = typeof query === 'string' && query.trim() ? query.trim() : 'That color'
|
|
50
|
+
return `VISUAL_CODER_COLOR_UNKNOWN ${label} is not a chat color. Connect with /coral, /amber, /lime, /orange, or /violet (aliases: /red, /yellow, /green, /purple). Blue is the global blueprint, not a chat.`
|
|
51
|
+
}
|
|
52
|
+
export function colorBusyMessage(colorName) {
|
|
53
|
+
return `VISUAL_CODER_COLOR_BUSY The ${colorName} session already has a chat connected. Finish or stop it in the map, then try again.`
|
|
54
|
+
}
|
|
55
|
+
export function colorMissingMessage(colorName) {
|
|
56
|
+
return `VISUAL_CODER_COLOR_UNKNOWN No ${colorName} session is open. Start Inbase with \`npx inbase run\`, then try again.`
|
|
57
|
+
}
|
|
19
58
|
export const MAX_CONTEXT_FILES = 16
|
|
20
59
|
export const MAX_CONTEXT_FILE_BYTES = 8 * 1024 * 1024
|
|
21
60
|
export const MAX_CONTEXT_TOTAL_BYTES = 24 * 1024 * 1024
|
|
@@ -58,6 +97,46 @@ function resolvedSessionName(manifest) {
|
|
|
58
97
|
return sessionName(manifest?.name) || sessionName(manifest?.feature)
|
|
59
98
|
}
|
|
60
99
|
|
|
100
|
+
export function resolveSessionColor(colorId) {
|
|
101
|
+
if (typeof colorId !== 'string' || colorId.trim() === '') return null
|
|
102
|
+
return SESSION_COLORS.find((entry) => entry.id === colorId) ?? null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function parseSessionColorQuery(value) {
|
|
106
|
+
if (typeof value !== 'string' || value.trim() === '') return null
|
|
107
|
+
const key = value.trim().toLowerCase()
|
|
108
|
+
if (GLOBAL_COLOR_QUERIES.has(key)) {
|
|
109
|
+
throw new Error(colorUnknownMessage(key === 'global' ? 'Global' : 'Blue'))
|
|
110
|
+
}
|
|
111
|
+
const id = SESSION_COLOR_ALIASES[key] ?? resolveSessionColor(key)?.id ?? null
|
|
112
|
+
const color = resolveSessionColor(id)
|
|
113
|
+
if (!color) throw new Error(colorUnknownMessage(value.trim()))
|
|
114
|
+
return color
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function assignedSessionColors(dataDir) {
|
|
118
|
+
const used = new Set()
|
|
119
|
+
for (const sessionId of listOpenSessionIds(dataDir)) {
|
|
120
|
+
const color = readManifest(dataDir, sessionId)?.color
|
|
121
|
+
if (resolveSessionColor(color)) used.add(color)
|
|
122
|
+
}
|
|
123
|
+
return used
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function nextSessionColor(dataDir) {
|
|
127
|
+
const used = assignedSessionColors(dataDir)
|
|
128
|
+
return SESSION_COLORS.find((entry) => !used.has(entry.id))?.id ?? SESSION_COLORS[0].id
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function ensureManifestColor(dataDir, manifest) {
|
|
132
|
+
if (!manifest) return manifest
|
|
133
|
+
if (resolveSessionColor(manifest.color)) return manifest
|
|
134
|
+
manifest.color = nextSessionColor(dataDir)
|
|
135
|
+
const { manifest: file } = sessionPaths(dataDir, manifest.sessionId)
|
|
136
|
+
atomicWrite(file, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
137
|
+
return manifest
|
|
138
|
+
}
|
|
139
|
+
|
|
61
140
|
export function sessionPaths(dataDir, sessionId) {
|
|
62
141
|
const safeId = assertSessionId(sessionId)
|
|
63
142
|
const root = path.join(dataDir, 'diff-sessions', safeId)
|
|
@@ -346,6 +425,45 @@ export function focusSession(dataDir, sessionId) {
|
|
|
346
425
|
return safeId
|
|
347
426
|
}
|
|
348
427
|
|
|
428
|
+
function sessionPoolFile(dataDir) {
|
|
429
|
+
return path.join(dataDir, 'session-pool.json')
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function enableSessionPool(dataDir, count = SESSION_SLOT_COUNT) {
|
|
433
|
+
atomicWrite(
|
|
434
|
+
sessionPoolFile(dataDir),
|
|
435
|
+
`${JSON.stringify({ count }, null, 2)}\n`,
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function sessionPoolSize(dataDir) {
|
|
440
|
+
const value = readJson(sessionPoolFile(dataDir), null)
|
|
441
|
+
const count = value?.count
|
|
442
|
+
return Number.isInteger(count) && count > 0 ? count : 0
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function ensureSessionPool(dataDir, options = {}) {
|
|
446
|
+
const size = sessionPoolSize(dataDir)
|
|
447
|
+
if (size <= 0) {
|
|
448
|
+
enableSessionPool(dataDir, options.count ?? SESSION_SLOT_COUNT)
|
|
449
|
+
}
|
|
450
|
+
const count = sessionPoolSize(dataDir)
|
|
451
|
+
const created = []
|
|
452
|
+
while (listOpenSessionIds(dataDir).length < count) {
|
|
453
|
+
created.push(setupSession(dataDir, { focus: false }))
|
|
454
|
+
}
|
|
455
|
+
if (options.focus !== false && !readActiveSession(dataDir)) {
|
|
456
|
+
const next = nextAttachSessionId(dataDir) ?? listOpenSessionIds(dataDir)[0]
|
|
457
|
+
if (next) focusSession(dataDir, next)
|
|
458
|
+
}
|
|
459
|
+
return created
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function refillSessionPool(dataDir) {
|
|
463
|
+
if (sessionPoolSize(dataDir) <= 0) return []
|
|
464
|
+
return ensureSessionPool(dataDir, { focus: false })
|
|
465
|
+
}
|
|
466
|
+
|
|
349
467
|
function attachQueueFile(dataDir) {
|
|
350
468
|
return path.join(dataDir, 'attach-queue.json')
|
|
351
469
|
}
|
|
@@ -417,6 +535,23 @@ function blueprintFile(dataDir) {
|
|
|
417
535
|
return path.join(dataDir, 'blueprint.json')
|
|
418
536
|
}
|
|
419
537
|
|
|
538
|
+
function localBlueprintFile(dataDir, sessionId) {
|
|
539
|
+
return sessionPaths(dataDir, sessionId).blueprint
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function isGlobalBlueprintColor(colorId) {
|
|
543
|
+
return !colorId || colorId === GLOBAL_BLUEPRINT_COLOR.id
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function findSessionIdByColor(dataDir, colorId) {
|
|
547
|
+
if (isGlobalBlueprintColor(colorId)) return null
|
|
548
|
+
for (const sessionId of listOpenSessionIds(dataDir)) {
|
|
549
|
+
const color = resolveSessionColor(readManifest(dataDir, sessionId)?.color)
|
|
550
|
+
if (color?.id === colorId) return sessionId
|
|
551
|
+
}
|
|
552
|
+
return null
|
|
553
|
+
}
|
|
554
|
+
|
|
420
555
|
function blueprintHasContent(blueprint) {
|
|
421
556
|
return (
|
|
422
557
|
(blueprint.userCreatedBlocks?.length ?? 0) > 0 ||
|
|
@@ -574,6 +709,7 @@ export function readManifest(dataDir, sessionId) {
|
|
|
574
709
|
value.pendingInstruction ??= null
|
|
575
710
|
value.workStartedAt ??= null
|
|
576
711
|
}
|
|
712
|
+
if (typeof value.pendingExplain !== 'boolean') value.pendingExplain = false
|
|
577
713
|
if (typeof value.stepByStep !== 'boolean') value.stepByStep = true
|
|
578
714
|
value.initialInstruction =
|
|
579
715
|
typeof value.initialInstruction === 'string' ? value.initialInstruction : null
|
|
@@ -748,13 +884,18 @@ export function sessionIntent(
|
|
|
748
884
|
const previewVisible = patches.length > 0
|
|
749
885
|
const blueprint = readBlueprint(dataDir, sessionId)
|
|
750
886
|
const canEnterBlueprint = manifest.phase === 'blueprint_ask'
|
|
887
|
+
const colored = ensureManifestColor(dataDir, manifest)
|
|
888
|
+
const color = resolveSessionColor(colored.color)
|
|
751
889
|
|
|
752
890
|
return {
|
|
753
|
-
updatedAt:
|
|
891
|
+
updatedAt: colored.updatedAt,
|
|
754
892
|
showMap: previewVisible,
|
|
755
893
|
status: activeView ? phaseStatus ?? historicalStatus : historicalStatus,
|
|
756
|
-
phase:
|
|
757
|
-
name: resolvedSessionName(
|
|
894
|
+
phase: colored.phase,
|
|
895
|
+
name: resolvedSessionName(colored) || null,
|
|
896
|
+
color: color?.id ?? null,
|
|
897
|
+
colorName: color?.name ?? null,
|
|
898
|
+
colorHex: color?.hex ?? null,
|
|
758
899
|
feature: manifest.feature,
|
|
759
900
|
initialInstruction:
|
|
760
901
|
typeof manifest.initialInstruction === 'string'
|
|
@@ -790,11 +931,13 @@ export function sessionIntent(
|
|
|
790
931
|
!isSessionConnected(dataDir, sessionId, waiterIds),
|
|
791
932
|
listening: waiterIds.has(sessionId),
|
|
792
933
|
lastAck: readSessionAck(dataDir, sessionId),
|
|
934
|
+
pendingExplain: Boolean(manifest.pendingExplain),
|
|
793
935
|
creationMode: true,
|
|
794
936
|
canEnterBlueprint,
|
|
795
937
|
blueprintHidden: Boolean(blueprint.hidden),
|
|
796
938
|
blueprintRevision: blueprint.revision,
|
|
797
939
|
blueprintSessionId: null,
|
|
940
|
+
localBlueprintEnabled: readLocalBlueprint(dataDir, sessionId).enabled,
|
|
798
941
|
userCreatedBlocks: blueprint.userCreatedBlocks,
|
|
799
942
|
userCreatedIslands: blueprint.userCreatedIslands,
|
|
800
943
|
...preview,
|
|
@@ -1072,6 +1215,7 @@ export function startSession(dataDir, input) {
|
|
|
1072
1215
|
currentStep: 1,
|
|
1073
1216
|
activeDiffId: null,
|
|
1074
1217
|
pendingInstruction: null,
|
|
1218
|
+
pendingExplain: false,
|
|
1075
1219
|
initialInstruction: null,
|
|
1076
1220
|
contextFiles: [],
|
|
1077
1221
|
workStartedAt: null,
|
|
@@ -1092,6 +1236,9 @@ export function setupSession(dataDir, input = {}) {
|
|
|
1092
1236
|
if (existing && !isTerminalSession(existing)) {
|
|
1093
1237
|
throw new Error(`Session ${sessionId} already exists`)
|
|
1094
1238
|
}
|
|
1239
|
+
if (!existing && listOpenSessionIds(dataDir).length >= SESSION_SLOT_COUNT) {
|
|
1240
|
+
throw new Error(CHAT_LIMIT_MESSAGE)
|
|
1241
|
+
}
|
|
1095
1242
|
if (existing) {
|
|
1096
1243
|
discardStoredSession(dataDir, sessionId, null, { restore: false })
|
|
1097
1244
|
}
|
|
@@ -1102,6 +1249,7 @@ export function setupSession(dataDir, input = {}) {
|
|
|
1102
1249
|
version: 2,
|
|
1103
1250
|
sessionId,
|
|
1104
1251
|
name,
|
|
1252
|
+
color: nextSessionColor(dataDir),
|
|
1105
1253
|
feature: featureName(input.feature) || name,
|
|
1106
1254
|
steps: [],
|
|
1107
1255
|
status: 'active',
|
|
@@ -1111,6 +1259,7 @@ export function setupSession(dataDir, input = {}) {
|
|
|
1111
1259
|
currentStep: 1,
|
|
1112
1260
|
activeDiffId: null,
|
|
1113
1261
|
pendingInstruction: null,
|
|
1262
|
+
pendingExplain: false,
|
|
1114
1263
|
initialInstruction: null,
|
|
1115
1264
|
contextFiles: [],
|
|
1116
1265
|
workStartedAt: null,
|
|
@@ -1119,7 +1268,7 @@ export function setupSession(dataDir, input = {}) {
|
|
|
1119
1268
|
diffs: [],
|
|
1120
1269
|
}
|
|
1121
1270
|
writeManifest(dataDir, manifest)
|
|
1122
|
-
focusSession(dataDir, sessionId)
|
|
1271
|
+
if (input.focus !== false) focusSession(dataDir, sessionId)
|
|
1123
1272
|
enqueueAttachSession(dataDir, sessionId)
|
|
1124
1273
|
return manifest
|
|
1125
1274
|
}
|
|
@@ -1244,28 +1393,47 @@ export function readAttachedSession(dataDir) {
|
|
|
1244
1393
|
return null
|
|
1245
1394
|
}
|
|
1246
1395
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1396
|
+
function resolveAttachSessionId(dataDir, sessionId, options = {}) {
|
|
1397
|
+
if (sessionId) return assertSessionId(sessionId)
|
|
1398
|
+
if (options.color) {
|
|
1399
|
+
const color = parseSessionColorQuery(options.color)
|
|
1400
|
+
if (!color) throw new Error(colorUnknownMessage(options.color))
|
|
1401
|
+
const matchId = findSessionIdByColor(dataDir, color.id)
|
|
1402
|
+
if (!matchId) throw new Error(colorMissingMessage(color.name))
|
|
1403
|
+
const existing = requireManifest(dataDir, matchId)
|
|
1404
|
+
if (!sessionIsWaitingToAttach(existing)) {
|
|
1405
|
+
throw new Error(colorBusyMessage(color.name))
|
|
1406
|
+
}
|
|
1407
|
+
return matchId
|
|
1408
|
+
}
|
|
1409
|
+
return nextAttachSessionId(dataDir)
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
export function attachSession(dataDir, sessionId, options = {}) {
|
|
1413
|
+
const safeId = resolveAttachSessionId(dataDir, sessionId, options)
|
|
1251
1414
|
if (!safeId) {
|
|
1252
|
-
throw new Error(
|
|
1253
|
-
'No visualizer session is waiting to attach. Click Setup LLM session in the map, then /inbase.',
|
|
1254
|
-
)
|
|
1415
|
+
throw new Error(CHAT_LIMIT_MESSAGE)
|
|
1255
1416
|
}
|
|
1256
1417
|
const manifest = requireManifest(
|
|
1257
1418
|
dataDir,
|
|
1258
1419
|
safeId,
|
|
1259
|
-
`No
|
|
1420
|
+
`No Inbase session ${safeId} is waiting to connect.`,
|
|
1260
1421
|
)
|
|
1261
1422
|
if (isTerminalSession(manifest)) {
|
|
1262
1423
|
throw sessionStoppedError(safeId)
|
|
1263
1424
|
}
|
|
1264
1425
|
focusSession(dataDir, safeId)
|
|
1265
1426
|
touchSessionConnection(dataDir, safeId)
|
|
1266
|
-
|
|
1427
|
+
const colored = ensureManifestColor(dataDir, manifest)
|
|
1428
|
+
const colorName = resolveSessionColor(colored.color)?.name
|
|
1429
|
+
recordSessionAck(
|
|
1430
|
+
dataDir,
|
|
1431
|
+
safeId,
|
|
1432
|
+
'attached',
|
|
1433
|
+
colorName || resolvedSessionName(colored) || safeId,
|
|
1434
|
+
)
|
|
1267
1435
|
maybeStartVisualizerHandshake(dataDir, safeId)
|
|
1268
|
-
return readManifest(dataDir, safeId) ??
|
|
1436
|
+
return readManifest(dataDir, safeId) ?? colored
|
|
1269
1437
|
}
|
|
1270
1438
|
|
|
1271
1439
|
export function answerBlueprint(dataDir, sessionId, enabled) {
|
|
@@ -1280,37 +1448,28 @@ export function answerBlueprint(dataDir, sessionId, enabled) {
|
|
|
1280
1448
|
}
|
|
1281
1449
|
|
|
1282
1450
|
export function updateBlueprint(dataDir, _sessionId, input = {}) {
|
|
1283
|
-
const
|
|
1451
|
+
const colorId = input.color
|
|
1452
|
+
const fields = blueprintInputFields(input)
|
|
1453
|
+
const current = readBlueprintByColor(dataDir, colorId)
|
|
1284
1454
|
const next = {
|
|
1285
1455
|
...current,
|
|
1286
|
-
userCreatedBlocks:
|
|
1287
|
-
userCreatedIslands:
|
|
1288
|
-
addedFunctions:
|
|
1289
|
-
addedVariables:
|
|
1290
|
-
addedImports:
|
|
1291
|
-
notes:
|
|
1292
|
-
pointers:
|
|
1456
|
+
userCreatedBlocks: fields.userCreatedBlocks ?? current.userCreatedBlocks,
|
|
1457
|
+
userCreatedIslands: fields.userCreatedIslands ?? current.userCreatedIslands,
|
|
1458
|
+
addedFunctions: fields.addedFunctions ?? current.addedFunctions,
|
|
1459
|
+
addedVariables: fields.addedVariables ?? current.addedVariables,
|
|
1460
|
+
addedImports: fields.addedImports ?? current.addedImports,
|
|
1461
|
+
notes: fields.notes ?? current.notes,
|
|
1462
|
+
pointers: fields.pointers ?? current.pointers,
|
|
1293
1463
|
}
|
|
1294
|
-
return
|
|
1464
|
+
return writeBlueprintByColor(dataDir, colorId, next)
|
|
1295
1465
|
}
|
|
1296
1466
|
|
|
1297
|
-
export function sendBlueprint(dataDir, sessionId,
|
|
1467
|
+
export function sendBlueprint(dataDir, sessionId, _input = {}) {
|
|
1298
1468
|
const safeId = assertSessionId(sessionId)
|
|
1299
1469
|
const manifest = requireManifest(dataDir, safeId)
|
|
1300
1470
|
if (manifest.phase !== 'blueprint') {
|
|
1301
1471
|
throw new Error(`Session ${safeId} is not in blueprint mode`)
|
|
1302
1472
|
}
|
|
1303
|
-
if (
|
|
1304
|
-
input.userCreatedBlocks ||
|
|
1305
|
-
input.userCreatedIslands ||
|
|
1306
|
-
input.addedFunctions ||
|
|
1307
|
-
input.addedVariables ||
|
|
1308
|
-
input.addedImports ||
|
|
1309
|
-
input.notes ||
|
|
1310
|
-
input.pointers
|
|
1311
|
-
) {
|
|
1312
|
-
updateBlueprint(dataDir, safeId, input)
|
|
1313
|
-
}
|
|
1314
1473
|
manifest.phase = 'preparing'
|
|
1315
1474
|
manifest.workStartedAt = new Date().toISOString()
|
|
1316
1475
|
writeManifest(dataDir, manifest)
|
|
@@ -1616,6 +1775,49 @@ export function requestReplan(
|
|
|
1616
1775
|
return manifest
|
|
1617
1776
|
}
|
|
1618
1777
|
|
|
1778
|
+
export function notifySessionExplain(dataDir, sessionId, detail) {
|
|
1779
|
+
const manifest = readManifest(dataDir, sessionId)
|
|
1780
|
+
if (!manifest) return null
|
|
1781
|
+
writeManifest(dataDir, manifest)
|
|
1782
|
+
recordSessionAck(
|
|
1783
|
+
dataDir,
|
|
1784
|
+
sessionId,
|
|
1785
|
+
'explain',
|
|
1786
|
+
typeof detail === 'string' && detail.trim() ? detail.trim() : 'a map target',
|
|
1787
|
+
)
|
|
1788
|
+
return manifest
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
export function requestExplainProposal(dataDir, sessionId, diffId) {
|
|
1792
|
+
const manifest = requireManifest(dataDir, sessionId)
|
|
1793
|
+
let title
|
|
1794
|
+
if (manifest.phase === 'review') {
|
|
1795
|
+
const active = pendingActive(manifest, diffId)
|
|
1796
|
+
title =
|
|
1797
|
+
manifest.steps.find((step) => step.index === active.step)?.title ||
|
|
1798
|
+
active.title ||
|
|
1799
|
+
`step ${active.step}`
|
|
1800
|
+
} else if (manifest.phase === 'plan_ready') {
|
|
1801
|
+
const step = manifest.currentStep
|
|
1802
|
+
title =
|
|
1803
|
+
manifest.steps.find((item) => item.index === step)?.title || `step ${step}`
|
|
1804
|
+
} else {
|
|
1805
|
+
throw new Error('No proposal to explain')
|
|
1806
|
+
}
|
|
1807
|
+
manifest.pendingExplain = true
|
|
1808
|
+
writeManifest(dataDir, manifest)
|
|
1809
|
+
recordSessionAck(dataDir, sessionId, 'explain', `the proposal for ${title}`)
|
|
1810
|
+
return manifest
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
export function consumeExplainRequest(dataDir, sessionId) {
|
|
1814
|
+
const manifest = readManifest(dataDir, sessionId)
|
|
1815
|
+
if (!manifest?.pendingExplain) return null
|
|
1816
|
+
manifest.pendingExplain = false
|
|
1817
|
+
writeManifest(dataDir, manifest)
|
|
1818
|
+
return manifest
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1619
1821
|
function unstageDiffSessionArtifacts(dataDir, targetRoot, extraPaths = []) {
|
|
1620
1822
|
if (!targetRoot) return
|
|
1621
1823
|
unstagePaths(targetRoot, [
|
|
@@ -1697,6 +1899,31 @@ export function discardInactiveDiffSessions(
|
|
|
1697
1899
|
return liveIds
|
|
1698
1900
|
}
|
|
1699
1901
|
|
|
1902
|
+
export function recycleDisconnectedSessions(
|
|
1903
|
+
dataDir,
|
|
1904
|
+
targetRoot = null,
|
|
1905
|
+
waiterIds = waiterSessionIds(),
|
|
1906
|
+
) {
|
|
1907
|
+
const waiters = new Set()
|
|
1908
|
+
for (const value of waiterIds) {
|
|
1909
|
+
try {
|
|
1910
|
+
waiters.add(assertSessionId(value))
|
|
1911
|
+
} catch {
|
|
1912
|
+
// Ignore process command lines with invalid session ids.
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
const recycled = []
|
|
1917
|
+
for (const sessionId of listOpenSessionIds(dataDir, waiters)) {
|
|
1918
|
+
const manifest = readManifest(dataDir, sessionId)
|
|
1919
|
+
if (!manifest || manifest.awaitingAttach) continue
|
|
1920
|
+
if (isSessionConnected(dataDir, sessionId, waiters)) continue
|
|
1921
|
+
stopSession(dataDir, sessionId, targetRoot)
|
|
1922
|
+
recycled.push(sessionId)
|
|
1923
|
+
}
|
|
1924
|
+
return recycled
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1700
1927
|
export function clearDiffSessions(dataDir, targetRoot = null) {
|
|
1701
1928
|
for (const sessionId of listStoredSessionIds(dataDir)) {
|
|
1702
1929
|
discardStoredSession(dataDir, sessionId, targetRoot)
|
|
@@ -1704,6 +1931,8 @@ export function clearDiffSessions(dataDir, targetRoot = null) {
|
|
|
1704
1931
|
writeActiveSession(dataDir, null)
|
|
1705
1932
|
writeBlueprintSession(dataDir, null)
|
|
1706
1933
|
writeAttachQueue(dataDir, [])
|
|
1934
|
+
const poolFile = sessionPoolFile(dataDir)
|
|
1935
|
+
if (fs.existsSync(poolFile)) fs.unlinkSync(poolFile)
|
|
1707
1936
|
|
|
1708
1937
|
const root = diffSessionsRoot(dataDir)
|
|
1709
1938
|
fs.mkdirSync(root, { recursive: true })
|
|
@@ -1715,9 +1944,9 @@ export function clearDiffSessions(dataDir, targetRoot = null) {
|
|
|
1715
1944
|
}
|
|
1716
1945
|
|
|
1717
1946
|
export function recoverOpenDiffSessions(dataDir, targetRoot = null) {
|
|
1718
|
-
// Visualizer startup never
|
|
1947
|
+
// Visualizer startup never restores a previous LLM session.
|
|
1719
1948
|
clearDiffSessions(dataDir, targetRoot)
|
|
1720
|
-
return
|
|
1949
|
+
return ensureSessionPool(dataDir).map((manifest) => manifest.sessionId)
|
|
1721
1950
|
}
|
|
1722
1951
|
|
|
1723
1952
|
export function stopSession(dataDir, sessionId, targetRoot = null) {
|
|
@@ -1727,6 +1956,7 @@ export function stopSession(dataDir, sessionId, targetRoot = null) {
|
|
|
1727
1956
|
const waiters = waiterSessionIds()
|
|
1728
1957
|
waiters.add(safeId)
|
|
1729
1958
|
discardInactiveDiffSessions(dataDir, targetRoot, waiters)
|
|
1959
|
+
refillSessionPool(dataDir)
|
|
1730
1960
|
return null
|
|
1731
1961
|
}
|
|
1732
1962
|
|
|
@@ -1755,6 +1985,7 @@ export function closeSession(dataDir, sessionId) {
|
|
|
1755
1985
|
|
|
1756
1986
|
export function finalizeFinishedSession(dataDir, sessionId) {
|
|
1757
1987
|
discardStoredSession(dataDir, sessionId, null, { restore: false })
|
|
1988
|
+
refillSessionPool(dataDir)
|
|
1758
1989
|
}
|
|
1759
1990
|
|
|
1760
1991
|
export function emptyBlueprint() {
|
|
@@ -1950,14 +2181,7 @@ function normalizeBlueprint(value) {
|
|
|
1950
2181
|
}
|
|
1951
2182
|
}
|
|
1952
2183
|
|
|
1953
|
-
|
|
1954
|
-
return normalizeBlueprint(readJson(blueprintFile(dataDir), emptyBlueprint()))
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
export function writeBlueprint(dataDir, sessionIdOrBlueprint, maybeBlueprint) {
|
|
1958
|
-
const current = readBlueprint(dataDir)
|
|
1959
|
-
const incoming =
|
|
1960
|
-
maybeBlueprint === undefined ? sessionIdOrBlueprint : maybeBlueprint
|
|
2184
|
+
function persistBlueprintFile(file, incoming, current) {
|
|
1961
2185
|
const next = normalizeBlueprint({
|
|
1962
2186
|
...current,
|
|
1963
2187
|
...incoming,
|
|
@@ -1972,7 +2196,7 @@ export function writeBlueprint(dataDir, sessionIdOrBlueprint, maybeBlueprint) {
|
|
|
1972
2196
|
next.enabled = blueprintHasContent(next)
|
|
1973
2197
|
next.sent = true
|
|
1974
2198
|
atomicWrite(
|
|
1975
|
-
|
|
2199
|
+
file,
|
|
1976
2200
|
`${JSON.stringify(
|
|
1977
2201
|
{
|
|
1978
2202
|
hidden: Boolean(next.hidden),
|
|
@@ -1991,24 +2215,104 @@ export function writeBlueprint(dataDir, sessionIdOrBlueprint, maybeBlueprint) {
|
|
|
1991
2215
|
2,
|
|
1992
2216
|
)}\n`,
|
|
1993
2217
|
)
|
|
1994
|
-
return
|
|
2218
|
+
return normalizeBlueprint(readJson(file, emptyBlueprint()))
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
export function readBlueprint(dataDir, _sessionId) {
|
|
2222
|
+
return normalizeBlueprint(readJson(blueprintFile(dataDir), emptyBlueprint()))
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
export function readLocalBlueprint(dataDir, sessionId) {
|
|
2226
|
+
if (!sessionId) return emptyBlueprint()
|
|
2227
|
+
return normalizeBlueprint(
|
|
2228
|
+
readJson(localBlueprintFile(dataDir, sessionId), emptyBlueprint()),
|
|
2229
|
+
)
|
|
1995
2230
|
}
|
|
1996
2231
|
|
|
1997
|
-
export function
|
|
1998
|
-
|
|
2232
|
+
export function readBlueprintByColor(dataDir, colorId) {
|
|
2233
|
+
if (isGlobalBlueprintColor(colorId)) return readBlueprint(dataDir)
|
|
2234
|
+
const sessionId = findSessionIdByColor(dataDir, colorId)
|
|
2235
|
+
return readLocalBlueprint(dataDir, sessionId)
|
|
1999
2236
|
}
|
|
2000
2237
|
|
|
2001
|
-
export function
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2238
|
+
export function listLocalBlueprints(dataDir) {
|
|
2239
|
+
const seen = new Set()
|
|
2240
|
+
const locals = []
|
|
2241
|
+
for (const sessionId of listOpenSessionIds(dataDir)) {
|
|
2242
|
+
const color = resolveSessionColor(readManifest(dataDir, sessionId)?.color)
|
|
2243
|
+
if (!color || seen.has(color.id)) continue
|
|
2244
|
+
seen.add(color.id)
|
|
2245
|
+
locals.push({
|
|
2246
|
+
color: color.id,
|
|
2247
|
+
colorName: color.name,
|
|
2248
|
+
colorHex: color.hex,
|
|
2249
|
+
sessionId,
|
|
2250
|
+
...readLocalBlueprint(dataDir, sessionId),
|
|
2251
|
+
})
|
|
2252
|
+
}
|
|
2253
|
+
return locals.sort(
|
|
2254
|
+
(left, right) =>
|
|
2255
|
+
SESSION_COLORS.findIndex((entry) => entry.id === left.color) -
|
|
2256
|
+
SESSION_COLORS.findIndex((entry) => entry.id === right.color),
|
|
2257
|
+
)
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
export function writeBlueprint(dataDir, sessionIdOrBlueprint, maybeBlueprint) {
|
|
2261
|
+
const incoming =
|
|
2262
|
+
maybeBlueprint === undefined ? sessionIdOrBlueprint : maybeBlueprint
|
|
2263
|
+
return persistBlueprintFile(
|
|
2264
|
+
blueprintFile(dataDir),
|
|
2265
|
+
incoming,
|
|
2266
|
+
readBlueprint(dataDir),
|
|
2267
|
+
)
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
export function writeLocalBlueprint(dataDir, sessionId, incoming) {
|
|
2271
|
+
const safeId = assertSessionId(sessionId)
|
|
2272
|
+
requireManifest(dataDir, safeId)
|
|
2273
|
+
return persistBlueprintFile(
|
|
2274
|
+
localBlueprintFile(dataDir, safeId),
|
|
2275
|
+
incoming,
|
|
2276
|
+
readLocalBlueprint(dataDir, safeId),
|
|
2277
|
+
)
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
export function writeBlueprintByColor(dataDir, colorId, incoming) {
|
|
2281
|
+
if (isGlobalBlueprintColor(colorId)) return writeBlueprint(dataDir, incoming)
|
|
2282
|
+
const sessionId = findSessionIdByColor(dataDir, colorId)
|
|
2283
|
+
if (!sessionId) return emptyBlueprint()
|
|
2284
|
+
return writeLocalBlueprint(dataDir, sessionId, incoming)
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
function blueprintInputFields(input = {}) {
|
|
2288
|
+
const { color: _color, ...fields } = input
|
|
2289
|
+
return fields
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
export function setBlueprintHidden(dataDir, hidden, colorId) {
|
|
2293
|
+
const current = readBlueprintByColor(dataDir, colorId)
|
|
2294
|
+
return writeBlueprintByColor(dataDir, colorId, {
|
|
2295
|
+
...current,
|
|
2296
|
+
hidden: Boolean(hidden),
|
|
2297
|
+
})
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
export function clearBlueprint(dataDir, colorId) {
|
|
2301
|
+
const current = readBlueprintByColor(dataDir, colorId)
|
|
2302
|
+
return writeBlueprintByColor(dataDir, colorId, {
|
|
2004
2303
|
...emptyBlueprint(),
|
|
2005
2304
|
hidden: current.hidden,
|
|
2006
2305
|
revision: current.revision,
|
|
2007
2306
|
})
|
|
2008
2307
|
}
|
|
2009
2308
|
|
|
2010
|
-
export function cleanupBlueprint(
|
|
2011
|
-
|
|
2309
|
+
export function cleanupBlueprint(
|
|
2310
|
+
dataDir,
|
|
2311
|
+
knownFileIds = [],
|
|
2312
|
+
knownFolderPaths = [],
|
|
2313
|
+
colorId,
|
|
2314
|
+
) {
|
|
2315
|
+
const current = readBlueprintByColor(dataDir, colorId)
|
|
2012
2316
|
const files = new Set(knownFileIds)
|
|
2013
2317
|
const folders = new Set(knownFolderPaths)
|
|
2014
2318
|
const removedFiles = new Set(
|
|
@@ -2026,14 +2330,23 @@ export function cleanupBlueprint(dataDir, knownFileIds = [], knownFolderPaths =
|
|
|
2026
2330
|
notes: current.notes.filter((item) => !removedFiles.has(item.file)),
|
|
2027
2331
|
pointers: current.pointers,
|
|
2028
2332
|
}
|
|
2029
|
-
return
|
|
2333
|
+
return writeBlueprintByColor(dataDir, colorId, next)
|
|
2030
2334
|
}
|
|
2031
2335
|
|
|
2032
|
-
export function markBlueprintSeen(dataDir, sessionId, revision) {
|
|
2336
|
+
export function markBlueprintSeen(dataDir, sessionId, revision, localRevision) {
|
|
2033
2337
|
const manifest = readManifest(dataDir, sessionId)
|
|
2034
2338
|
if (!manifest) return null
|
|
2035
|
-
|
|
2036
|
-
|
|
2339
|
+
const nextGlobal = Number.isInteger(revision) ? revision : manifest.blueprintRevision
|
|
2340
|
+
const nextLocal =
|
|
2341
|
+
Number.isInteger(localRevision) ? localRevision : manifest.localBlueprintRevision
|
|
2342
|
+
if (
|
|
2343
|
+
manifest.blueprintRevision === nextGlobal &&
|
|
2344
|
+
manifest.localBlueprintRevision === nextLocal
|
|
2345
|
+
) {
|
|
2346
|
+
return manifest
|
|
2347
|
+
}
|
|
2348
|
+
manifest.blueprintRevision = nextGlobal
|
|
2349
|
+
if (Number.isInteger(localRevision)) manifest.localBlueprintRevision = nextLocal
|
|
2037
2350
|
writeManifest(dataDir, manifest)
|
|
2038
2351
|
return manifest
|
|
2039
2352
|
}
|
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
export function resolvePathValue(value: string | undefined, fallback: string): string
|
|
2
2
|
export function resolveTargetRoot(value?: string): string
|
|
3
3
|
export function resolveDataDir(value?: string): string
|
|
4
|
-
export const targetRoot: string
|
|
5
4
|
export const dataDir: string
|
|
6
|
-
export
|
|
5
|
+
export function persistedTargetFile(dir?: string): string
|
|
6
|
+
export function readPersistedTargetId(dir?: string): string | null
|
|
7
|
+
export function writePersistedTargetId(id: string, dir?: string): void
|
|
8
|
+
export function isWorkspaceDevSwitcherEnabled(options?: {
|
|
9
|
+
exampleTarget?: string
|
|
10
|
+
resolvedDataDir?: string
|
|
11
|
+
explorerDataDir?: string
|
|
12
|
+
}): boolean
|
|
13
|
+
export type WorkspaceTarget = {
|
|
14
|
+
id: string
|
|
15
|
+
label: string
|
|
16
|
+
root: string
|
|
17
|
+
}
|
|
18
|
+
export function listWorkspaceTargets(options?: {
|
|
19
|
+
appsRoot?: string
|
|
20
|
+
repositoryRoot?: string
|
|
21
|
+
}): WorkspaceTarget[]
|
|
22
|
+
export function matchWorkspaceTargetId(
|
|
23
|
+
root: string,
|
|
24
|
+
targets?: WorkspaceTarget[],
|
|
25
|
+
): string | null
|
|
26
|
+
export function resolveInitialTargetRoot(options?: {
|
|
27
|
+
envTarget?: string
|
|
28
|
+
persistedId?: string | null
|
|
29
|
+
switcherEnabled?: boolean
|
|
30
|
+
targets?: WorkspaceTarget[]
|
|
31
|
+
fallback?: string
|
|
32
|
+
}): string
|
|
33
|
+
export let targetRoot: string
|
|
34
|
+
export let targetName: string
|
|
35
|
+
export let targetPathPrefix: string | null
|
|
7
36
|
export function resolveTargetPathPrefix(root?: string): string | null
|
|
8
|
-
export
|
|
37
|
+
export function currentWorkspaceTargetId(): string | null
|
|
38
|
+
export function workspaceDevTargetsState(): {
|
|
39
|
+
enabled: boolean
|
|
40
|
+
currentId: string | null
|
|
41
|
+
targets: Array<{ id: string; label: string }>
|
|
42
|
+
}
|
|
43
|
+
export function setWorkspaceTarget(id: string): WorkspaceTarget
|