pinokiod 8.0.39 → 8.0.41

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.
Files changed (37) hide show
  1. package/kernel/api/hf/index.js +2 -2
  2. package/kernel/api/index.js +12 -3
  3. package/kernel/connect/index.js +2 -2
  4. package/kernel/connect/providers/huggingface/index.js +14 -5
  5. package/kernel/index.js +14 -2
  6. package/kernel/shell.js +3 -2
  7. package/kernel/shells.js +6 -0
  8. package/kernel/vault/constants.js +13 -0
  9. package/kernel/vault/hash_worker.js +9 -2
  10. package/kernel/vault/index.js +1856 -316
  11. package/kernel/vault/registry.js +526 -52
  12. package/kernel/vault/snapshot.js +29 -0
  13. package/kernel/vault/sweeper.js +480 -210
  14. package/kernel/vault/walker.js +142 -0
  15. package/package.json +1 -1
  16. package/server/index.js +79 -90
  17. package/server/lib/privacy_filter_cache.js +4 -1
  18. package/server/public/storage-size.js +12 -0
  19. package/server/public/style.css +13 -0
  20. package/server/public/tab-link-popover.js +3 -0
  21. package/server/public/vault-nav.js +96 -0
  22. package/server/public/vault.css +1079 -0
  23. package/server/public/vault.js +1835 -0
  24. package/server/views/app.ejs +93 -16
  25. package/server/views/connect/huggingface.ejs +7 -9
  26. package/server/views/partials/main_sidebar.ejs +6 -1
  27. package/server/views/partials/vault_workspace.ejs +55 -0
  28. package/server/views/vault.ejs +5 -1532
  29. package/server/views/vault_app.ejs +22 -0
  30. package/test/hf-api.test.js +4 -2
  31. package/test/huggingface-connect.test.js +7 -11
  32. package/test/huggingface-token-validity.test.js +135 -0
  33. package/test/plugin-action-functions.test.js +19 -0
  34. package/test/privacy-filter-cache.test.js +1 -0
  35. package/test/vault-engine.test.js +1276 -26
  36. package/test/vault-sweep.test.js +1043 -35
  37. package/test/vault-ui.test.js +2184 -65
@@ -0,0 +1,22 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="UTF-8">
4
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
5
+ <link href="/css/fontawesome.min.css" rel="stylesheet">
6
+ <link href="/css/solid.min.css" rel="stylesheet">
7
+ <link href="/css/regular.min.css" rel="stylesheet">
8
+ <link href="/style.css" rel="stylesheet"/>
9
+ <link href="/task-launcher.css" rel="stylesheet"/>
10
+ <% if (agent === "electron") { %>
11
+ <link href="/electron.css" rel="stylesheet"/>
12
+ <% } %>
13
+ <link href="/vault.css" rel="stylesheet"/>
14
+ </head>
15
+ <body class='<%=theme%> task-launcher-page task-page vault-page vault-embed-page' data-agent="<%=agent%>" data-vault-mode="app" data-vault-scope="<%=scope_id%>">
16
+ <main class='vault-embed-main'>
17
+ <%- include('partials/vault_workspace', { appMode: true }) %>
18
+ </main>
19
+ <script src="/storage-size.js"></script>
20
+ <script src="/vault.js"></script>
21
+ </body>
22
+ </html>
@@ -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
+ })
@@ -77,6 +77,19 @@ test("resolveActionSteps preserves array actions without changing launcher synta
77
77
  })
78
78
  })
79
79
 
80
+ test("running request ids retain their authoritative script paths", () => {
81
+ const api = new Api(createKernel())
82
+ const request = { id: "custom-run-id", path: "/pinokio/api/demo/start.js" }
83
+
84
+ api.setRunning(request, () => {})
85
+ assert.equal(api.running[request.id], true)
86
+ assert.equal(api.running_paths[request.id], request.path)
87
+
88
+ api.clearRunning(request)
89
+ assert.equal(api.running[request.id], undefined)
90
+ assert.equal(api.running_paths[request.id], undefined)
91
+ })
92
+
80
93
  test("resolveActionSteps executes function actions once per request and clears cached steps", async () => {
81
94
  const api = new Api(createKernel())
82
95
  const request = { id: "function-request", path: "/pinokio/api/demo/plugin/pinokio.js" }
@@ -311,6 +324,7 @@ test("step clears resolved function actions when an RPC returns an error", async
311
324
  }
312
325
  const steps = [{ method: "test.fail" }]
313
326
  api.running[request.id] = true
327
+ api.running_paths[request.id] = request.path
314
328
  api.resolved_actions[request.id] = { actionKey: "run", steps }
315
329
  api.resolveScript = async () => ({
316
330
  cwd: appDir,
@@ -328,6 +342,8 @@ test("step clears resolved function actions when an RPC returns an error", async
328
342
  await api.step(request, steps[0], {}, 0, 1, {})
329
343
 
330
344
  assert.equal(api.resolved_actions[request.id], undefined)
345
+ assert.equal(api.running[request.id], true)
346
+ assert.equal(api.running_paths[request.id], request.path)
331
347
  })
332
348
  })
333
349
 
@@ -339,6 +355,7 @@ test("step clears resolved function actions when an RPC throws", async () => {
339
355
  }
340
356
  const steps = [{ method: "test.throw" }]
341
357
  api.running[request.id] = true
358
+ api.running_paths[request.id] = request.path
342
359
  api.resolved_actions[request.id] = { actionKey: "run", steps }
343
360
  api.resolveScript = async () => ({
344
361
  cwd: appDir,
@@ -361,5 +378,7 @@ test("step clears resolved function actions when an RPC throws", async () => {
361
378
  }
362
379
 
363
380
  assert.equal(api.resolved_actions[request.id], undefined)
381
+ assert.equal(api.running[request.id], true)
382
+ assert.equal(api.running_paths[request.id], request.path)
364
383
  })
365
384
  })
@@ -110,6 +110,7 @@ test('privacy filter cache install endpoint accepts only same-origin browser wri
110
110
  const { mod, restore } = loadCacheWithAxiosMock({ get: async () => ({ data: Readable.from(['']) }) })
111
111
  try {
112
112
  assert.equal(mod.isInstallRequestAllowed(fakeRequest({})), true)
113
+ assert.strictEqual(mod.isSameOriginRequest, mod.isInstallRequestAllowed)
113
114
  assert.equal(mod.isInstallRequestAllowed(fakeRequest({
114
115
  Host: 'localhost:42000',
115
116
  Origin: 'http://localhost:42000',