pinokiod 8.0.38 → 8.0.40

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.
@@ -214,7 +214,7 @@ class HF {
214
214
 
215
215
  if (!force) {
216
216
  const existing = await connect.keys("huggingface", authContext)
217
- if (existing && existing.access_token) {
217
+ if (existing && existing.access_token && await connect.connected("huggingface", { timeout: 5000 }, authContext)) {
218
218
  return {
219
219
  status: "success",
220
220
  already_logged_in: true,
@@ -273,7 +273,7 @@ class HF {
273
273
  const startedAt = Date.now()
274
274
  while (Date.now() - startedAt < timeout) {
275
275
  const keys = await connect.keys("huggingface", authContext)
276
- if (keys && keys.access_token) {
276
+ if (keys && keys.access_token && await connect.connected("huggingface", { timeout: 5000 }, authContext)) {
277
277
  if (useModal) {
278
278
  await this.closeLoginModal(req, ondata, kernel)
279
279
  }
@@ -42,9 +42,9 @@ class Connect {
42
42
  let res = await this.clients[provider].keys(context)
43
43
  return res
44
44
  }
45
- async connected(provider, options) {
45
+ async connected(provider, options, context) {
46
46
  if (this.clients[provider] && this.clients[provider].connected) {
47
- return await this.clients[provider].connected(options)
47
+ return await this.clients[provider].connected(options, context)
48
48
  }
49
49
  return false
50
50
  }
@@ -32,10 +32,11 @@ class Huggingface {
32
32
  try {
33
33
  const stat = await fs.promises.lstat(tokenPath)
34
34
  if (stat.isDirectory()) {
35
- await fs.promises.rm(tokenPath, { recursive: true, force: true })
35
+ await fs.promises.rmdir(tokenPath)
36
36
  }
37
37
  } catch (error) {
38
- if (!error || error.code !== "ENOENT") {
38
+ if (error.code !== "ENOENT") {
39
+ error.tokenPath = tokenPath
39
40
  throw error
40
41
  }
41
42
  }
@@ -45,6 +46,7 @@ class Huggingface {
45
46
  let env
46
47
  if (context.parentPath) {
47
48
  env = await Environment.get2(context.parentPath, this.kernel)
49
+ if (env.HF_TOKEN_PATH === this.kernel.path("cache", "HF_AUTH", "anonymous")) env.HF_TOKEN_PATH = this.defaultTokenPath()
48
50
  } else {
49
51
  let systemEnv = {}
50
52
  try {
@@ -96,6 +98,7 @@ class Huggingface {
96
98
  maxBuffer: 1024 * 1024,
97
99
  }, (error, stdout, stderr) => {
98
100
  if (error) {
101
+ error.tokenPath = env.HF_TOKEN_PATH
99
102
  error.stdout = stdout
100
103
  error.stderr = stderr
101
104
  reject(error)
@@ -244,11 +247,17 @@ class Huggingface {
244
247
  return null
245
248
  }
246
249
  }
247
- async connected(options = {}) {
250
+ async connected(options = {}, context = {}) {
248
251
  try {
249
- await this.runHf(["auth", "whoami", "--format", "quiet"], options)
252
+ const result = await this.runHf(["auth", "whoami", "--format", "quiet"], options, context)
253
+ if (this.kernel.envs && result.env.HF_TOKEN_PATH === this.defaultTokenPath()) {
254
+ this.kernel.envs.HF_TOKEN_PATH = result.env.HF_TOKEN_PATH
255
+ }
250
256
  return true
251
- } catch (_) {
257
+ } catch (error) {
258
+ if (this.kernel.envs && error.tokenPath === this.defaultTokenPath()) {
259
+ this.kernel.envs.HF_TOKEN_PATH = this.kernel.path("cache", "HF_AUTH", "anonymous")
260
+ }
252
261
  return false
253
262
  }
254
263
  }
package/kernel/index.js CHANGED
@@ -37,7 +37,6 @@ const PinokioDomainRouter = require("./router/pinokio_domain_router")
37
37
  const Procs = require('./procs')
38
38
  const Peer = require('./peer')
39
39
  const Git = require('./git')
40
- const Vault = require('./vault')
41
40
  const Connect = require('./connect')
42
41
  const Favicon = require('./favicon')
43
42
  const AppLauncher = require('./app_launcher')
@@ -1217,17 +1216,6 @@ class Kernel {
1217
1216
  await this.git.loadCheckpoints()
1218
1217
  console.timeEnd("git.loadCheckpoints")
1219
1218
 
1220
- // Shared model store: registry verification only at startup — never a
1221
- // discovery walk (spec/requirements/shared-model-store.md, trigger 5).
1222
- this.vault = new Vault(this)
1223
- if (this.homedir) {
1224
- this.vault.init().then((result) => {
1225
- if (result && result.enabled) return this.vault.verify()
1226
- }).catch((err) => {
1227
- console.warn("Vault init error:", err && err.message ? err.message : err)
1228
- })
1229
- }
1230
-
1231
1219
  // let contents = await fs.promises.readdir(this.homedir)
1232
1220
  //await this.bin.init()
1233
1221
  let ts = Date.now()
package/kernel/shell.js CHANGED
@@ -291,7 +291,7 @@ class Shell {
291
291
  }
292
292
 
293
293
  setDefaultEnvValue(this.env, "HF_HUB_DISABLE_UPDATE_CHECK", "1")
294
- setDefaultEnvValue(this.env, "HF_TOKEN_PATH", path.resolve(this.kernel.homedir, "cache", "HF_AUTH", "token"))
294
+ setDefaultEnvValue(this.env, "HF_TOKEN_PATH", this.kernel.envs?.HF_TOKEN_PATH || path.resolve(this.kernel.homedir, "cache", "HF_AUTH", "token"))
295
295
 
296
296
  if (this.platform === "win32") {
297
297
  const managedSslDir = path.resolve(this.kernel.homedir, "bin", "miniforge", "Library", "ssl")
package/kernel/shells.js CHANGED
@@ -47,6 +47,9 @@ class Shells {
47
47
  }
48
48
  */
49
49
  async init() {
50
+ const hfDisconnected = !this.kernel.envs?.HF_TOKEN_PATH &&
51
+ !(await this.kernel.connect.connected("huggingface", { timeout: 5000 }))
52
+
50
53
  // iterate through all the envs
51
54
  let sh = new Shell(this.kernel)
52
55
  let m
@@ -58,6 +61,9 @@ class Shells {
58
61
  await sh.init_env({
59
62
  env: this.kernel.bin.envs({}),
60
63
  })
64
+ if (hfDisconnected && sh.env.HF_TOKEN_PATH === this.kernel.path("cache", "HF_AUTH", "token")) {
65
+ sh.env.HF_TOKEN_PATH = this.kernel.path("cache", "HF_AUTH", "anonymous")
66
+ }
61
67
 
62
68
  await this.ensureBracketedPasteSupport(sh.shell)
63
69
  if (this.kernel.bracketedPasteSupport) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.0.38",
3
+ "version": "8.0.40",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -15732,119 +15732,6 @@ class Server {
15732
15732
  // }))
15733
15733
 
15734
15734
 
15735
- // Vault dashboard data: everything from memory, never a walk. The scan
15736
- // itself runs ONLY via the explicit scan action below.
15737
- this.app.get("/info/dedup", ex(async (req, res) => {
15738
- const vault = this.kernel.vault
15739
- if (!vault || !vault.enabled) {
15740
- res.json({ enabled: false })
15741
- return
15742
- }
15743
- if (req.query && req.query.progress === "1") {
15744
- res.json(vault.progressStatus())
15745
- return
15746
- }
15747
- res.json(await vault.status())
15748
- }))
15749
- this.app.get("/vault", ex(async (req, res) => {
15750
- res.render("vault", { theme: this.theme, agent: req.agent })
15751
- }))
15752
- this.app.post("/vault/action", ex(async (req, res) => {
15753
- const vault = this.kernel.vault
15754
- if (!vault || !vault.enabled) {
15755
- res.json({ error: "vault is not enabled" })
15756
- return
15757
- }
15758
- const body = req.body || {}
15759
- try {
15760
- switch (body.action) {
15761
- case "add_source": {
15762
- const result = await vault.addExternalSource(body.path)
15763
- res.json({
15764
- created: result.created,
15765
- source: {
15766
- id: result.source.id,
15767
- label: result.source.label,
15768
- target_path: result.source.root,
15769
- shareable: !!result.source.shareable
15770
- }
15771
- })
15772
- return
15773
- }
15774
- case "deduplicate": {
15775
- const scopeId = typeof body.scope_id === "string" ? body.scope_id : null
15776
- if (scopeId) {
15777
- const source = vault.sources().find((item) => item.id === scopeId && item.kind !== "virtual")
15778
- if (!source) {
15779
- res.json({ error: "That location is no longer available. Scan again to refresh it." })
15780
- return
15781
- }
15782
- if (!source.shareable) {
15783
- res.json({ error: "This location is on a disk that cannot share space with this vault." })
15784
- return
15785
- }
15786
- if (source.kind === "app") {
15787
- const appRoot = this.kernel.path("api", source.app) + path.sep
15788
- const running = this.kernel.api && this.kernel.api.running ? this.kernel.api.running : {}
15789
- const busy = Object.keys(running).some((k) => running[k] && k.startsWith(appRoot))
15790
- if (busy) {
15791
- res.json({ error: "This app has a running script. Stop it first, then deduplicate." })
15792
- return
15793
- }
15794
- }
15795
- res.json(await vault.sweeper.convertPending(undefined, {
15796
- scope_id: scopeId, batch_id: `batch-${Date.now()}`
15797
- }))
15798
- return
15799
- }
15800
- const app = Object.prototype.hasOwnProperty.call(body, "app") ? (body.app || null) : undefined
15801
- if (app) {
15802
- const appRoot = this.kernel.path("api", app) + path.sep
15803
- const running = this.kernel.api && this.kernel.api.running ? this.kernel.api.running : {}
15804
- const busy = Object.keys(running).some((k) => running[k] && k.startsWith(appRoot))
15805
- if (busy) {
15806
- res.json({ error: "This app has a running script. Stop it first, then deduplicate." })
15807
- return
15808
- }
15809
- }
15810
- res.json(await vault.sweeper.convertPending(app, { batch_id: `batch-${Date.now()}` }))
15811
- return
15812
- }
15813
- case "reclaim":
15814
- res.json(await vault.reclaim(body.hash))
15815
- return
15816
- case "reclaim_all":
15817
- res.json(await vault.reclaimAll())
15818
- return
15819
- case "scan":
15820
- vault.sweeper.scan().catch(() => {})
15821
- res.json({ started: true })
15822
- return
15823
- case "repair":
15824
- case "rebuild":
15825
- if (vault.sweeper && vault.sweeper.state.active) {
15826
- res.json({ error: "Wait for the current scan to finish before repairing the index." })
15827
- return
15828
- }
15829
- await vault.rebuild()
15830
- res.json({ done: true })
15831
- return
15832
- case "undo":
15833
- res.json(await vault.undoBatch(body.batch_id))
15834
- return
15835
- case "detach":
15836
- res.json(await vault.detach(body.path))
15837
- return
15838
- case "reshare":
15839
- res.json(await vault.reshare(body.path))
15840
- return
15841
- default:
15842
- res.json({ error: "unknown action" })
15843
- }
15844
- } catch (e) {
15845
- res.json({ error: e && e.message ? e.message : String(e) })
15846
- }
15847
- }))
15848
15735
  this.app.get("/info/scripts", ex(async (req, res) => {
15849
15736
  /*
15850
15737
  returns something like this by using the this.kernel.memory.local variable, extracting the api name and adding all running scripts in each associated array, setting the uri as the script path, and the local variables as the local attribute
@@ -168,9 +168,9 @@
168
168
  <script>
169
169
  const LOGIN_URL = "/connect/huggingface/login"
170
170
  const LOGOUT_URL = "/connect/huggingface/logout"
171
- const KEYS_URL = "/connect/huggingface/keys"
171
+ const STATUS_URL = "/connect/status/huggingface"
172
172
  const PROFILE_URL = "/connect/huggingface/profile"
173
- const POLL_INTERVAL = 2000
173
+ const POLL_INTERVAL = 5000
174
174
  const POLL_TIMEOUT = 120000
175
175
 
176
176
  const badgeElement = document.getElementById("hf-badge")
@@ -261,9 +261,9 @@
261
261
  return res.json()
262
262
  }
263
263
 
264
- async function readToken() {
265
- const json = await postJSON(KEYS_URL)
266
- return json && json.access_token && !json.error ? json.access_token : null
264
+ async function isConnected() {
265
+ const json = await fetch(STATUS_URL, { cache: "no-store" }).then((res) => res.json())
266
+ return Boolean(json && json.connected)
267
267
  }
268
268
 
269
269
  async function showConnected() {
@@ -326,8 +326,7 @@
326
326
  }
327
327
 
328
328
  async function pollLoginStatus() {
329
- const token = await readToken()
330
- if (token) {
329
+ if (await isConnected()) {
331
330
  await showConnected()
332
331
  }
333
332
  }
@@ -415,8 +414,7 @@
415
414
 
416
415
  window.addEventListener("load", async () => {
417
416
  try {
418
- const token = await readToken()
419
- if (token) {
417
+ if (await isConnected()) {
420
418
  await showConnected()
421
419
  return
422
420
  }
@@ -26,7 +26,6 @@
26
26
  <div class='main-sidebar-section' aria-label="Manage">
27
27
  <div class="main-sidebar-section-title">Manage</div>
28
28
  <a class="tab <%= sidebarSelected === 'checkpoints' ? 'selected' : '' %>" href="/checkpoints"><i class="fa-solid fa-clock-rotate-left"></i><div class='caption'>Checkpoints</div></a>
29
- <a class="tab <%= sidebarSelected === 'vault' ? 'selected' : '' %>" href="/vault"><i class="fa-solid fa-hard-drive"></i><div class='caption'>Vault</div></a>
30
29
  <a class="tab <%= sidebarSelected === 'tools' ? 'selected' : '' %>" href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Tools</div></a>
31
30
  <a class="tab <%= sidebarSelected === 'plugins' ? 'selected' : '' %>" href="/plugins"><i class="fa-solid fa-plug-circle-bolt"></i><div class='caption'>Plugins</div></a>
32
31
  <a class="tab <%= sidebarSelected === 'tasks' ? 'selected' : '' %>" href="/tasks"><i class="fa-solid fa-list-check"></i><div class='caption'>Tasks</div></a>
@@ -41,7 +41,8 @@ test('hf.login shows a modal, waits for the user to open Hugging Face, then clos
41
41
  login,
42
42
  token_path: '/tmp/pinokio/hf-token'
43
43
  }
44
- }
44
+ },
45
+ connected: async () => true
45
46
  },
46
47
  api: {
47
48
  async wait(key) {
@@ -161,7 +162,8 @@ test('hf.login returns success without opening anything when already logged in',
161
162
  access_token: 'hf_secret',
162
163
  token_path: '/tmp/pinokio/hf-token'
163
164
  }
164
- }
165
+ },
166
+ connected: async () => true
165
167
  }
166
168
  }
167
169
 
@@ -240,7 +240,7 @@ test('Hugging Face connect leaves existing token and refresh files unchanged', a
240
240
  }
241
241
  })
242
242
 
243
- test('Hugging Face connect removes a non-empty token directory', async () => {
243
+ test('Hugging Face connect preserves a non-empty token directory', async () => {
244
244
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-hf-connect-invalid-dir-'))
245
245
  const authDir = path.join(root, 'cache', 'HF_AUTH')
246
246
  const tokenPath = path.join(authDir, 'token')
@@ -248,16 +248,12 @@ test('Hugging Face connect removes a non-empty token directory', async () => {
248
248
  await fs.writeFile(path.join(tokenPath, 'leftover.txt'), 'leftover')
249
249
  await fs.writeFile(path.join(authDir, 'stored_tokens'), 'refresh state')
250
250
  try {
251
- const provider = new HuggingfaceConnect(createKernel(root), {})
252
- await Promise.all([provider.authEnv(), provider.authEnv()])
253
- provider.hfPath = () => process.execPath
254
- const { env } = await provider.runHf([
255
- '-e',
256
- 'require("node:fs").writeFileSync(process.env.HF_TOKEN_PATH, "hf_replacement_token")'
257
- ])
258
-
259
- assert.equal(env.HF_TOKEN_PATH, tokenPath)
260
- assert.equal(await fs.readFile(tokenPath, 'utf8'), 'hf_replacement_token')
251
+ const kernel = createKernel(root)
252
+ kernel.envs = { HF_TOKEN_PATH: tokenPath }
253
+ const provider = new HuggingfaceConnect(kernel, {})
254
+ assert.equal(await provider.connected(), false)
255
+ assert.equal(kernel.envs.HF_TOKEN_PATH, path.join(authDir, 'anonymous'))
256
+ assert.equal(await fs.readFile(path.join(tokenPath, 'leftover.txt'), 'utf8'), 'leftover')
261
257
  assert.equal(await fs.readFile(path.join(authDir, 'stored_tokens'), 'utf8'), 'refresh state')
262
258
  } finally {
263
259
  await fs.rm(root, { recursive: true, force: true })
@@ -0,0 +1,135 @@
1
+ const assert = require('node:assert/strict')
2
+ const fs = require('node:fs/promises')
3
+ const os = require('node:os')
4
+ const path = require('node:path')
5
+ const test = require('node:test')
6
+
7
+ const HF = require('../kernel/api/hf')
8
+ const HuggingfaceConnect = require('../kernel/connect/providers/huggingface')
9
+ const Shell = require('../kernel/shell')
10
+ const Shells = require('../kernel/shells')
11
+
12
+ function createKernel(root) {
13
+ return {
14
+ homedir: root,
15
+ platform: process.platform,
16
+ path: (...parts) => path.join(root, ...parts),
17
+ exists: async (target) => fs.access(target).then(() => true, () => false)
18
+ }
19
+ }
20
+
21
+ test('hf.login does not accept an invalid saved token', async () => {
22
+ const context = {
23
+ parentPath: '/pinokio/api/test/login.js',
24
+ cwd: '/pinokio/api/test',
25
+ env: { HF_TOKEN_PATH: './token' }
26
+ }
27
+ const kernel = {
28
+ connect: {
29
+ keys: async () => ({ access_token: 'invalid' }),
30
+ async connected(provider, options, receivedContext) {
31
+ assert.equal(provider, 'huggingface')
32
+ assert.deepEqual(options, { timeout: 5000 })
33
+ assert.deepEqual(receivedContext, context)
34
+ return false
35
+ },
36
+ login: async () => ({ status: 'error', error: 'login started' })
37
+ }
38
+ }
39
+
40
+ const result = await new HF().login({
41
+ parent: { path: context.parentPath },
42
+ cwd: context.cwd,
43
+ params: { env: context.env }
44
+ }, () => {}, kernel)
45
+
46
+ assert.deepEqual(result, { status: 'error', error: 'login started' })
47
+ })
48
+
49
+ test('whoami updates only the managed shared token path', async () => {
50
+ const root = path.join(os.tmpdir(), 'pinokio-hf-state')
51
+ const tokenPath = path.join(root, 'cache', 'HF_AUTH', 'token')
52
+ const anonymousPath = path.join(root, 'cache', 'HF_AUTH', 'anonymous')
53
+ const kernel = createKernel(root)
54
+ kernel.envs = { HF_TOKEN_PATH: anonymousPath }
55
+ const provider = new HuggingfaceConnect(kernel, {})
56
+ const customContext = { env: { HF_TOKEN_PATH: '/tmp/custom-hf-token' } }
57
+
58
+ provider.runHf = async (_args, _options, receivedContext) => {
59
+ assert.equal(receivedContext, customContext)
60
+ return { env: { HF_TOKEN_PATH: customContext.env.HF_TOKEN_PATH } }
61
+ }
62
+ assert.equal(await provider.connected({}, customContext), true)
63
+ assert.equal(kernel.envs.HF_TOKEN_PATH, anonymousPath)
64
+
65
+ provider.runHf = async () => ({ env: { HF_TOKEN_PATH: tokenPath } })
66
+ assert.equal(await provider.connected(), true)
67
+ assert.equal(kernel.envs.HF_TOKEN_PATH, tokenPath)
68
+
69
+ provider.runHf = async () => {
70
+ const error = new Error('invalid token')
71
+ error.tokenPath = tokenPath
72
+ throw error
73
+ }
74
+ assert.equal(await provider.connected(), false)
75
+ assert.equal(kernel.envs.HF_TOKEN_PATH, anonymousPath)
76
+ })
77
+
78
+ test('provider login ignores only Pinokio\'s inherited anonymous path', async () => {
79
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-hf-context-'))
80
+ const appDir = path.join(root, 'api', 'demo')
81
+ const scriptPath = path.join(appDir, 'start.js')
82
+ await fs.mkdir(appDir, { recursive: true })
83
+ await fs.writeFile(path.join(root, 'ENVIRONMENT'), '')
84
+ await fs.writeFile(path.join(appDir, 'ENVIRONMENT'), '')
85
+ await fs.writeFile(scriptPath, '')
86
+ try {
87
+ const kernel = createKernel(root)
88
+ kernel.envs = { HF_TOKEN_PATH: path.join(root, 'cache', 'HF_AUTH', 'anonymous') }
89
+ const provider = new HuggingfaceConnect(kernel, {})
90
+ const context = { parentPath: scriptPath, cwd: appDir }
91
+
92
+ assert.equal((await provider.authEnv(context)).HF_TOKEN_PATH, provider.defaultTokenPath())
93
+ assert.equal(
94
+ (await provider.authEnv({ ...context, env: { HF_TOKEN_PATH: './custom-token' } })).HF_TOKEN_PATH,
95
+ path.join(appDir, 'custom-token')
96
+ )
97
+ } finally {
98
+ await fs.rm(root, { recursive: true, force: true })
99
+ }
100
+ })
101
+
102
+ test('invalid managed tokens are replaced once without overriding apps', async () => {
103
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-hf-shells-'))
104
+ await fs.writeFile(path.join(root, 'ENVIRONMENT'), '')
105
+ let checks = 0
106
+ const kernel = createKernel(root)
107
+ kernel.bin = { envs: () => ({}) }
108
+ kernel.bracketedPasteSupport = {}
109
+ kernel.which = () => null
110
+ kernel.connect = {
111
+ connected: async () => {
112
+ checks++
113
+ return false
114
+ }
115
+ }
116
+ const shells = new Shells(kernel)
117
+ shells.ensureBracketedPasteSupport = async () => {}
118
+ try {
119
+ await shells.init()
120
+ const anonymousPath = path.join(root, 'cache', 'HF_AUTH', 'anonymous')
121
+ assert.equal(kernel.envs.HF_TOKEN_PATH, anonymousPath)
122
+ await shells.init()
123
+ assert.equal(checks, 1)
124
+
125
+ const inheritedShell = new Shell(kernel)
126
+ await inheritedShell.init_env({ env: {} })
127
+ assert.equal(inheritedShell.env.HF_TOKEN_PATH, anonymousPath)
128
+
129
+ const appShell = new Shell(kernel)
130
+ await appShell.init_env({ env: { HF_TOKEN_PATH: '/tmp/app-hf-token' } })
131
+ assert.equal(appShell.env.HF_TOKEN_PATH, '/tmp/app-hf-token')
132
+ } finally {
133
+ await fs.rm(root, { recursive: true, force: true })
134
+ }
135
+ })
@@ -1,19 +0,0 @@
1
- const { parentPort } = require('worker_threads')
2
- const crypto = require('crypto')
3
- const fs = require('fs')
4
-
5
- parentPort.on('message', ({ id, filePath }) => {
6
- const hash = crypto.createHash('sha256')
7
- let size = 0
8
- const stream = fs.createReadStream(filePath)
9
- stream.on('data', (chunk) => {
10
- size += chunk.length
11
- hash.update(chunk)
12
- })
13
- stream.on('error', (error) => {
14
- parentPort.postMessage({ id, error: error.message })
15
- })
16
- stream.on('end', () => {
17
- parentPort.postMessage({ id, hash: hash.digest('hex'), size })
18
- })
19
- })