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.
- package/kernel/api/hf/index.js +2 -2
- package/kernel/api/index.js +12 -3
- package/kernel/connect/index.js +2 -2
- package/kernel/connect/providers/huggingface/index.js +14 -5
- package/kernel/index.js +14 -2
- package/kernel/shell.js +3 -2
- package/kernel/shells.js +6 -0
- package/kernel/vault/constants.js +13 -0
- package/kernel/vault/hash_worker.js +9 -2
- package/kernel/vault/index.js +1856 -316
- package/kernel/vault/registry.js +526 -52
- package/kernel/vault/snapshot.js +29 -0
- package/kernel/vault/sweeper.js +480 -210
- package/kernel/vault/walker.js +142 -0
- package/package.json +1 -1
- package/server/index.js +79 -90
- package/server/lib/privacy_filter_cache.js +4 -1
- package/server/public/storage-size.js +12 -0
- package/server/public/style.css +13 -0
- package/server/public/tab-link-popover.js +3 -0
- package/server/public/vault-nav.js +96 -0
- package/server/public/vault.css +1079 -0
- package/server/public/vault.js +1835 -0
- package/server/views/app.ejs +93 -16
- package/server/views/connect/huggingface.ejs +7 -9
- package/server/views/partials/main_sidebar.ejs +6 -1
- package/server/views/partials/vault_workspace.ejs +55 -0
- package/server/views/vault.ejs +5 -1532
- package/server/views/vault_app.ejs +22 -0
- package/test/hf-api.test.js +4 -2
- package/test/huggingface-connect.test.js +7 -11
- package/test/huggingface-token-validity.test.js +135 -0
- package/test/plugin-action-functions.test.js +19 -0
- package/test/privacy-filter-cache.test.js +1 -0
- package/test/vault-engine.test.js +1276 -26
- package/test/vault-sweep.test.js +1043 -35
- 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>
|
package/test/hf-api.test.js
CHANGED
|
@@ -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
|
|
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
|
|
252
|
-
|
|
253
|
-
provider
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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',
|