@davait/aipictury 1.10.3 → 1.10.4
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/accountUi.js +23 -6
- package/ext/agent.js +25 -10
- package/ext/cloud.js +46 -2
- package/ext/package.json +2 -2
- package/main.js +1 -1
- package/package.json +1 -1
package/ext/accountUi.js
CHANGED
|
@@ -212,10 +212,23 @@ function register(context, { registry, sessionsView, serverUrl }) {
|
|
|
212
212
|
async function deleteCloudData() {
|
|
213
213
|
const yes = 'Delete everything'
|
|
214
214
|
const ok = await vscode.window.showWarningMessage('Delete everything of yours kept with your AIPictury account?', {
|
|
215
|
-
modal: true, detail: 'Conversations
|
|
215
|
+
modal: true, detail: 'Conversations and projects, rules, preferences, usage, your pictures and clips (Renders, and every file kept '
|
|
216
|
+
+ 'with them), remote-control links and the list of your machines — from every machine and the web app. What is on this '
|
|
217
|
+
+ 'machine stays, and is not sent back up by itself: only what you do from now on is kept again (while its switch is on).' }, yes)
|
|
216
218
|
if (ok !== yes) return null
|
|
217
219
|
const n = await cloud.deleteEverything()
|
|
218
|
-
return `Deleted (${n} records). Your account stays — sign out or keep using it.`
|
|
220
|
+
return `Deleted (${n} records and files). Your account stays — sign out or keep using it.`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const KIND_LABEL = { conversations: 'conversations', memory: 'rules', settings: 'preferences', usage: 'usage' }
|
|
224
|
+
/** A switch turned off: also delete what is already kept of that kind? Asked; null to keep it. */
|
|
225
|
+
async function deleteKind(kind) {
|
|
226
|
+
const yes = 'Delete them too'
|
|
227
|
+
const ok = await vscode.window.showWarningMessage(`No longer keeping your ${KIND_LABEL[kind] || kind} with your account. Delete the ones already kept?`, {
|
|
228
|
+
modal: true, detail: 'From every machine and the web app. What is on this machine stays.' }, yes, 'Keep them')
|
|
229
|
+
if (ok !== yes) return null
|
|
230
|
+
const n = await cloud.deleteKind(kind)
|
|
231
|
+
return `No longer sent, and the ${KIND_LABEL[kind] || kind} kept before are deleted (${n} records).`
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
function localPreferences() {
|
|
@@ -235,7 +248,7 @@ function register(context, { registry, sessionsView, serverUrl }) {
|
|
|
235
248
|
context.subscriptions.push({ dispose: () => { globalThis.fetch = plainFetch } })
|
|
236
249
|
|
|
237
250
|
account.user().then(changed).catch(() => changed(null))
|
|
238
|
-
return { account, cloud, signedIn, signIn, signOut, deleteCloudData, keeps, user: () => user }
|
|
251
|
+
return { account, cloud, signedIn, signIn, signOut, deleteCloudData, deleteKind, keeps, user: () => user }
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
async function projectRulesText() {
|
|
@@ -265,7 +278,7 @@ function accountSpec(ctl, note) {
|
|
|
265
278
|
{ title: 'Kept with your account', items: kinds.map(([id, label, detail]) => ({ id: `keep:${id}`, label, detail, toggle: { on: config().get(`cloud.${id}`, true) } })) },
|
|
266
279
|
{ title: 'Account', items: [
|
|
267
280
|
{ id: 'signout', label: 'Sign out', detail: 'Forgets the session on this machine: nothing of the account stays here.', actions: [{ id: 'signout', label: 'Sign out', always: true }] },
|
|
268
|
-
{ id: 'delete', label: 'Delete what is kept in the cloud', detail: 'Everything above, from every machine. Local files stay.', actions: [{ id: 'delete', label: 'Delete…', danger: true, always: true }] },
|
|
281
|
+
{ id: 'delete', label: 'Delete what is kept in the cloud', detail: 'Everything above, and your pictures and clips (Renders), from every machine and the web app. Local files stay.', actions: [{ id: 'delete', label: 'Delete…', danger: true, always: true }] },
|
|
269
282
|
] },
|
|
270
283
|
] }
|
|
271
284
|
}
|
|
@@ -274,8 +287,12 @@ function accountHandler(ctl) {
|
|
|
274
287
|
return async (action, message) => {
|
|
275
288
|
const item = String(message.item || '')
|
|
276
289
|
if (action === 'toggle' && item.startsWith('keep:')) {
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
const kind = item.slice(5)
|
|
291
|
+
await config().update(`cloud.${kind}`, !!message.value, vscode.ConfigurationTarget.Global)
|
|
292
|
+
if (message.value) return accountSpec(ctl, 'Kept with your account from now on.')
|
|
293
|
+
// Off: what is already kept of it can go too — asked, since it goes from every machine.
|
|
294
|
+
const said = await ctl.deleteKind?.(kind).catch((error) => `No longer sent; deleting what was kept failed: ${error.message}`)
|
|
295
|
+
return accountSpec(ctl, said || 'No longer sent. What is in the cloud stays until you delete it.')
|
|
279
296
|
}
|
|
280
297
|
if (action === 'signin') { await ctl.signIn(); return accountSpec(ctl) }
|
|
281
298
|
if (action === 'signout') { await ctl.signOut(); return accountSpec(ctl, 'Signed out.') }
|
package/ext/agent.js
CHANGED
|
@@ -73,9 +73,9 @@ const MODES = {
|
|
|
73
73
|
plan: { label: 'Plan', icon: '\u25F4',
|
|
74
74
|
hint: 'Read-only: investigates and proposes a plan, changes nothing.' },
|
|
75
75
|
never: { label: 'Auto', icon: '\u26A1',
|
|
76
|
-
hint: '
|
|
76
|
+
hint: 'Asks nothing: does the work, takes the recommended choices, and lists at the end what would have been asked and why. Your own ask rules still ask.' },
|
|
77
77
|
bypass: { label: 'Bypass permissions', icon: '\u26A0',
|
|
78
|
-
hint: 'Does everything without asking, with no checks at all. Only with aipictury.allowDangerouslySkipPermissions.' },
|
|
78
|
+
hint: 'Does everything without asking, with no checks at all — not even your ask rules. Only with aipictury.allowDangerouslySkipPermissions.' },
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** The mode in force. Bypass needs its own setting: without it, it is Auto. */
|
|
@@ -97,11 +97,11 @@ approves it (and you may then make the changes) or asks for changes to it.`
|
|
|
97
97
|
|
|
98
98
|
const AUTO_NOTE = `
|
|
99
99
|
|
|
100
|
-
You are in AUTO mode: the user is not approving each step
|
|
101
|
-
you choose between real options — it keeps a record of the
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
would be costly to undo.`
|
|
100
|
+
You are in AUTO mode: the user is not approving each step, and nothing stops to ask them.
|
|
101
|
+
Still call ask_user whenever you choose between real options — it keeps a record of the
|
|
102
|
+
choice: your recommended (first) option is taken at once, you carry on without waiting, and
|
|
103
|
+
the user can change it when you finish. Commands and tools run without asking too, so take
|
|
104
|
+
the careful way yourself when an action would be costly to undo.`
|
|
105
105
|
|
|
106
106
|
const SYSTEM = `You are AIPictury's coding agent, working inside the user's editor on
|
|
107
107
|
their own machine. You can read and change the workspace with the tools provided.
|
|
@@ -319,11 +319,14 @@ async function approve(call, session, signal, hooks = {}, preview = null, { forc
|
|
|
319
319
|
if (mode === 'bypass') return { ok: true }
|
|
320
320
|
if (mode === 'never') {
|
|
321
321
|
// Auto: edits inside the workspace go ahead (a checkpoint can undo them); anything else
|
|
322
|
-
// is judged — fixed rules, routine work, then the model — and
|
|
322
|
+
// is judged — fixed rules, routine work, then the model — and what would have been asked
|
|
323
|
+
// goes ahead all the same, listed at the end of the answer with why.
|
|
323
324
|
if (WRITES.has(name)) return { ok: true }
|
|
324
325
|
const verdict = await judge(call, session, signal, hooks, server)
|
|
325
326
|
if (verdict.verdict === 'allow') return { ok: true, judged: verdict }
|
|
326
|
-
|
|
327
|
+
// Auto never stops to ask: judged risky, it goes ahead too — and is said at the end.
|
|
328
|
+
hooks.onAutoRan?.({ tool: name, args, reason: verdict.reason || 'it could not tell whether this is safe' })
|
|
329
|
+
return { ok: true, judged: verdict, auto: true }
|
|
327
330
|
}
|
|
328
331
|
if (mode === 'edits' && !isRun) return { ok: true }
|
|
329
332
|
if (session.allowAll) return { ok: true }
|
|
@@ -538,7 +541,9 @@ async function askUser(args, report, signal, hooks) {
|
|
|
538
541
|
}
|
|
539
542
|
const mode = modeNow()
|
|
540
543
|
const recommended = (q) => q.options[0].label.replace(/\s*\(recommended\)\s*$/i, '')
|
|
541
|
-
|
|
544
|
+
// Auto takes the recommended option of every question, important ones too (listed at the end,
|
|
545
|
+
// each open to change); the user is never stopped mid-run.
|
|
546
|
+
if (unattended(mode) || !hooks.askUser) {
|
|
542
547
|
const picks = questions.map((q) => ({ ...q, picked: [q.options[0].label], other: '' }))
|
|
543
548
|
hooks.onDecision?.(picks)
|
|
544
549
|
report({ tool: 'ask_user', args: { questions }, status: 'decided', result: picks })
|
|
@@ -935,6 +940,13 @@ async function executeTool(call, session, report, signal, server, hooks = {}) {
|
|
|
935
940
|
sandbox: box.asked ? 'the agent asks to run this outside the sandbox' : 'the sandbox is on, but this machine has none (sandbox-exec / bubblewrap)' })
|
|
936
941
|
}
|
|
937
942
|
if (hooks.hookPermission === 'ask') Object.assign(rule, { decision: 'ask', rule: null, hook: hooks.hookReason || 'a hook' })
|
|
943
|
+
// Auto never stops to ask. Only what the user set up to be asked — an ask rule of theirs, a
|
|
944
|
+
// hook — still asks; the rest (leaving the sandbox, a password field, a new site) goes ahead,
|
|
945
|
+
// and the answer ends with what was done without asking, and why it would have been asked.
|
|
946
|
+
if (modeNow() === 'never' && rule.decision === 'ask' && !rule.rule && !rule.hook) {
|
|
947
|
+
hooks.onAutoRan?.({ tool: name, args, reason: rule.why || rule.sandbox || 'it would have asked' })
|
|
948
|
+
rule.decision = 'allow'
|
|
949
|
+
}
|
|
938
950
|
const standing = session.allowTools?.includes(name)
|
|
939
951
|
const needsYes = rule.decision === 'ask'
|
|
940
952
|
|| (rule.decision !== 'allow' && !standing && (WRITES.has(name) || (isRun && (!readOnlyRun || readsOutside)) || (isMcp && !mcp.readOnly(name)) || browserAsks))
|
|
@@ -1715,6 +1727,9 @@ async function work({ server, model, history, session, signal,
|
|
|
1715
1727
|
let known = null // the dependencies the workspace declares, read when first needed
|
|
1716
1728
|
const holds = new Map() // package → times a call bringing it in was held
|
|
1717
1729
|
const hooks = { askUser: ask, onDecision, approve: approveHook, approvePlan, onTodos, onTasks, onFileChange, readSpecial, model, trustHooks, onMonitor, schedule, onHelperDone,
|
|
1730
|
+
// Auto mode did what would have been asked: listed with the decisions, at the end.
|
|
1731
|
+
onAutoRan: ({ tool, args = {}, reason }) => onDecision?.([{ ran: true, tool, header: tool === 'run_command' ? 'Ran' : tool,
|
|
1732
|
+
question: String(args.command || args.path || args.url || args.page || args.action || JSON.stringify(args)).slice(0, 300), reason: String(reason || '') }]),
|
|
1718
1733
|
// For helpers: what the user said, so a package is judged chosen by them, not by a task text.
|
|
1719
1734
|
userSaid: () => deps.saidByUser(messages, fromUser) }
|
|
1720
1735
|
let steps = 0
|
package/ext/cloud.js
CHANGED
|
@@ -122,7 +122,7 @@ class Cloud {
|
|
|
122
122
|
const now = Date.now()
|
|
123
123
|
const existing = await this.db.get(base)
|
|
124
124
|
await this.db.set(base, { email: user.email || '', displayName: user.displayName || '', photoURL: user.photoURL || '',
|
|
125
|
-
created: existing?.created || now, lastSeen: now })
|
|
125
|
+
created: existing?.created || now, lastSeen: now, ...(existing?.cleared ? { cleared: existing.cleared } : {}) })
|
|
126
126
|
await this.db.set(`${base}/devices/${this.device.id}`, { name: this.device.name, app: this.device.app,
|
|
127
127
|
platform: this.device.platform, version: this.device.version, lastSeen: now })
|
|
128
128
|
}
|
|
@@ -229,11 +229,21 @@ class Cloud {
|
|
|
229
229
|
|
|
230
230
|
/** The conversations of `folder` kept on this machine (`store`: sessions.js) that the account
|
|
231
231
|
* lacks or has an older copy of, carried up; how many. `still()`: false stops (signed out). */
|
|
232
|
+
/** When everything kept with the account was last deleted (ms; 0: never), read afresh. What is
|
|
233
|
+
* older than that is not carried up again by itself: deleting the cloud copy, the conversations
|
|
234
|
+
* still on a machine came straight back at its next start. What is done after it syncs again. */
|
|
235
|
+
async clearedAt() {
|
|
236
|
+
const user = await this.db.get(await this.base()).catch(() => null)
|
|
237
|
+
return Number(user?.cleared) || 0
|
|
238
|
+
}
|
|
239
|
+
|
|
232
240
|
async bringUp(folder, store, { still = () => true } = {}) {
|
|
233
241
|
const remote = new Map((await this.listConversations(folder)).map((c) => [c.id, c]))
|
|
242
|
+
const cleared = await this.clearedAt()
|
|
234
243
|
let up = 0
|
|
235
244
|
for (const meta of store.list().slice(0, 500)) {
|
|
236
245
|
if (!still()) break
|
|
246
|
+
if ((meta.updated || 0) <= cleared) continue // from before the cloud copy was deleted
|
|
237
247
|
const there = remote.get(meta.id)
|
|
238
248
|
if (there && (there.updated || 0) >= (meta.updated || 0)) continue
|
|
239
249
|
const entry = store.get(meta.id)
|
|
@@ -362,12 +372,44 @@ class Cloud {
|
|
|
362
372
|
|
|
363
373
|
async dropRemote(id) { await this.db.delete(`${await this.base()}/remote/${id}`) }
|
|
364
374
|
|
|
375
|
+
/** One kind of what is kept, deleted from every machine's cloud copy (its switch turned off):
|
|
376
|
+
* 'conversations' (with their chunks), 'memory' (your rules and each project's), 'settings'
|
|
377
|
+
* (preferences) or 'usage'. Returns how many records went. */
|
|
378
|
+
async deleteKind(kind) {
|
|
379
|
+
const base = await this.base()
|
|
380
|
+
const writes = []
|
|
381
|
+
if (kind === 'conversations' || kind === 'memory') {
|
|
382
|
+
for (const p of await this.db.list(`${base}/projects`)) {
|
|
383
|
+
if (kind === 'memory') { writes.push({ delete: `${base}/projects/${p.id}/memory/current` }); continue }
|
|
384
|
+
for (const c of await this.db.list(`${base}/projects/${p.id}/conversations`)) {
|
|
385
|
+
const chunks = new Set([...Array(c.data.chunks || 0).keys()].map(String))
|
|
386
|
+
for (const k of await this.db.list(`${base}/projects/${p.id}/conversations/${c.id}/chunks`).catch(() => [])) chunks.add(k.id)
|
|
387
|
+
for (const k of chunks) writes.push({ delete: `${base}/projects/${p.id}/conversations/${c.id}/chunks/${k}` })
|
|
388
|
+
writes.push({ delete: `${base}/projects/${p.id}/conversations/${c.id}` })
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (kind === 'memory') writes.push({ delete: `${base}/memory/rules` })
|
|
392
|
+
} else if (kind === 'settings') {
|
|
393
|
+
writes.push({ delete: `${base}/settings/preferences` })
|
|
394
|
+
} else if (kind === 'usage') {
|
|
395
|
+
for (const d of await this.db.list(`${base}/usage`)) writes.push({ delete: `${base}/usage/${d.id}` })
|
|
396
|
+
} else {
|
|
397
|
+
throw new Error(`nothing kept as ${kind}`)
|
|
398
|
+
}
|
|
399
|
+
await this.db.batch(writes)
|
|
400
|
+
if (kind === 'conversations') this.pushed.clear()
|
|
401
|
+
return writes.length
|
|
402
|
+
}
|
|
403
|
+
|
|
365
404
|
async deleteEverything() {
|
|
366
405
|
const base = await this.base()
|
|
367
406
|
const writes = []
|
|
368
407
|
for (const p of await this.db.list(`${base}/projects`)) {
|
|
369
408
|
for (const c of await this.db.list(`${base}/projects/${p.id}/conversations`)) {
|
|
370
|
-
|
|
409
|
+
// Every chunk there is, not as many as the summary says: one left by a save cut short stays otherwise.
|
|
410
|
+
const chunks = new Set([...Array(c.data.chunks || 0).keys()].map(String))
|
|
411
|
+
for (const k of await this.db.list(`${base}/projects/${p.id}/conversations/${c.id}/chunks`).catch(() => [])) chunks.add(k.id)
|
|
412
|
+
for (const k of chunks) writes.push({ delete: `${base}/projects/${p.id}/conversations/${c.id}/chunks/${k}` })
|
|
371
413
|
writes.push({ delete: `${base}/projects/${p.id}/conversations/${c.id}` })
|
|
372
414
|
}
|
|
373
415
|
writes.push({ delete: `${base}/projects/${p.id}/memory/current` }, { delete: `${base}/projects/${p.id}` })
|
|
@@ -377,6 +419,8 @@ class Cloud {
|
|
|
377
419
|
await this.db.batch(writes)
|
|
378
420
|
// The media kept with the account (pictures, clips, attachments) go too.
|
|
379
421
|
if (this.storage) for (const o of await this.storage.list(`${base}/media/`)) { await this.storage.delete(o.name); writes.push(o) }
|
|
422
|
+
// The moment it was cleared: no app carries back up what it still holds from before.
|
|
423
|
+
await this.db.set(base, { cleared: Date.now() })
|
|
380
424
|
this.pushed.clear()
|
|
381
425
|
return writes.length
|
|
382
426
|
}
|
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.10.
|
|
5
|
+
"version": "1.10.4",
|
|
6
6
|
"publisher": "aipictury",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
8
8
|
"engines": {
|
|
@@ -848,7 +848,7 @@
|
|
|
848
848
|
"Manual — ask before every file change and command",
|
|
849
849
|
"Edit Automatically — apply file edits without asking; still ask before running commands",
|
|
850
850
|
"Plan — read-only: investigate and propose a plan, change nothing",
|
|
851
|
-
"Auto —
|
|
851
|
+
"Auto — asks nothing: does the work, takes the recommended choices, lists at the end what would have been asked and why (your own ask rules still ask)",
|
|
852
852
|
"Bypass permissions — everything without asking, no checks (needs allowDangerouslySkipPermissions)"
|
|
853
853
|
],
|
|
854
854
|
"default": "never",
|
package/main.js
CHANGED
|
@@ -387,7 +387,7 @@ async function interactive(o, convo, first, cfg) {
|
|
|
387
387
|
const view = new T.MarkdownStream()
|
|
388
388
|
view.push(`${plan}\n`)
|
|
389
389
|
view.end()
|
|
390
|
-
const answer = await ask(`\n ${bold('y')}es, go ahead (asking as now) · ${bold('a')}uto (go ahead, asking
|
|
390
|
+
const answer = await ask(`\n ${bold('y')}es, go ahead (asking as now) · ${bold('a')}uto (go ahead, asking nothing) · or say what to change › `)
|
|
391
391
|
spinner.start()
|
|
392
392
|
if (answer === null) return { approved: false, feedback: '' }
|
|
393
393
|
const a = answer.trim()
|
package/package.json
CHANGED