@bibobonking/opensol 3.1.5 → 3.2.0
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/CHANGELOG.md +8 -2
- package/LICENSE +21 -0
- package/README.md +24 -20
- package/desktop/README.md +1 -1
- package/dist/assets/{index-C0yvs6Vl.js → index-BUVnnhRH.js} +1 -1
- package/dist/assets/index-CyxlAgKM.js +271 -0
- package/dist/assets/index-DFeF90S8.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/README.md +15 -68
- package/server/daemon.mjs +6 -1
- package/server/lib/deploy.mjs +5 -10
- package/server/lib/shell.mjs +149 -0
- package/server/lib/shell.test.mjs +72 -0
- package/server/lib/tools.mjs +8 -44
- package/src/agent/adapters/anthropic.ts +2 -3
- package/src/agent/adapters/gemini.ts +2 -3
- package/src/agent/adapters/openai-responses.ts +2 -3
- package/src/agent/adapters/openai.ts +2 -3
- package/src/agent/adapters/shared.ts +3 -0
- package/src/components/ChatView.tsx +17 -54
- package/src/components/Composer.tsx +143 -135
- package/src/components/ContextBar.tsx +2 -2
- package/src/components/HomeDashboard.tsx +7 -4
- package/src/components/MessageItem.tsx +33 -1
- package/src/components/ModelDeck.tsx +5 -27
- package/src/components/SettingsModal.tsx +8 -0
- package/src/components/ToolCallCard.tsx +2 -2
- package/src/components/TrajectoryView.tsx +1 -1
- package/src/components/composer/PlusMenu.tsx +117 -0
- package/src/components/composer/Popover.tsx +109 -0
- package/src/components/composer/WorkspacePicker.tsx +102 -0
- package/src/core/plugins/tools/shell.ts +2 -2
- package/src/core/plugins/tools/subagent.ts +18 -8
- package/src/core/services/agent-loop.ts +45 -43
- package/src/core/services/compaction.ts +289 -22
- package/src/core/services/llm.ts +6 -2
- package/src/core/services/subagents.ts +191 -3
- package/src/core/services/system-prompt.ts +35 -7
- package/src/core/services/tools.ts +2 -1
- package/src/core/types.ts +5 -0
- package/src/lib/compact-plan.test.mjs +62 -0
- package/src/lib/compact-plan.ts +188 -0
- package/src/lib/subagent-types.ts +54 -0
- package/src/lib/tool-meta.ts +40 -0
- package/src/store/settings.ts +3 -0
- package/src/store/ui.ts +12 -0
- package/src/types.ts +26 -0
- package/shared/plugin-builder.test.mjs +0 -43
- package/shared/plugin-package.d.mts +0 -34
- package/shared/plugin-package.mjs +0 -197
- package/shared/plugin-package.test.mjs +0 -94
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
const MAX_ARCHIVE_BYTES = 5 * 1024 * 1024
|
|
2
|
-
const MAX_PACKAGE_BYTES = 10 * 1024 * 1024
|
|
3
|
-
const MAX_FILES = 100
|
|
4
|
-
const MAX_MANIFEST_BYTES = 64 * 1024
|
|
5
|
-
const CAPABILITIES = new Set([
|
|
6
|
-
'workspace.list',
|
|
7
|
-
'workspace.read',
|
|
8
|
-
'workspace.write',
|
|
9
|
-
'workspace.edit',
|
|
10
|
-
'ui.commands',
|
|
11
|
-
'ui.panels',
|
|
12
|
-
'ui.themes',
|
|
13
|
-
'network.request',
|
|
14
|
-
])
|
|
15
|
-
const MANIFEST_KEYS = new Set([
|
|
16
|
-
'id',
|
|
17
|
-
'name',
|
|
18
|
-
'version',
|
|
19
|
-
'apiVersion',
|
|
20
|
-
'entry',
|
|
21
|
-
'stylesheet',
|
|
22
|
-
'capabilities',
|
|
23
|
-
'domains',
|
|
24
|
-
])
|
|
25
|
-
|
|
26
|
-
function packageError(message) {
|
|
27
|
-
throw new Error(`Invalid plugin package: ${message}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function bytesToString(bytes) {
|
|
31
|
-
if (typeof bytes === 'string') return bytes
|
|
32
|
-
return new TextDecoder('utf-8').decode(bytes)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function stringToBytes(str) {
|
|
36
|
-
return new TextEncoder().encode(str)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function validateSource(path, content, apiVersion) {
|
|
40
|
-
// API v2: skip validation — full kernel access, blob isolation is enough
|
|
41
|
-
if (apiVersion === 2) return
|
|
42
|
-
|
|
43
|
-
const source = bytesToString(content)
|
|
44
|
-
const rule = path.endsWith('.js')
|
|
45
|
-
? [
|
|
46
|
-
/\b(?:import|export)\b|\b(?:fetch|XMLHttpRequest|WebSocket|EventSource|importScripts)\s*\(|\bnavigator\s*\.\s*sendBeacon\s*\(/i,
|
|
47
|
-
'module imports and unmanaged network calls are forbidden',
|
|
48
|
-
]
|
|
49
|
-
: path.endsWith('.css')
|
|
50
|
-
? [
|
|
51
|
-
/@import\b|url\s*\(\s*(['"]?)(?!data:image\/(?:png|jpeg|gif|webp);base64,)[^)]*\)/i,
|
|
52
|
-
'external CSS resources and imports are forbidden',
|
|
53
|
-
]
|
|
54
|
-
: null
|
|
55
|
-
if (!rule) return
|
|
56
|
-
const found = rule[0].exec(source)
|
|
57
|
-
if (!found) return
|
|
58
|
-
const line = source.slice(0, found.index).split('\n').length
|
|
59
|
-
const hit = found[0].length > 60 ? `${found[0].slice(0, 60)}…` : found[0]
|
|
60
|
-
packageError(`${rule[1]}: found "${hit}" at ${path}:${line}`)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function safePath(value) {
|
|
64
|
-
if (
|
|
65
|
-
typeof value !== 'string' ||
|
|
66
|
-
!value ||
|
|
67
|
-
value.includes('\0') ||
|
|
68
|
-
value.includes('\\') ||
|
|
69
|
-
value.startsWith('/') ||
|
|
70
|
-
/^[a-zA-Z]:/.test(value) ||
|
|
71
|
-
value.split('/').some((part) => !part || part === '.' || part === '..')
|
|
72
|
-
) {
|
|
73
|
-
packageError(`unsafe path "${String(value)}"`)
|
|
74
|
-
}
|
|
75
|
-
return value
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function validateDomain(value) {
|
|
79
|
-
if (typeof value !== 'string' || value.length > 253 || value.endsWith('.')) {
|
|
80
|
-
packageError('invalid domain')
|
|
81
|
-
}
|
|
82
|
-
const labels = value.split('.')
|
|
83
|
-
if (
|
|
84
|
-
labels.length < 2 ||
|
|
85
|
-
labels.some((label) => !/^(?=.{1,63}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label))
|
|
86
|
-
) {
|
|
87
|
-
packageError(`invalid domain "${value}"`)
|
|
88
|
-
}
|
|
89
|
-
return value.toLowerCase()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function validatePluginManifest(value) {
|
|
93
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) packageError('manifest must be an object')
|
|
94
|
-
if (Object.keys(value).some((key) => !MANIFEST_KEYS.has(key))) packageError('unknown manifest field')
|
|
95
|
-
const { id, name, version, apiVersion, entry, stylesheet, capabilities, domains } = value
|
|
96
|
-
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9-]{1,63}$/.test(id)) packageError('invalid plugin id')
|
|
97
|
-
if (typeof name !== 'string' || !name.trim() || name.length > 100) packageError('invalid plugin name')
|
|
98
|
-
if (typeof version !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
99
|
-
packageError('invalid plugin version')
|
|
100
|
-
}
|
|
101
|
-
if (apiVersion !== 1 && apiVersion !== 2) packageError('unsupported API version')
|
|
102
|
-
safePath(entry)
|
|
103
|
-
safePath(stylesheet)
|
|
104
|
-
if (!entry.endsWith('.js') || !stylesheet.endsWith('.css')) packageError('entry must be JavaScript and stylesheet must be CSS')
|
|
105
|
-
|
|
106
|
-
// API v2: capabilities optional (defaults to [])
|
|
107
|
-
const normalizedCapabilities = capabilities ?? []
|
|
108
|
-
if (!Array.isArray(normalizedCapabilities)) packageError('capabilities must be an array')
|
|
109
|
-
if (apiVersion === 1 && normalizedCapabilities.some((item) => !CAPABILITIES.has(item))) {
|
|
110
|
-
packageError('invalid capability')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (domains !== undefined && !Array.isArray(domains)) packageError('domains must be an array')
|
|
114
|
-
const normalizedDomains = (domains ?? []).map(validateDomain)
|
|
115
|
-
if (new Set(normalizedDomains).size !== normalizedDomains.length) packageError('duplicate domain')
|
|
116
|
-
return {
|
|
117
|
-
id,
|
|
118
|
-
name: name.trim(),
|
|
119
|
-
version,
|
|
120
|
-
apiVersion,
|
|
121
|
-
entry,
|
|
122
|
-
stylesheet,
|
|
123
|
-
capabilities: [...new Set(normalizedCapabilities)],
|
|
124
|
-
domains: normalizedDomains,
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function normalizeFiles(files, manifest) {
|
|
129
|
-
if (!files || typeof files !== 'object' || Array.isArray(files)) packageError('files must be a path-to-source object')
|
|
130
|
-
const paths = Object.keys(files)
|
|
131
|
-
if (paths.length > MAX_FILES) packageError('too many files')
|
|
132
|
-
const normalized = {}
|
|
133
|
-
let total = 0
|
|
134
|
-
for (const rawPath of paths) {
|
|
135
|
-
const filePath = safePath(rawPath)
|
|
136
|
-
if (filePath === 'manifest.json') packageError('manifest is generated from the manifest field')
|
|
137
|
-
if (/\.(?:mjs|cjs|jsx|ts|tsx)$/i.test(filePath) && filePath !== manifest.entry) {
|
|
138
|
-
packageError(`undeclared executable file "${filePath}"`)
|
|
139
|
-
}
|
|
140
|
-
if (filePath !== manifest.entry && filePath !== manifest.stylesheet && /\.(?:js|css)$/i.test(filePath)) {
|
|
141
|
-
packageError(`undeclared executable file "${filePath}"`)
|
|
142
|
-
}
|
|
143
|
-
const content = files[rawPath]
|
|
144
|
-
if (typeof content !== 'string' && !(content instanceof Uint8Array)) {
|
|
145
|
-
packageError(`file "${filePath}" must be text or bytes`)
|
|
146
|
-
}
|
|
147
|
-
const data = typeof content === 'string' ? stringToBytes(content) : content
|
|
148
|
-
total += data.byteLength
|
|
149
|
-
if (total > MAX_PACKAGE_BYTES) packageError('package is too large')
|
|
150
|
-
if (filePath === manifest.entry || filePath === manifest.stylesheet) validateSource(filePath, data, manifest.apiVersion)
|
|
151
|
-
normalized[filePath] = typeof content === 'string' ? content : bytesToString(data)
|
|
152
|
-
}
|
|
153
|
-
if (!(manifest.entry in normalized)) packageError('entry file is missing')
|
|
154
|
-
if (!(manifest.stylesheet in normalized)) packageError('stylesheet file is missing')
|
|
155
|
-
if (stringToBytes(normalized[manifest.entry]).byteLength > 2 * 1024 * 1024) packageError('entry file is too large')
|
|
156
|
-
return normalized
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function buildPluginArchive(sources) {
|
|
160
|
-
const manifest = validatePluginManifest(sources?.manifest)
|
|
161
|
-
const files = normalizeFiles(sources?.files, manifest)
|
|
162
|
-
const manifestJson = JSON.stringify(manifest)
|
|
163
|
-
if (stringToBytes(manifestJson).byteLength > MAX_MANIFEST_BYTES) packageError('manifest is too large')
|
|
164
|
-
|
|
165
|
-
const packageData = {
|
|
166
|
-
format: 'cordis-plugin',
|
|
167
|
-
manifest,
|
|
168
|
-
files,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const encoded = stringToBytes(JSON.stringify(packageData))
|
|
172
|
-
if (encoded.byteLength > MAX_ARCHIVE_BYTES) packageError('package is too large')
|
|
173
|
-
return encoded
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function readPluginArchive(bytes) {
|
|
177
|
-
if (!bytes || !(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
|
|
178
|
-
packageError('package archive is empty or invalid')
|
|
179
|
-
}
|
|
180
|
-
let parsed
|
|
181
|
-
try {
|
|
182
|
-
const text = new TextDecoder('utf-8').decode(bytes)
|
|
183
|
-
parsed = JSON.parse(text)
|
|
184
|
-
} catch {
|
|
185
|
-
packageError('package is not a valid JSON archive')
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!parsed || typeof parsed !== 'object') packageError('invalid package format')
|
|
189
|
-
const manifest = validatePluginManifest(parsed.manifest)
|
|
190
|
-
const files = parsed.files
|
|
191
|
-
if (!files || typeof files !== 'object') packageError('missing package files')
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
manifest,
|
|
195
|
-
files,
|
|
196
|
-
}
|
|
197
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import test from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { buildPluginArchive, readPluginArchive } from './plugin-package.mjs'
|
|
4
|
-
|
|
5
|
-
const source = {
|
|
6
|
-
manifest: {
|
|
7
|
-
id: 'sample-plugin',
|
|
8
|
-
name: 'Sample Plugin',
|
|
9
|
-
version: '1.0.0',
|
|
10
|
-
apiVersion: 1,
|
|
11
|
-
entry: 'plugin.js',
|
|
12
|
-
stylesheet: 'plugin.css',
|
|
13
|
-
capabilities: ['workspace.read'],
|
|
14
|
-
domains: ['api.example.test'],
|
|
15
|
-
},
|
|
16
|
-
files: {
|
|
17
|
-
'plugin.js': 'api.registerCommand("sample", () => "ok")',
|
|
18
|
-
'plugin.css': '.sample { color: red }',
|
|
19
|
-
'icon.svg': '<svg xmlns="http://www.w3.org/2000/svg"></svg>',
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
test('plugin archive round-trips manifest and local source files', () => {
|
|
24
|
-
const archive = buildPluginArchive(source)
|
|
25
|
-
const result = readPluginArchive(archive)
|
|
26
|
-
assert.deepEqual(result.manifest, source.manifest)
|
|
27
|
-
assert.deepEqual(result.files, source.files)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test('plugin archive rejects traversal paths before packaging', () => {
|
|
31
|
-
assert.throws(
|
|
32
|
-
() => buildPluginArchive({ ...source, files: { ...source.files, '../outside.js': 'bad' } }),
|
|
33
|
-
/path/i,
|
|
34
|
-
)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('plugin archive rejects a manifest whose entry is missing', () => {
|
|
38
|
-
assert.throws(() => buildPluginArchive({ ...source, files: { 'plugin.css': 'x' } }), /entry/i)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('plugin archive rejects undeclared executable source', () => {
|
|
42
|
-
assert.throws(
|
|
43
|
-
() => buildPluginArchive({ ...source, files: { ...source.files, 'extra.js': 'alert(1)' } }),
|
|
44
|
-
/executable/i,
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test('plugin archive rejects remote code declarations and invalid domains', () => {
|
|
49
|
-
assert.throws(
|
|
50
|
-
() => buildPluginArchive({ ...source, manifest: { ...source.manifest, entry: 'https://evil.test/p.js' } }),
|
|
51
|
-
/entry|path/i,
|
|
52
|
-
)
|
|
53
|
-
assert.throws(
|
|
54
|
-
() => buildPluginArchive({ ...source, manifest: { ...source.manifest, domains: ['https://example.test'] } }),
|
|
55
|
-
/domain/i,
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('plugin archive rejects unmanaged network APIs and external CSS resources', () => {
|
|
60
|
-
assert.throws(
|
|
61
|
-
() => buildPluginArchive({ ...source, files: { ...source.files, 'plugin.js': 'fetch("https://evil.test")' } }),
|
|
62
|
-
/network|remote/i,
|
|
63
|
-
)
|
|
64
|
-
assert.throws(
|
|
65
|
-
() => buildPluginArchive({ ...source, files: { ...source.files, 'plugin.css': '@import url("https://evil.test/style.css")' } }),
|
|
66
|
-
/network|remote|external/i,
|
|
67
|
-
)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('a rejected source names the file, the line and the text that matched', () => {
|
|
71
|
-
// The pattern is text-only, so a comment or a string trips it too — the message
|
|
72
|
-
// has to point at the match or the author cannot find it.
|
|
73
|
-
assert.throws(
|
|
74
|
-
() =>
|
|
75
|
-
buildPluginArchive({
|
|
76
|
-
...source,
|
|
77
|
-
files: { ...source.files, 'plugin.js': 'maxcode.log("hi")\n// export the palette\nconst x = 1' },
|
|
78
|
-
}),
|
|
79
|
-
/found "export" at plugin\.js:2/,
|
|
80
|
-
)
|
|
81
|
-
assert.throws(() => buildPluginArchive({ ...source, files: { ...source.files, 'plugin.js': 'fetch("https://evil.test")' } }), /"fetch\("/)
|
|
82
|
-
assert.throws(
|
|
83
|
-
() => buildPluginArchive({ ...source, files: { ...source.files, 'plugin.css': '.a{}\n@import url("https://evil.test/x.css")' } }),
|
|
84
|
-
/plugin\.css:2/,
|
|
85
|
-
)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('plugin archive rejects malformed or oversized archives', () => {
|
|
89
|
-
assert.throws(() => readPluginArchive(new Uint8Array([0, 1, 2, 3])), /archive|zip|package/i)
|
|
90
|
-
assert.throws(
|
|
91
|
-
() => buildPluginArchive({ ...source, files: { ...source.files, 'plugin.js': 'x'.repeat(10_485_761) } }),
|
|
92
|
-
/size|large/i,
|
|
93
|
-
)
|
|
94
|
-
})
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["./src/App.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/agent/approval.ts","./src/agent/compact.ts","./src/agent/index.ts","./src/agent/model.ts","./src/agent/prompt.ts","./src/agent/queue.ts","./src/agent/subagent.ts","./src/agent/tools.ts","./src/agent/adapters/anthropic.ts","./src/agent/adapters/gemini.ts","./src/agent/adapters/openai-responses.ts","./src/agent/adapters/openai.ts","./src/agent/adapters/shared.ts","./src/components/ChatView.tsx","./src/components/CommandPalette.tsx","./src/components/Composer.tsx","./src/components/ContextBar.tsx","./src/components/DeployModal.tsx","./src/components/DiffView.tsx","./src/components/ErrorBoundary.tsx","./src/components/FileIcon.tsx","./src/components/HomeDashboard.tsx","./src/components/Logo.tsx","./src/components/Markdown.tsx","./src/components/McpPanel.tsx","./src/components/MessageItem.tsx","./src/components/ModelDeck.tsx","./src/components/PixleSol.tsx","./src/components/PluginBuilder.tsx","./src/components/PluginManager.tsx","./src/components/PreviewPane.tsx","./src/components/SessionsTab.tsx","./src/components/SettingsModal.tsx","./src/components/Sidebar.tsx","./src/components/Splash.tsx","./src/components/SystemPromptButton.tsx","./src/components/ToolCallCard.tsx","./src/components/TopBar.tsx","./src/components/TrajectoryView.tsx","./src/components/WorkspacePanel.tsx","./src/components/ui.tsx","./src/core/kernel.ts","./src/core/mcp-tools.ts","./src/core/types.ts","./src/core/plugins/tools/fs.ts","./src/core/plugins/tools/git.ts","./src/core/plugins/tools/grep.ts","./src/core/plugins/tools/shell.ts","./src/core/plugins/tools/skill.ts","./src/core/plugins/tools/subagent.ts","./src/core/plugins/tools/web.ts","./src/core/services/agent-loop.ts","./src/core/services/approval.ts","./src/core/services/compaction.ts","./src/core/services/llm.ts","./src/core/services/sessions.ts","./src/core/services/subagents.ts","./src/core/services/system-prompt.ts","./src/core/services/titles.ts","./src/core/services/tools.ts","./src/core/services/ui.ts","./src/data/providers.ts","./src/lib/boot.ts","./src/lib/context.ts","./src/lib/conversation.ts","./src/lib/credits.ts","./src/lib/daemon.ts","./src/lib/diff.ts","./src/lib/hooks.ts","./src/lib/mcp-config.ts","./src/lib/permissions.ts","./src/lib/pixlesol.ts","./src/lib/plugin-folder.ts","./src/lib/reasoning.ts","./src/lib/retry.ts","./src/lib/sse.ts","./src/lib/system-prompt.ts","./src/lib/themes.ts","./src/lib/usage.ts","./src/lib/utils.ts","./src/plugins/api.ts","./src/plugins/bridge.d.mts","./src/plugins/index.ts","./src/plugins/preview.ts","./src/plugins/runtime.ts","./src/plugins/types.ts","./src/store/chats.ts","./src/store/mcp.ts","./src/store/models.ts","./src/store/settings.ts","./src/store/skills.ts","./src/store/ui.ts","./src/store/workspace.ts"],"version":"5.9.3"}
|