@davait/aipictury 1.9.2 → 1.10.3
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/ext/agent.js +6 -1
- package/ext/extension.js +3 -0
- package/ext/gpuRun.js +38 -6
- package/ext/package.json +2 -2
- package/ext/storage.js +3 -2
- package/main.js +2 -0
- package/package.json +1 -1
package/ext/agent.js
CHANGED
|
@@ -1046,7 +1046,10 @@ async function runHelper(args, { server, session, signal, report, hooks, model }
|
|
|
1046
1046
|
const allowedTools = role?.tools.length ? new Set(role.tools.flatMap((t) => AGENT_TOOLS[t] || [t])) : null
|
|
1047
1047
|
const writes = !allowedTools || [...allowedTools].some((t) => WRITES.has(t) || t === 'run_command')
|
|
1048
1048
|
const type = role ? (writes ? 'general' : 'explore') : args.subagent_type === 'general' ? 'general' : 'explore'
|
|
1049
|
-
|
|
1049
|
+
// The run's own rules hold for its helpers too: a tool denied for the run (--disallowedTools)
|
|
1050
|
+
// was still offered to a helper, which ran the command the run could not.
|
|
1051
|
+
const sub = { allowAll: session.allowAll, allowCommands: session.allowCommands, allowSites: session.allowSites, seen: new Map(), cwd: session.cwd, worktree: session.worktree,
|
|
1052
|
+
...(session.commandAllow ? { commandAllow: session.commandAllow } : {}), ...(session.commandDeny ? { commandDeny: session.commandDeny } : {}) }
|
|
1050
1053
|
// isolation: "worktree" — the helper works in a checkout of its own.
|
|
1051
1054
|
let ownTree = null
|
|
1052
1055
|
if (args.isolation === 'worktree') {
|
|
@@ -1055,7 +1058,9 @@ async function runHelper(args, { server, session, signal, report, hooks, model }
|
|
|
1055
1058
|
sub.cwd = ownTree.path
|
|
1056
1059
|
}
|
|
1057
1060
|
const readOnly = type === 'explore' || modeNow() === 'plan'
|
|
1061
|
+
const denied = new Set((session.commandDeny || []).flatMap((rule) => permissions.toolsNamed(rule)))
|
|
1058
1062
|
const tools = toolSchema({ readOnly, helper: true, extra: [...mcp.schema({ readOnly, loaded: (session.loadedMcp ||= new Set()) }), ...mcp.resourceSchema()] })
|
|
1063
|
+
.filter((t) => !denied.has(t.function?.name))
|
|
1059
1064
|
.filter((t) => !allowedTools || allowedTools.has(t.function.name) || (t.function.name.startsWith('mcp__') && allowedTools.has(t.function.name)))
|
|
1060
1065
|
const id = `agent_${helpersOf(session).size + 1}`
|
|
1061
1066
|
const h = {
|
package/ext/extension.js
CHANGED
|
@@ -2295,6 +2295,9 @@ function moveConversationsToDisk(context) {
|
|
|
2295
2295
|
}
|
|
2296
2296
|
|
|
2297
2297
|
function activate(context) {
|
|
2298
|
+
// This window names itself on every call to its GPU server (gpuRun.js).
|
|
2299
|
+
gpuRun.setApp({ client: 'vscode', server: serverUrl })
|
|
2300
|
+
gpuRun.nameCalls()
|
|
2298
2301
|
try { moveConversationsToDisk(context) } catch { /* tried again next start */ }
|
|
2299
2302
|
syncCommandEnv().catch(() => { /* again before each run */ })
|
|
2300
2303
|
undoOldThinkingSwitch(context).catch(() => { /* the setting stays as it was */ })
|
package/ext/gpuRun.js
CHANGED
|
@@ -10,11 +10,20 @@ const { AsyncLocalStorage } = require('async_hooks')
|
|
|
10
10
|
const store = new AsyncLocalStorage()
|
|
11
11
|
const BEAT = 30_000
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// This app (this VS Code window, this CLI): on every call to its server, so the server lets its
|
|
14
|
+
// quick calls (a title, a rule saved) and its own runs share the GPU — never another app's.
|
|
15
|
+
const app = { id: `app-${crypto.randomUUID()}`, client: 'vscode', server: () => '' }
|
|
16
|
+
/** Who this app is ('vscode' | 'cli') and where its GPU server is (a function: it can change). */
|
|
17
|
+
function setApp({ client, server } = {}) { if (client) app.client = client; if (server) app.server = server }
|
|
18
|
+
|
|
19
|
+
/** The headers of every call this code makes to its server: the app's, and the run's in a run. */
|
|
14
20
|
function headers() {
|
|
21
|
+
const own = { 'X-AIPictury-App': app.id, 'X-AIPictury-Client': app.client }
|
|
15
22
|
const r = store.getStore()
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
// Work a run left going after it ended (a rule saved, a title written) is no longer that run:
|
|
24
|
+
// named so, it took the GPU back for a run that was over, and other apps waited 90 s.
|
|
25
|
+
if (!r || r.ended) return own
|
|
26
|
+
return { ...own, 'X-AIPictury-Client': r.client, 'X-AIPictury-Run': r.run, 'X-AIPictury-Label': encodeURIComponent(r.label || '') }
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
class GpuBusyError extends Error {
|
|
@@ -30,8 +39,29 @@ class GpuBusyError extends Error {
|
|
|
30
39
|
* fn() as a run holding the GPU of `server`. `client`: 'vscode' | 'cli'. A GPU held by another
|
|
31
40
|
* run: GpuBusyError before anything is done. A server without holds (older): fn() as before.
|
|
32
41
|
*/
|
|
42
|
+
// Every call a run makes to its own server carries the run, whoever made the call: not only
|
|
43
|
+
// through the account's fetch (account.js serverFetch), which a window without an account (the
|
|
44
|
+
// extension's tests) does not have — there the run's own model calls were refused as another's.
|
|
45
|
+
function nameCalls() {
|
|
46
|
+
if (globalThis.fetch.namesRuns) return
|
|
47
|
+
const base = globalThis.fetch
|
|
48
|
+
const named = (input, init = {}) => {
|
|
49
|
+
const r = store.getStore()
|
|
50
|
+
const url = typeof input === 'string' ? input : input?.url || String(input)
|
|
51
|
+
const server = (r && !r.ended && r.server) || String(app.server() || '').replace(/\/+$/, '')
|
|
52
|
+
if (!server || !url.startsWith(server)) return base(input, init)
|
|
53
|
+
const h = new Headers(init.headers || (typeof input === 'object' && input?.headers) || undefined)
|
|
54
|
+
for (const [k, v] of Object.entries(headers())) if (!h.has(k)) h.set(k, v)
|
|
55
|
+
return base(input, { ...init, headers: h })
|
|
56
|
+
}
|
|
57
|
+
named.namesRuns = true
|
|
58
|
+
globalThis.fetch = named
|
|
59
|
+
}
|
|
60
|
+
|
|
33
61
|
async function holding({ server, client, label = '' }, fn) {
|
|
34
|
-
|
|
62
|
+
nameCalls()
|
|
63
|
+
const run = { client, server: String(server || '').replace(/\/+$/, ''), run: `${client}-${crypto.randomUUID()}`,
|
|
64
|
+
label: String(label).replace(/\s+/g, ' ').slice(0, 100) }
|
|
35
65
|
return store.run(run, async () => {
|
|
36
66
|
const h = headers()
|
|
37
67
|
let held = false
|
|
@@ -53,6 +83,7 @@ async function holding({ server, client, label = '' }, fn) {
|
|
|
53
83
|
try {
|
|
54
84
|
return await fn()
|
|
55
85
|
} finally {
|
|
86
|
+
run.ended = true
|
|
56
87
|
if (beat) clearInterval(beat)
|
|
57
88
|
if (held) await fetch(`${server}/api/gpu/hold`, { method: 'DELETE', headers: h, signal: AbortSignal.timeout(10_000) }).catch(() => {})
|
|
58
89
|
}
|
|
@@ -70,8 +101,9 @@ async function state(server) {
|
|
|
70
101
|
/** The hold of another run on every GPU (all busy), or null (one is free, or it is this run's). */
|
|
71
102
|
function busyFor(gpu, mine = null) {
|
|
72
103
|
const lanes = gpu?.lanes || []
|
|
73
|
-
|
|
104
|
+
// A quick call (no run: a title, a rule saved) is done in a moment; the server lets its owner in.
|
|
105
|
+
if (!lanes.length || lanes.some((l) => !l.hold || l.hold.run === mine || l.hold.side)) return null
|
|
74
106
|
return lanes[0].hold
|
|
75
107
|
}
|
|
76
108
|
|
|
77
|
-
module.exports = { holding, headers, state, busyFor, GpuBusyError, store }
|
|
109
|
+
module.exports = { holding, headers, setApp, nameCalls, state, busyFor, GpuBusyError, store }
|
package/ext/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "aipictury",
|
|
3
3
|
"displayName": "AIPictury",
|
|
4
4
|
"description": "A coding agent and a generation studio running on your own GPU, with uncensored local models.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.10.3",
|
|
6
6
|
"publisher": "aipictury",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
8
8
|
"engines": {
|
|
@@ -1203,7 +1203,7 @@
|
|
|
1203
1203
|
"@eslint/js": "^10.0.1",
|
|
1204
1204
|
"@types/vscode": "^1.95.0",
|
|
1205
1205
|
"@vscode/test-electron": "^3.1.0",
|
|
1206
|
-
"@vscode/vsce": "^
|
|
1206
|
+
"@vscode/vsce": "^4.0.0",
|
|
1207
1207
|
"dompurify": "^3.4.16",
|
|
1208
1208
|
"esbuild": "^0.28.2",
|
|
1209
1209
|
"eslint": "^10.11.0",
|
package/ext/storage.js
CHANGED
|
@@ -26,11 +26,12 @@ class Storage {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Stores bytes at `path` ("users/u/media/a.png"). Resolves to the object's metadata. A multipart
|
|
29
|
-
* upload, as the SDK's: the type goes in the object's metadata, where the rules read it
|
|
29
|
+
* upload, as the SDK's: the type goes in the object's metadata, where the rules read it; and
|
|
30
|
+
* no-store, so no browser that shows it keeps a copy past signing out. */
|
|
30
31
|
async upload(path, bytes, contentType) {
|
|
31
32
|
const boundary = `aipictury-${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`
|
|
32
33
|
const body = Buffer.concat([
|
|
33
|
-
Buffer.from(`--${boundary}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n${JSON.stringify({ name: path, contentType })}\r\n`
|
|
34
|
+
Buffer.from(`--${boundary}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n${JSON.stringify({ name: path, contentType, cacheControl: 'private, no-store' })}\r\n`
|
|
34
35
|
+ `--${boundary}\r\nContent-Type: ${contentType}\r\n\r\n`),
|
|
35
36
|
Buffer.from(bytes),
|
|
36
37
|
Buffer.from(`\r\n--${boundary}--`),
|
package/main.js
CHANGED
|
@@ -711,6 +711,8 @@ async function main() {
|
|
|
711
711
|
if (!require(path.join(EXT, 'gpuServer.js')).explicit()) {
|
|
712
712
|
try { await require(path.join(EXT, 'gpuServer.js')).adopt((await sync.cloud.getPreferences())?.values?.gpuServer) } catch { /* said below if none */ }
|
|
713
713
|
}
|
|
714
|
+
// This CLI names itself on every call to its GPU server (gpuRun.js).
|
|
715
|
+
require(path.join(EXT, 'gpuRun.js')).setApp({ client: 'cli', server })
|
|
714
716
|
// Every call to the GPU server carries the account's token (the server may require it), and only those.
|
|
715
717
|
const { serverFetch } = require(path.join(EXT, 'account.js'))
|
|
716
718
|
globalThis.fetch = serverFetch({ fetch: globalThis.fetch, account, server })
|
package/package.json
CHANGED