@jkwd/inbase 0.1.15 → 0.1.16
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 +2 -2
- package/apps/explorer/scripts/branch-changes.mjs +2 -19
- package/apps/explorer/scripts/patch-lib.d.ts +3 -0
- package/apps/explorer/scripts/patch-lib.mjs +3 -0
- package/apps/explorer/scripts/scan-ignore.mjs +156 -0
- package/apps/explorer/scripts/scan-target.mjs +67 -29
- package/apps/explorer/scripts/session-store.d.ts +24 -3
- package/apps/explorer/scripts/session-store.mjs +180 -74
- package/apps/explorer/src/App.tsx +255 -147
- package/apps/explorer/src/agentIntent.ts +87 -5
- package/apps/explorer/src/index.css +106 -5
- package/apps/explorer/src/keyboard.ts +26 -0
- package/apps/explorer/src/scene/BlockPlacer.tsx +2 -11
- package/apps/explorer/src/scene/FileBlock.tsx +12 -4
- package/apps/explorer/src/scene/FolderArea.tsx +20 -0
- package/apps/explorer/src/scene/IslandPlacer.tsx +2 -11
- package/apps/explorer/src/scene/MapView.tsx +41 -10
- package/apps/explorer/src/scene/Player.tsx +9 -9
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +2 -0
- package/apps/explorer/src/scene/World.tsx +46 -1
- package/apps/explorer/src/types.ts +28 -0
- package/apps/explorer/src/ui/HUD.tsx +435 -99
- package/apps/explorer/src/ui/MapContextMenu.tsx +2 -0
- package/apps/explorer/src/userCreated.ts +98 -0
- package/apps/explorer/vite.config.ts +71 -5
- package/bin/session.mjs +40 -12
- package/package.json +2 -1
- package/skill/commands/inbase.md +1 -1
- package/skill/inbase/SKILL.md +24 -22
package/README.md
CHANGED
|
@@ -49,11 +49,11 @@ inbase run --target /path/to/your/project
|
|
|
49
49
|
|
|
50
50
|
Inbase does not call a model itself. The visual coding loop currently supports **Cursor**. The installed skill makes the agent work through the map: it reports a plan, waits on the **HUD** (heads-up display — the overlay panel on the 3D map), edits live files for each invoked step, and records that step as a patch. Those stored patches are the session record and are applied on every step update.
|
|
51
51
|
|
|
52
|
-
Sessions start in the map: click **Setup LLM session**, type an **initial instruction**, optionally place a **blueprint** (`Space` for files, `B` for folders), then open a Cursor chat and run **`/inbase`**. That command connects the chat and starts the work. The layout is the source of truth for the chat.
|
|
52
|
+
Sessions start in the map: click **Setup LLM session**, type an **initial instruction**, optionally place a **blueprint** (`Space` for files, `B` for folders), then open a Cursor chat and run **`/inbase`**. That command connects the chat and starts the work. The layout is the source of truth for the chat. The blueprint is shared across sessions and stays on the map even after those files and folders exist; use **Hide/Show blueprint**, **Clear blueprint**, or **Cleanup blueprint** (drops planned items that already exist). When a session finishes it is discarded; the shared blueprint remains. Restarting the visualizer also starts with no LLM session; leftover session files are not restored.
|
|
53
53
|
|
|
54
54
|
A normal chat request does not open a session. Use `/inbase` after Setup LLM session, or `/skipinbase [request]` to work outside the map. `/inbase` starts the session immediately; `wait-for-blueprint` only reads the optional blueprint.
|
|
55
55
|
|
|
56
|
-
If several sessions are open, `/inbase` attaches the
|
|
56
|
+
If several sessions are open, `/inbase` attaches the oldest session that is still waiting. Sessions that already have an LLM are skipped, and the map window does not need to be focused.
|
|
57
57
|
|
|
58
58
|
Turn on **Make LLM look where I look** if the agent should prefer the island you are standing on and the blocks you are facing. With **Step by step** on, click **Create proposal** to start a step, then **Accept proposal** when the patch is ready. With it off, the LLM implements the full plan; you can still walk Previous/Next over the diffs, then **Accept proposal**. Send an alternative instruction from the HUD to revise the remaining plan, or **Stop** to end the session.
|
|
59
59
|
|
|
@@ -8,23 +8,10 @@ import {
|
|
|
8
8
|
foldersFromFileIds,
|
|
9
9
|
parseUnifiedPatch,
|
|
10
10
|
} from './patch-lib.mjs'
|
|
11
|
+
import { shouldIgnoreRelativePath, toPosix } from './scan-ignore.mjs'
|
|
11
12
|
|
|
12
|
-
const SKIP_DIRS = new Set([
|
|
13
|
-
'node_modules',
|
|
14
|
-
'dist',
|
|
15
|
-
'build',
|
|
16
|
-
'out',
|
|
17
|
-
'coverage',
|
|
18
|
-
'.git',
|
|
19
|
-
'.inbase',
|
|
20
|
-
])
|
|
21
|
-
const SKIP_FILES = new Set(['package-lock.json'])
|
|
22
13
|
const BINARY_PROBE_BYTES = 8000
|
|
23
14
|
|
|
24
|
-
function toPosix(filePath) {
|
|
25
|
-
return filePath.split(path.sep).join('/')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
15
|
function unique(ids) {
|
|
29
16
|
return [...new Set(ids)]
|
|
30
17
|
}
|
|
@@ -64,11 +51,7 @@ function isBinaryFile(filePath) {
|
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
function shouldSkipPath(fileId) {
|
|
67
|
-
|
|
68
|
-
if (parts.some((part) => part.startsWith('.') || SKIP_DIRS.has(part))) {
|
|
69
|
-
return true
|
|
70
|
-
}
|
|
71
|
-
return SKIP_FILES.has(parts.at(-1) ?? '')
|
|
54
|
+
return shouldIgnoreRelativePath(fileId)
|
|
72
55
|
}
|
|
73
56
|
|
|
74
57
|
function currentBranch(cwd) {
|
|
@@ -114,12 +114,15 @@ export const emptyIntent: {
|
|
|
114
114
|
initialInstruction: string | null
|
|
115
115
|
creationMode: boolean
|
|
116
116
|
canEnterBlueprint: boolean
|
|
117
|
+
blueprintHidden?: boolean
|
|
118
|
+
blueprintRevision?: number
|
|
117
119
|
blueprintSessionId: string | null
|
|
118
120
|
userCreatedBlocks: unknown[]
|
|
119
121
|
userCreatedIslands: unknown[]
|
|
120
122
|
blueprintFunctions: unknown[]
|
|
121
123
|
blueprintVariables: unknown[]
|
|
122
124
|
blueprintImports: unknown[]
|
|
125
|
+
blueprintNotes: unknown[]
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
export function isLastStep(intent: {
|
|
@@ -541,12 +541,15 @@ export const emptyIntent = {
|
|
|
541
541
|
initialInstruction: null,
|
|
542
542
|
creationMode: false,
|
|
543
543
|
canEnterBlueprint: false,
|
|
544
|
+
blueprintHidden: false,
|
|
545
|
+
blueprintRevision: 0,
|
|
544
546
|
blueprintSessionId: null,
|
|
545
547
|
userCreatedBlocks: [],
|
|
546
548
|
userCreatedIslands: [],
|
|
547
549
|
blueprintFunctions: [],
|
|
548
550
|
blueprintVariables: [],
|
|
549
551
|
blueprintImports: [],
|
|
552
|
+
blueprintNotes: [],
|
|
550
553
|
}
|
|
551
554
|
|
|
552
555
|
export function isLastStep(intent) {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export const IGNORE_DIR_NAMES = new Set([
|
|
5
|
+
'node_modules',
|
|
6
|
+
'dist',
|
|
7
|
+
'build',
|
|
8
|
+
'out',
|
|
9
|
+
'coverage',
|
|
10
|
+
'.git',
|
|
11
|
+
'.inbase',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
export const IGNORE_FILE_NAMES = new Set(['package-lock.json'])
|
|
15
|
+
|
|
16
|
+
export function toPosix(filePath) {
|
|
17
|
+
return filePath.split(path.sep).join('/')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** True when any path segment is ignored, e.g. apps/web/node_modules/pkg/index.js. */
|
|
21
|
+
export function shouldIgnoreRelativePath(relative) {
|
|
22
|
+
const parts = toPosix(relative).split('/').filter(Boolean)
|
|
23
|
+
if (
|
|
24
|
+
parts.some(
|
|
25
|
+
(part) =>
|
|
26
|
+
IGNORE_DIR_NAMES.has(part) ||
|
|
27
|
+
(part.startsWith('.') && part !== '.' && part !== '..'),
|
|
28
|
+
)
|
|
29
|
+
) {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
return IGNORE_FILE_NAMES.has(parts.at(-1) ?? '')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseGitignore(text) {
|
|
36
|
+
const rules = []
|
|
37
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
38
|
+
const trimmed = raw.trim()
|
|
39
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
40
|
+
let pattern = trimmed
|
|
41
|
+
const negated = pattern.startsWith('!')
|
|
42
|
+
if (negated) pattern = pattern.slice(1)
|
|
43
|
+
const dirOnly = pattern.endsWith('/')
|
|
44
|
+
if (dirOnly) pattern = pattern.slice(0, -1)
|
|
45
|
+
const fromRoot = pattern.startsWith('/')
|
|
46
|
+
if (fromRoot) pattern = pattern.slice(1)
|
|
47
|
+
const anchored = fromRoot || pattern.includes('/')
|
|
48
|
+
rules.push({ negated, dirOnly, anchored, pattern })
|
|
49
|
+
}
|
|
50
|
+
return rules
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function globToRegExp(pattern) {
|
|
54
|
+
let source = '^'
|
|
55
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
56
|
+
const char = pattern[index]
|
|
57
|
+
if (char === '*' && pattern[index + 1] === '*') {
|
|
58
|
+
source += '.*'
|
|
59
|
+
index += 1
|
|
60
|
+
if (pattern[index + 1] === '/') index += 1
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
if (char === '*') {
|
|
64
|
+
source += '[^/]*'
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
if (char === '?') {
|
|
68
|
+
source += '[^/]'
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
if ('\\.^$+{}()|[]'.includes(char)) source += `\\${char}`
|
|
72
|
+
else source += char
|
|
73
|
+
}
|
|
74
|
+
source += '(/.*)?$'
|
|
75
|
+
return new RegExp(source)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function ruleMatches(relative, rule) {
|
|
79
|
+
if (!rule.pattern) return false
|
|
80
|
+
if (rule.anchored || rule.pattern.includes('*') || rule.pattern.includes('?')) {
|
|
81
|
+
const source = rule.anchored ? rule.pattern : `**/${rule.pattern}`
|
|
82
|
+
return globToRegExp(source).test(relative) || globToRegExp(rule.pattern).test(relative)
|
|
83
|
+
}
|
|
84
|
+
const parts = relative.split('/').filter(Boolean)
|
|
85
|
+
if (rule.dirOnly) {
|
|
86
|
+
return parts.slice(0, -1).includes(rule.pattern) || relative === rule.pattern
|
|
87
|
+
}
|
|
88
|
+
return parts.includes(rule.pattern)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function matchesGitignoreRules(relative, rules) {
|
|
92
|
+
const rel = toPosix(relative).replace(/^\/+/, '')
|
|
93
|
+
if (!rel) return false
|
|
94
|
+
let ignored = false
|
|
95
|
+
for (const rule of rules) {
|
|
96
|
+
if (ruleMatches(rel, rule)) ignored = !rule.negated
|
|
97
|
+
}
|
|
98
|
+
return ignored
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function readGitignoreRules(dir) {
|
|
102
|
+
try {
|
|
103
|
+
return parseGitignore(fs.readFileSync(path.join(dir, '.gitignore'), 'utf8'))
|
|
104
|
+
} catch {
|
|
105
|
+
return []
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function collectGitignoreSets(root) {
|
|
110
|
+
const resolved = path.resolve(root)
|
|
111
|
+
if (isIgnoredByEnclosingGitignore(resolved)) {
|
|
112
|
+
const rules = readGitignoreRules(resolved)
|
|
113
|
+
return rules.length ? [{ base: resolved, rules }] : []
|
|
114
|
+
}
|
|
115
|
+
const chain = [resolved]
|
|
116
|
+
let dir = resolved
|
|
117
|
+
while (!fs.existsSync(path.join(dir, '.git'))) {
|
|
118
|
+
const parent = path.dirname(dir)
|
|
119
|
+
if (parent === dir) {
|
|
120
|
+
const rules = readGitignoreRules(resolved)
|
|
121
|
+
return rules.length ? [{ base: resolved, rules }] : []
|
|
122
|
+
}
|
|
123
|
+
dir = parent
|
|
124
|
+
chain.push(dir)
|
|
125
|
+
}
|
|
126
|
+
const sets = []
|
|
127
|
+
for (const base of chain.reverse()) {
|
|
128
|
+
const rules = readGitignoreRules(base)
|
|
129
|
+
if (rules.length) sets.push({ base, rules })
|
|
130
|
+
}
|
|
131
|
+
return sets
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isIgnoredByEnclosingGitignore(resolved) {
|
|
135
|
+
let dir = path.dirname(resolved)
|
|
136
|
+
for (;;) {
|
|
137
|
+
const rules = readGitignoreRules(dir)
|
|
138
|
+
if (rules.length) {
|
|
139
|
+
const relative = toPosix(path.relative(dir, resolved))
|
|
140
|
+
if (relative && matchesGitignoreRules(relative, rules)) return true
|
|
141
|
+
}
|
|
142
|
+
if (fs.existsSync(path.join(dir, '.git'))) return false
|
|
143
|
+
const parent = path.dirname(dir)
|
|
144
|
+
if (parent === dir) return false
|
|
145
|
+
dir = parent
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function isIgnoredByGitignore(absolutePath, ignoreSets) {
|
|
150
|
+
for (const { base, rules } of ignoreSets) {
|
|
151
|
+
const relative = toPosix(path.relative(base, absolutePath))
|
|
152
|
+
if (!relative || relative.startsWith('..')) continue
|
|
153
|
+
if (matchesGitignoreRules(relative, rules)) return true
|
|
154
|
+
}
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
@@ -7,6 +7,13 @@ import {
|
|
|
7
7
|
collectImportSpecifiers,
|
|
8
8
|
extractJsSymbols,
|
|
9
9
|
} from './js-source.mjs'
|
|
10
|
+
import {
|
|
11
|
+
collectGitignoreSets,
|
|
12
|
+
isIgnoredByGitignore,
|
|
13
|
+
readGitignoreRules,
|
|
14
|
+
shouldIgnoreRelativePath,
|
|
15
|
+
toPosix,
|
|
16
|
+
} from './scan-ignore.mjs'
|
|
10
17
|
import {
|
|
11
18
|
dataDir as defaultDataDir,
|
|
12
19
|
targetName as defaultTargetName,
|
|
@@ -14,49 +21,78 @@ import {
|
|
|
14
21
|
} from './target-config.mjs'
|
|
15
22
|
|
|
16
23
|
const defaultOutPath = path.join(defaultDataDir, 'codebase.json')
|
|
17
|
-
const IGNORE_DIRS = new Set([
|
|
18
|
-
'node_modules',
|
|
19
|
-
'dist',
|
|
20
|
-
'build',
|
|
21
|
-
'out',
|
|
22
|
-
'coverage',
|
|
23
|
-
'.git',
|
|
24
|
-
'.inbase',
|
|
25
|
-
])
|
|
26
|
-
const IGNORE_FILES = new Set(['package-lock.json'])
|
|
27
24
|
const BINARY_PROBE_BYTES = 8000
|
|
28
25
|
|
|
29
|
-
function
|
|
30
|
-
|
|
26
|
+
function isBinaryFile(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
const fd = fs.openSync(filePath, 'r')
|
|
29
|
+
try {
|
|
30
|
+
const buf = Buffer.alloc(BINARY_PROBE_BYTES)
|
|
31
|
+
const bytes = fs.readSync(fd, buf, 0, buf.length, 0)
|
|
32
|
+
return buf.subarray(0, bytes).includes(0)
|
|
33
|
+
} finally {
|
|
34
|
+
fs.closeSync(fd)
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
function
|
|
34
|
-
const fd = fs.openSync(filePath, 'r')
|
|
41
|
+
function resolvesThroughIgnored(absolutePath, root) {
|
|
35
42
|
try {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const real = fs.realpathSync(absolutePath)
|
|
44
|
+
const realRoot = fs.realpathSync(root)
|
|
45
|
+
if (!(real === realRoot || real.startsWith(realRoot + path.sep))) return true
|
|
46
|
+
return shouldIgnoreRelativePath(path.relative(realRoot, real))
|
|
47
|
+
} catch {
|
|
48
|
+
return true
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
function walk(dir, acc = []) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
function walk(dir, root, ignoreSets, acc = []) {
|
|
53
|
+
const localRules = readGitignoreRules(dir)
|
|
54
|
+
const nextSets = localRules.length
|
|
55
|
+
? [...ignoreSets, { base: dir, rules: localRules }]
|
|
56
|
+
: ignoreSets
|
|
57
|
+
let entries
|
|
58
|
+
try {
|
|
59
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
60
|
+
} catch {
|
|
61
|
+
return acc
|
|
62
|
+
}
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const absolutePath = path.join(dir, entry.name)
|
|
65
|
+
const relative = toPosix(path.relative(root, absolutePath))
|
|
66
|
+
if (
|
|
67
|
+
shouldIgnoreRelativePath(relative) ||
|
|
68
|
+
resolvesThroughIgnored(absolutePath, root) ||
|
|
69
|
+
isIgnoredByGitignore(absolutePath, nextSets)
|
|
70
|
+
) {
|
|
50
71
|
continue
|
|
51
72
|
}
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
let stat = entry
|
|
74
|
+
if (entry.isSymbolicLink()) {
|
|
75
|
+
try {
|
|
76
|
+
stat = fs.statSync(absolutePath)
|
|
77
|
+
} catch {
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (stat.isDirectory()) {
|
|
82
|
+
walk(absolutePath, root, nextSets, acc)
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
if (!stat.isFile()) continue
|
|
54
86
|
if (isBinaryFile(absolutePath)) continue
|
|
55
87
|
acc.push(absolutePath)
|
|
56
88
|
}
|
|
57
89
|
return acc
|
|
58
90
|
}
|
|
59
91
|
|
|
92
|
+
function listSourceAbsolutes(root) {
|
|
93
|
+
return walk(root, root, collectGitignoreSets(root))
|
|
94
|
+
}
|
|
95
|
+
|
|
60
96
|
function languageOf(filePath) {
|
|
61
97
|
const ext = path.extname(filePath).slice(1).toLowerCase()
|
|
62
98
|
return ext || 'txt'
|
|
@@ -140,7 +176,9 @@ function ensureFolder(folders, folderPath, rootName) {
|
|
|
140
176
|
|
|
141
177
|
export function listSourceFiles(root) {
|
|
142
178
|
if (!root || !fs.existsSync(root) || !fs.statSync(root).isDirectory()) return []
|
|
143
|
-
return
|
|
179
|
+
return listSourceAbsolutes(root).map((absolutePath) =>
|
|
180
|
+
toPosix(path.relative(root, absolutePath)),
|
|
181
|
+
)
|
|
144
182
|
}
|
|
145
183
|
|
|
146
184
|
export function scanTarget({
|
|
@@ -154,7 +192,7 @@ export function scanTarget({
|
|
|
154
192
|
)
|
|
155
193
|
}
|
|
156
194
|
|
|
157
|
-
const absoluteFiles =
|
|
195
|
+
const absoluteFiles = listSourceAbsolutes(root)
|
|
158
196
|
const folders = new Map()
|
|
159
197
|
ensureFolder(folders, '.', name)
|
|
160
198
|
|
|
@@ -133,6 +133,8 @@ export function readAttachedSession(dataDir: string): string | null
|
|
|
133
133
|
export function listAttachQueue(dataDir: string): string[]
|
|
134
134
|
export function nextAttachSessionId(dataDir: string): string | null
|
|
135
135
|
export type SessionBlueprint = {
|
|
136
|
+
hidden: boolean
|
|
137
|
+
revision: number
|
|
136
138
|
enabled: boolean
|
|
137
139
|
sent: boolean
|
|
138
140
|
userCreatedBlocks: unknown[]
|
|
@@ -140,14 +142,31 @@ export type SessionBlueprint = {
|
|
|
140
142
|
addedFunctions: unknown[]
|
|
141
143
|
addedVariables: unknown[]
|
|
142
144
|
addedImports: unknown[]
|
|
145
|
+
notes: unknown[]
|
|
143
146
|
}
|
|
144
147
|
export function emptyBlueprint(): SessionBlueprint
|
|
145
|
-
export function readBlueprint(dataDir: string, sessionId
|
|
148
|
+
export function readBlueprint(dataDir: string, sessionId?: string): SessionBlueprint
|
|
149
|
+
export function writeBlueprint(
|
|
150
|
+
dataDir: string,
|
|
151
|
+
blueprint: SessionBlueprint,
|
|
152
|
+
): SessionBlueprint
|
|
146
153
|
export function writeBlueprint(
|
|
147
154
|
dataDir: string,
|
|
148
155
|
sessionId: string,
|
|
149
156
|
blueprint: SessionBlueprint,
|
|
150
|
-
):
|
|
157
|
+
): SessionBlueprint
|
|
158
|
+
export function setBlueprintHidden(dataDir: string, hidden: boolean): SessionBlueprint
|
|
159
|
+
export function clearBlueprint(dataDir: string): SessionBlueprint
|
|
160
|
+
export function cleanupBlueprint(
|
|
161
|
+
dataDir: string,
|
|
162
|
+
knownFileIds?: string[],
|
|
163
|
+
knownFolderPaths?: string[],
|
|
164
|
+
): SessionBlueprint
|
|
165
|
+
export function markBlueprintSeen(
|
|
166
|
+
dataDir: string,
|
|
167
|
+
sessionId: string,
|
|
168
|
+
revision: number,
|
|
169
|
+
): DiffManifest | null
|
|
151
170
|
export function answerBlueprint(
|
|
152
171
|
dataDir: string,
|
|
153
172
|
sessionId: string,
|
|
@@ -155,13 +174,14 @@ export function answerBlueprint(
|
|
|
155
174
|
): DiffManifest
|
|
156
175
|
export function updateBlueprint(
|
|
157
176
|
dataDir: string,
|
|
158
|
-
sessionId
|
|
177
|
+
sessionId?: string | null,
|
|
159
178
|
input?: {
|
|
160
179
|
userCreatedBlocks?: unknown[]
|
|
161
180
|
userCreatedIslands?: unknown[]
|
|
162
181
|
addedFunctions?: unknown[]
|
|
163
182
|
addedVariables?: unknown[]
|
|
164
183
|
addedImports?: unknown[]
|
|
184
|
+
notes?: unknown[]
|
|
165
185
|
},
|
|
166
186
|
): SessionBlueprint
|
|
167
187
|
export function sendBlueprint(
|
|
@@ -173,6 +193,7 @@ export function sendBlueprint(
|
|
|
173
193
|
addedFunctions?: unknown[]
|
|
174
194
|
addedVariables?: unknown[]
|
|
175
195
|
addedImports?: unknown[]
|
|
196
|
+
notes?: unknown[]
|
|
176
197
|
},
|
|
177
198
|
): DiffManifest
|
|
178
199
|
export function maybeStartVisualizerHandshake(
|