pinokiod 7.2.17 → 7.2.18
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/agent_instructions.js +166 -0
- package/kernel/api/index.js +137 -12
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/environment.js +23 -9
- package/kernel/plugin_sources.js +57 -4
- package/kernel/prototype.js +4 -0
- package/kernel/shell.js +2 -0
- package/kernel/watch/index.js +31 -4
- package/package.json +1 -1
- package/server/features/index.js +4 -4
- package/server/features/{drafts → notes}/index.js +9 -9
- package/server/features/{drafts → notes}/parser.js +12 -7
- package/server/features/notes/public/notes.css +955 -0
- package/server/features/notes/public/notes.js +1149 -0
- package/server/features/{drafts → notes}/registry_import.js +22 -22
- package/server/features/notes/routes.js +156 -0
- package/server/features/notes/service.js +326 -0
- package/server/features/{drafts → notes}/watcher.js +14 -16
- package/server/index.js +61 -30
- package/server/lib/content_validation.js +19 -8
- package/server/lib/workspace_catalog.js +18 -18
- package/server/public/task-launcher.css +11 -3
- package/server/public/tasker.css +336 -0
- package/server/public/tasker.js +407 -0
- package/server/views/d.ejs +33 -2
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/partials/workspace_row.ejs +11 -11
- package/server/views/pre.ejs +1 -1
- package/server/views/task_launch.ejs +10 -10
- package/server/views/tasker.ejs +40 -0
- package/server/views/terminal.ejs +15 -6
- package/server/views/terminals.ejs +0 -1
- package/server/views/workspaces.ejs +2 -1
- package/system/plugin/antigravity/pinokio.js +2 -4
- package/system/plugin/claude/pinokio.js +2 -4
- package/system/plugin/claude-auto/pinokio.js +2 -4
- package/system/plugin/claude-desktop/pinokio.js +2 -4
- package/system/plugin/codex/pinokio.js +2 -4
- package/system/plugin/codex-auto/pinokio.js +2 -4
- package/system/plugin/codex-desktop/pinokio.js +2 -4
- package/system/plugin/crush/pinokio.js +2 -4
- package/system/plugin/cursor/pinokio.js +2 -4
- package/system/plugin/gemini/pinokio.js +2 -4
- package/system/plugin/gemini-auto/pinokio.js +2 -4
- package/system/plugin/qwen/pinokio.js +2 -4
- package/system/plugin/vscode/pinokio.js +2 -4
- package/system/plugin/windsurf/pinokio.js +2 -4
- package/test/plugin-sources.test.js +45 -0
- package/server/features/drafts/public/drafts.js +0 -1504
- package/server/features/drafts/routes.js +0 -68
- package/server/features/drafts/service.js +0 -261
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
const PluginSources = require("./plugin_sources")
|
|
4
|
+
|
|
5
|
+
const NOTES_BEGIN = "<!-- PINOKIO:NOTES:BEGIN -->"
|
|
6
|
+
const NOTES_END = "<!-- PINOKIO:NOTES:END -->"
|
|
7
|
+
const LEGACY_DRAFTS_BEGIN = "<!-- PINOKIO:DRAFTS:BEGIN -->"
|
|
8
|
+
const LEGACY_DRAFTS_END = "<!-- PINOKIO:DRAFTS:END -->"
|
|
9
|
+
|
|
10
|
+
const AGENT_INSTRUCTION_FILES = [
|
|
11
|
+
"AGENTS.md",
|
|
12
|
+
"CLAUDE.md",
|
|
13
|
+
"GEMINI.md",
|
|
14
|
+
"QWEN.md",
|
|
15
|
+
".windsurfrules",
|
|
16
|
+
".cursorrules",
|
|
17
|
+
".clinerules",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
21
|
+
|
|
22
|
+
const normalizeBlock = (block) => `${String(block || "").trim()}\n`
|
|
23
|
+
|
|
24
|
+
const extractManagedBlock = (content, begin = NOTES_BEGIN, end = NOTES_END) => {
|
|
25
|
+
const text = String(content || "")
|
|
26
|
+
const start = text.indexOf(begin)
|
|
27
|
+
const finish = text.indexOf(end, start + begin.length)
|
|
28
|
+
if (start < 0 || finish < 0) {
|
|
29
|
+
return ""
|
|
30
|
+
}
|
|
31
|
+
return text.slice(start, finish + end.length)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const insertionIndex = (content) => {
|
|
35
|
+
const text = String(content || "")
|
|
36
|
+
let offset = 0
|
|
37
|
+
|
|
38
|
+
if (text.startsWith("---\n")) {
|
|
39
|
+
const frontmatterEnd = text.indexOf("\n---\n", 4)
|
|
40
|
+
if (frontmatterEnd >= 0) {
|
|
41
|
+
offset = frontmatterEnd + "\n---\n".length
|
|
42
|
+
while (text[offset] === "\n") {
|
|
43
|
+
offset += 1
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rest = text.slice(offset)
|
|
49
|
+
const h1 = rest.match(/^# .*(?:\n|$)/)
|
|
50
|
+
if (h1) {
|
|
51
|
+
return offset + h1[0].length
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return offset
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const insertManagedBlock = (content, block) => {
|
|
58
|
+
const text = String(content || "").replace(/\r\n/g, "\n")
|
|
59
|
+
const noteBlock = normalizeBlock(block)
|
|
60
|
+
if (!text.trim()) {
|
|
61
|
+
return noteBlock
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const index = insertionIndex(text)
|
|
65
|
+
const before = text.slice(0, index)
|
|
66
|
+
const after = text.slice(index).replace(/^\n+/, "")
|
|
67
|
+
const beforeGap = before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n"
|
|
68
|
+
const afterGap = after ? "\n" : ""
|
|
69
|
+
|
|
70
|
+
return `${before}${beforeGap}${noteBlock}${afterGap}${after}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const upsertManagedBlock = (content, block, begin = NOTES_BEGIN, end = NOTES_END) => {
|
|
74
|
+
let text = String(content || "").replace(/\r\n/g, "\n")
|
|
75
|
+
const noteBlock = normalizeBlock(block)
|
|
76
|
+
const pattern = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}\\n?`)
|
|
77
|
+
const legacyPattern = new RegExp(`${escapeRegExp(LEGACY_DRAFTS_BEGIN)}[\\s\\S]*?${escapeRegExp(LEGACY_DRAFTS_END)}\\n?`)
|
|
78
|
+
|
|
79
|
+
if (pattern.test(text)) {
|
|
80
|
+
return text.replace(pattern, `${noteBlock}\n`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
text = text.replace(legacyPattern, "")
|
|
84
|
+
return insertManagedBlock(text, noteBlock)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const containedBy = (child, parent) => {
|
|
88
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child))
|
|
89
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const isPluginScriptPath = (kernel, scriptPath) => {
|
|
93
|
+
if (!kernel || !scriptPath) {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const absolutePath = path.resolve(scriptPath)
|
|
98
|
+
if (containedBy(absolutePath, kernel.path("plugin", "code"))) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const roots = [
|
|
103
|
+
PluginSources.systemPluginRoot(kernel),
|
|
104
|
+
kernel.path("plugin"),
|
|
105
|
+
].filter(Boolean)
|
|
106
|
+
|
|
107
|
+
return roots.some((root) => containedBy(absolutePath, root))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const ensureManagedBlockInFile = async (filePath, block) => {
|
|
111
|
+
let content = ""
|
|
112
|
+
try {
|
|
113
|
+
content = await fs.promises.readFile(filePath, "utf8")
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (!e || e.code !== "ENOENT") {
|
|
116
|
+
throw e
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const nextContent = upsertManagedBlock(content, block)
|
|
121
|
+
if (nextContent !== content) {
|
|
122
|
+
await fs.promises.writeFile(filePath, nextContent, "utf8")
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const ensureNoteInstructionsForCwd = async ({ kernel, cwd }) => {
|
|
130
|
+
if (!kernel || !cwd) {
|
|
131
|
+
return { updated: [], skipped: "missing-cwd" }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const targetDir = path.resolve(cwd)
|
|
135
|
+
const stat = await fs.promises.stat(targetDir).catch(() => null)
|
|
136
|
+
if (!stat || !stat.isDirectory()) {
|
|
137
|
+
return { updated: [], skipped: "invalid-cwd" }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const sourcePath = kernel.path("prototype", "system", "AGENTS.md")
|
|
141
|
+
const source = await fs.promises.readFile(sourcePath, "utf8").catch(() => "")
|
|
142
|
+
const block = extractManagedBlock(source)
|
|
143
|
+
if (!block) {
|
|
144
|
+
return { updated: [], skipped: "missing-block" }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const updated = []
|
|
148
|
+
for (const filename of AGENT_INSTRUCTION_FILES) {
|
|
149
|
+
const filePath = path.join(targetDir, filename)
|
|
150
|
+
if (await ensureManagedBlockInFile(filePath, block)) {
|
|
151
|
+
updated.push(filePath)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { updated, skipped: null }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
NOTES_BEGIN,
|
|
160
|
+
NOTES_END,
|
|
161
|
+
AGENT_INSTRUCTION_FILES,
|
|
162
|
+
extractManagedBlock,
|
|
163
|
+
upsertManagedBlock,
|
|
164
|
+
isPluginScriptPath,
|
|
165
|
+
ensureNoteInstructionsForCwd,
|
|
166
|
+
}
|
package/kernel/api/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const Loader = require("../loader")
|
|
|
12
12
|
const Environment = require("../environment")
|
|
13
13
|
const Util = require('../util')
|
|
14
14
|
const ShellRunTemplate = require('./shell_run_template')
|
|
15
|
+
const AgentInstructions = require("../agent_instructions")
|
|
15
16
|
|
|
16
17
|
class Api {
|
|
17
18
|
constructor(kernel) {
|
|
@@ -27,6 +28,7 @@ class Api {
|
|
|
27
28
|
this.proxies = {}
|
|
28
29
|
this.mods = {}
|
|
29
30
|
this.child_procs = {}
|
|
31
|
+
this.resolved_actions = {}
|
|
30
32
|
this.lproxy = new Lproxy()
|
|
31
33
|
}
|
|
32
34
|
startData(rpc) {
|
|
@@ -233,7 +235,7 @@ class Api {
|
|
|
233
235
|
meta.ui = `/p/${api_name}`
|
|
234
236
|
meta.browse = `/p/${api_name}/dev`
|
|
235
237
|
} else {
|
|
236
|
-
meta.icon = meta.icon ? `/asset/api/${api_name}/${meta.icon}` : "/pinokio-black.png"
|
|
238
|
+
meta.icon = meta.icon ? `/asset/api/${api_name}/${relpath}/${meta.icon}` : "/pinokio-black.png"
|
|
237
239
|
meta.link = `/p/${api_name}/${relpath}/dev#n1`
|
|
238
240
|
meta.web_path = `/api/${api_name}/${relpath}`
|
|
239
241
|
meta.ui = `/p/${api_name}/${relpath}`
|
|
@@ -412,6 +414,84 @@ class Api {
|
|
|
412
414
|
let result = await endpoint(rpc, ondata, this.kernel)
|
|
413
415
|
return result
|
|
414
416
|
}
|
|
417
|
+
requestId(request) {
|
|
418
|
+
return request ? (request.id || request.path) : null
|
|
419
|
+
}
|
|
420
|
+
clearResolvedAction(requestOrId) {
|
|
421
|
+
const id = typeof requestOrId === "string" ? requestOrId : this.requestId(requestOrId)
|
|
422
|
+
if (id) {
|
|
423
|
+
delete this.resolved_actions[id]
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
setRunning(request, done) {
|
|
427
|
+
const id = this.requestId(request)
|
|
428
|
+
if (!id) {
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
this.running[id] = true
|
|
432
|
+
this.done[id] = done
|
|
433
|
+
}
|
|
434
|
+
isActionCandidate(action) {
|
|
435
|
+
return (Array.isArray(action) && action.length > 0) || typeof action === "function"
|
|
436
|
+
}
|
|
437
|
+
async actionContext({ request, script, scriptDir, actionKey, input, args }) {
|
|
438
|
+
const cwd = request.cwd || scriptDir
|
|
439
|
+
const id = this.requestId(request)
|
|
440
|
+
const env = await Environment.get2(request.path, this.kernel)
|
|
441
|
+
this.kernel.template.update({ envs: env, env })
|
|
442
|
+
const port = await this.kernel.port()
|
|
443
|
+
const name = path.relative(this.kernel.path("api"), scriptDir)
|
|
444
|
+
return {
|
|
445
|
+
kernel: this.kernel,
|
|
446
|
+
info: this.kernel.info,
|
|
447
|
+
script: this.kernel.script,
|
|
448
|
+
action: actionKey,
|
|
449
|
+
input,
|
|
450
|
+
args,
|
|
451
|
+
global: (this.kernel.memory.global[id] || {}),
|
|
452
|
+
local: (this.kernel.memory.local[id] || {}),
|
|
453
|
+
key: this.kernel.memory.key,
|
|
454
|
+
uri: request.uri,
|
|
455
|
+
cwd,
|
|
456
|
+
dirname: scriptDir,
|
|
457
|
+
exists: (...args) => {
|
|
458
|
+
return fs.existsSync(path.resolve(cwd, ...args))
|
|
459
|
+
},
|
|
460
|
+
running: (...args) => {
|
|
461
|
+
let fullpath = path.resolve(cwd, ...args)
|
|
462
|
+
return this.running[fullpath]
|
|
463
|
+
},
|
|
464
|
+
name,
|
|
465
|
+
self: script,
|
|
466
|
+
port,
|
|
467
|
+
env,
|
|
468
|
+
envs: env,
|
|
469
|
+
...this.kernel.vars,
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async resolveActionSteps({ request, script, scriptDir, actionKey, input, args }) {
|
|
473
|
+
const id = this.requestId(request)
|
|
474
|
+
const cached = id ? this.resolved_actions[id] : null
|
|
475
|
+
if (cached && cached.actionKey === actionKey && Array.isArray(cached.steps)) {
|
|
476
|
+
return cached.steps
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const action = script ? script[actionKey] : null
|
|
480
|
+
let steps
|
|
481
|
+
if (Array.isArray(action)) {
|
|
482
|
+
steps = action
|
|
483
|
+
} else if (typeof action === "function") {
|
|
484
|
+
const context = await this.actionContext({ request, script, scriptDir, actionKey, input, args })
|
|
485
|
+
steps = await action.call(script, this.kernel, this.kernel.info, context)
|
|
486
|
+
} else {
|
|
487
|
+
steps = null
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (id && Array.isArray(steps)) {
|
|
491
|
+
this.resolved_actions[id] = { actionKey, steps }
|
|
492
|
+
}
|
|
493
|
+
return steps
|
|
494
|
+
}
|
|
415
495
|
async stop(req, ondata) {
|
|
416
496
|
// 1. set the "stop" flag for the uri, so the next execution in the queue for the uri will NOT queue another task
|
|
417
497
|
// 2. stream a message closing the socket
|
|
@@ -420,10 +500,12 @@ class Api {
|
|
|
420
500
|
// /Users/x/pinokio/prototype/system/aicode/template/claude.json?cwd=/Users/x/pinokio/api/MMAudio.git
|
|
421
501
|
// take the first part only since the rest is the cwd
|
|
422
502
|
req.params.uri = req.params.id.split("?")[0]
|
|
503
|
+
this.clearResolvedAction(req.params.id)
|
|
423
504
|
}
|
|
424
505
|
|
|
425
506
|
// 1. if the scropt has 'on.stop', run it when stopping
|
|
426
507
|
let requestPath = this.filePath(req.params.uri)
|
|
508
|
+
this.clearResolvedAction(requestPath)
|
|
427
509
|
let { cwd, script } = await this.resolveScript(requestPath)
|
|
428
510
|
if (script.on) {
|
|
429
511
|
if (script.on.stop) {
|
|
@@ -910,7 +992,7 @@ class Api {
|
|
|
910
992
|
|
|
911
993
|
let { cwd: scriptDir, script } = await this.resolveScript(request.path)
|
|
912
994
|
const actionKey = request.action || 'run'
|
|
913
|
-
const steps = (
|
|
995
|
+
const steps = await this.resolveActionSteps({ request, script, scriptDir, actionKey, input, args }) || []
|
|
914
996
|
const totalSteps = steps.length
|
|
915
997
|
|
|
916
998
|
let name = path.relative(this.kernel.path("api"), scriptDir)
|
|
@@ -1494,6 +1576,22 @@ class Api {
|
|
|
1494
1576
|
args: input
|
|
1495
1577
|
})
|
|
1496
1578
|
}
|
|
1579
|
+
async ensurePluginAgentInstructions(request) {
|
|
1580
|
+
if (!request || !request.cwd || !request.path) {
|
|
1581
|
+
return
|
|
1582
|
+
}
|
|
1583
|
+
if (!AgentInstructions.isPluginScriptPath(this.kernel, request.path)) {
|
|
1584
|
+
return
|
|
1585
|
+
}
|
|
1586
|
+
try {
|
|
1587
|
+
await AgentInstructions.ensureNoteInstructionsForCwd({
|
|
1588
|
+
kernel: this.kernel,
|
|
1589
|
+
cwd: request.cwd,
|
|
1590
|
+
})
|
|
1591
|
+
} catch (e) {
|
|
1592
|
+
console.warn("[agent-instructions] failed to update note instructions:", e && e.message ? e.message : e)
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1497
1595
|
createQueue(queue_id, concurrency) {
|
|
1498
1596
|
this.queues[queue_id] = fastq.promise(async ({ request, rawrpc, input, step, total, cwd, args }) => {
|
|
1499
1597
|
try {
|
|
@@ -1711,18 +1809,12 @@ class Api {
|
|
|
1711
1809
|
} else {
|
|
1712
1810
|
const actionKey = request.action || 'run'
|
|
1713
1811
|
request.action = actionKey
|
|
1714
|
-
const
|
|
1812
|
+
const action = script ? script[actionKey] : null
|
|
1715
1813
|
|
|
1716
|
-
// 3. Check if the resolved endpoint has the requested action attribute and it
|
|
1717
|
-
if (
|
|
1814
|
+
// 3. Check if the resolved endpoint has the requested action attribute and resolve it to steps.
|
|
1815
|
+
if (this.isActionCandidate(action)) {
|
|
1718
1816
|
|
|
1719
|
-
|
|
1720
|
-
this.running[request.id] = true
|
|
1721
|
-
this.done[request.id] = done
|
|
1722
|
-
} else if (request.path) {
|
|
1723
|
-
this.running[request.path] = true
|
|
1724
|
-
this.done[request.path] = done
|
|
1725
|
-
}
|
|
1817
|
+
this.setRunning(request, done)
|
|
1726
1818
|
|
|
1727
1819
|
// set DNS
|
|
1728
1820
|
|
|
@@ -1735,6 +1827,39 @@ class Api {
|
|
|
1735
1827
|
}
|
|
1736
1828
|
|
|
1737
1829
|
const initialPayload = typeof request.input === "undefined" ? {} : request.input
|
|
1830
|
+
let steps
|
|
1831
|
+
try {
|
|
1832
|
+
steps = await this.resolveActionSteps({
|
|
1833
|
+
request,
|
|
1834
|
+
script,
|
|
1835
|
+
scriptDir: cwd,
|
|
1836
|
+
actionKey,
|
|
1837
|
+
input: initialPayload,
|
|
1838
|
+
args: initialPayload
|
|
1839
|
+
})
|
|
1840
|
+
} catch (e) {
|
|
1841
|
+
this.clearResolvedAction(request)
|
|
1842
|
+
delete this.running[this.requestId(request)]
|
|
1843
|
+
this.ondata({
|
|
1844
|
+
id: request.id || request.path,
|
|
1845
|
+
type: "error",
|
|
1846
|
+
data: e.stack,
|
|
1847
|
+
})
|
|
1848
|
+
return
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
1852
|
+
this.clearResolvedAction(request)
|
|
1853
|
+
delete this.running[this.requestId(request)]
|
|
1854
|
+
this.ondata({
|
|
1855
|
+
id: request.id || request.path,
|
|
1856
|
+
type: "error",
|
|
1857
|
+
data: `missing or invalid attribute: ${actionKey}`
|
|
1858
|
+
})
|
|
1859
|
+
return
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
await this.ensurePluginAgentInstructions(request)
|
|
1738
1863
|
await this.startWatchersForRequest(request, script, cwd, initialPayload)
|
|
1739
1864
|
this.queue(request, steps[0], initialPayload, 0, steps.length, cwd, initialPayload)
|
|
1740
1865
|
|
|
@@ -18,7 +18,7 @@ class Huggingface {
|
|
|
18
18
|
let version = this.kernel.bin.installed.conda_versions.huggingface_hub
|
|
19
19
|
if (version) {
|
|
20
20
|
let coerced = semver.coerce(version)
|
|
21
|
-
if (semver.
|
|
21
|
+
if (coerced && semver.eq(coerced, "1.0.1")) {
|
|
22
22
|
return true
|
|
23
23
|
}
|
|
24
24
|
}
|
package/kernel/environment.js
CHANGED
|
@@ -154,6 +154,8 @@ const ensurePinokioCacheDirs = async (kernel, options = {}) => {
|
|
|
154
154
|
logCachePreflight(`cache root mkdir failed path=${cacheRoot} ${formatCachePreflightError(error)}`)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
if (process.platform === "darwin") await fs.promises.writeFile(path.resolve(root, ".metadata_never_index"), "", { flag: "a" }).catch(() => {})
|
|
158
|
+
|
|
157
159
|
const errors = []
|
|
158
160
|
const results = []
|
|
159
161
|
|
|
@@ -618,18 +620,21 @@ const requirements = async (script, cwd, kernel) => {
|
|
|
618
620
|
let requires_instantiation = false
|
|
619
621
|
if (script) {
|
|
620
622
|
let pre
|
|
621
|
-
if (script.pre) {
|
|
623
|
+
if (Array.isArray(script.pre)) {
|
|
622
624
|
pre = script.pre
|
|
623
|
-
} else if (script.env) {
|
|
625
|
+
} else if (Array.isArray(script.env)) {
|
|
624
626
|
pre = script.env
|
|
625
627
|
}
|
|
626
628
|
if (pre) {
|
|
627
629
|
let env = await get2(cwd, kernel)
|
|
628
630
|
for(let item of pre) {
|
|
631
|
+
if (!item || typeof item !== "object") {
|
|
632
|
+
continue
|
|
633
|
+
}
|
|
629
634
|
let env_key
|
|
630
|
-
if (item.env) {
|
|
635
|
+
if (typeof item.env === "string" && item.env) {
|
|
631
636
|
env_key = item.env
|
|
632
|
-
} else if (item.key) {
|
|
637
|
+
} else if (typeof item.key === "string" && item.key) {
|
|
633
638
|
env_key = item.key
|
|
634
639
|
item.env = item.key
|
|
635
640
|
}
|
|
@@ -638,11 +643,20 @@ const requirements = async (script, cwd, kernel) => {
|
|
|
638
643
|
if (!item.index) {
|
|
639
644
|
item.index = 0
|
|
640
645
|
}
|
|
641
|
-
if (item.host) {
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
+
if (typeof item.host === "string" && item.host.trim()) {
|
|
647
|
+
const host = item.host.trim()
|
|
648
|
+
let parsedHost = ""
|
|
649
|
+
try {
|
|
650
|
+
const hasProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(host);
|
|
651
|
+
const url = new URL(hasProtocol ? host : `https://${host}`);
|
|
652
|
+
parsedHost = url.host
|
|
653
|
+
} catch (e) {
|
|
654
|
+
item.host = ""
|
|
655
|
+
}
|
|
656
|
+
if (parsedHost) {
|
|
657
|
+
item.host = parsedHost
|
|
658
|
+
item.val = await kernel.kv.get(item.host, item.index)
|
|
659
|
+
}
|
|
646
660
|
} else {
|
|
647
661
|
item.host = ""
|
|
648
662
|
}
|
package/kernel/plugin_sources.js
CHANGED
|
@@ -10,6 +10,7 @@ const LOCAL_PLUGIN_RUN_PREFIX = `${LOCAL_RUN_PREFIX}/plugin`
|
|
|
10
10
|
const LOCAL_PLUGIN_ASSET_PREFIX = `${LOCAL_ASSET_PREFIX}/plugin`
|
|
11
11
|
const SYSTEM_PLUGIN_RUN_PREFIX = `${SYSTEM_RUN_PREFIX}/plugin`
|
|
12
12
|
const SYSTEM_PLUGIN_ASSET_PREFIX = `${SYSTEM_ASSET_PREFIX}/plugin`
|
|
13
|
+
const ACTION_KEYS = new Set(["run", "install", "uninstall", "update"])
|
|
13
14
|
|
|
14
15
|
const toPathname = (value) => {
|
|
15
16
|
const raw = typeof value === "string" ? value.trim() : ""
|
|
@@ -124,6 +125,29 @@ const pluginAssetHrefForIcon = (normalizedPath, icon) => {
|
|
|
124
125
|
return ""
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
const isAction = (value) => Array.isArray(value) || typeof value === "function"
|
|
129
|
+
const hasActionFunction = (config, key) => ACTION_KEYS.has(key) && typeof config[key] === "function"
|
|
130
|
+
const isPlainObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)
|
|
131
|
+
const declaredPluginPath = (config) => typeof config.path === "string" ? config.path.trim() : ""
|
|
132
|
+
const hasInvalidTopLevelFunction = (config) => {
|
|
133
|
+
return Object.keys(config).some((key) => typeof config[key] === "function" && !hasActionFunction(config, key))
|
|
134
|
+
}
|
|
135
|
+
const hasInvalidAction = (config) => {
|
|
136
|
+
return Array.from(ACTION_KEYS).some((key) => key in config && !isAction(config[key]))
|
|
137
|
+
}
|
|
138
|
+
const isValidPluginConfig = (config, options = {}) => {
|
|
139
|
+
if (!isPlainObject(config) || !isAction(config.run)) {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
if (hasInvalidTopLevelFunction(config) || hasInvalidAction(config)) {
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
if (options.standalone && declaredPluginPath(config) !== "plugin") {
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
|
|
127
151
|
const normalizeLauncherTool = (toolValue) => {
|
|
128
152
|
let normalizedTool = typeof toolValue === "string" ? toolValue.trim() : ""
|
|
129
153
|
normalizedTool = normalizedTool.replace(/^https?:\/\/[^/]+/i, "")
|
|
@@ -161,7 +185,32 @@ const resolveLauncherPluginHref = (toolValue) => {
|
|
|
161
185
|
return `${LOCAL_PLUGIN_RUN_PREFIX}/${normalizedTool}/pinokio.js`
|
|
162
186
|
}
|
|
163
187
|
|
|
164
|
-
const
|
|
188
|
+
const resolveLauncherPluginSelection = (toolValue) => {
|
|
189
|
+
const href = resolveLauncherPluginHref(toolValue)
|
|
190
|
+
if (href.startsWith(`${LOCAL_RUN_PREFIX}/`)) {
|
|
191
|
+
return href.slice(LOCAL_RUN_PREFIX.length)
|
|
192
|
+
}
|
|
193
|
+
return href
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const normalizeLauncherSuccessPlugin = (successUrl, toolValue) => {
|
|
197
|
+
if (typeof successUrl !== "string" || typeof toolValue !== "string" || !toolValue.trim()) {
|
|
198
|
+
return successUrl
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const parsed = new URL(successUrl, "http://localhost")
|
|
203
|
+
if (!parsed.searchParams.has("plugin")) {
|
|
204
|
+
return successUrl
|
|
205
|
+
}
|
|
206
|
+
parsed.searchParams.set("plugin", resolveLauncherPluginSelection(toolValue))
|
|
207
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`
|
|
208
|
+
} catch (_) {
|
|
209
|
+
return successUrl
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const loadPluginsFromRoot = async ({ kernel, root, runPrefix, assetPrefix, source, ignore = [], standalone = false }) => {
|
|
165
214
|
const exists = await fs.promises.stat(root).then((stat) => stat.isDirectory()).catch(() => false)
|
|
166
215
|
if (!exists) return []
|
|
167
216
|
|
|
@@ -170,9 +219,7 @@ const loadPluginsFromRoot = async ({ kernel, root, runPrefix, assetPrefix, sourc
|
|
|
170
219
|
for (const pluginPath of pluginPaths) {
|
|
171
220
|
const normalizedPluginPath = normalizeSlashes(pluginPath)
|
|
172
221
|
const config = await kernel.require(path.resolve(root, pluginPath))
|
|
173
|
-
if (!config
|
|
174
|
-
const invalid = Object.keys(config).some((key) => typeof config[key] === "function")
|
|
175
|
-
if (invalid) continue
|
|
222
|
+
if (!isValidPluginConfig(config, { standalone })) continue
|
|
176
223
|
|
|
177
224
|
const cwd = normalizedPluginPath.split("/").slice(0, -1).join("/")
|
|
178
225
|
const href = `${runPrefix}/${normalizedPluginPath}`
|
|
@@ -204,6 +251,7 @@ const loadPluginMenu = async (kernel) => {
|
|
|
204
251
|
assetPrefix: LOCAL_PLUGIN_ASSET_PREFIX,
|
|
205
252
|
source: "local",
|
|
206
253
|
ignore: ["code/**"],
|
|
254
|
+
standalone: true,
|
|
207
255
|
})
|
|
208
256
|
return systemPlugins.concat(localPlugins)
|
|
209
257
|
}
|
|
@@ -232,5 +280,10 @@ module.exports = {
|
|
|
232
280
|
pluginRunHrefForPath,
|
|
233
281
|
pluginAssetHrefForIcon,
|
|
234
282
|
resolveLauncherPluginHref,
|
|
283
|
+
resolveLauncherPluginSelection,
|
|
284
|
+
normalizeLauncherSuccessPlugin,
|
|
235
285
|
loadPluginMenu,
|
|
286
|
+
ACTION_KEYS,
|
|
287
|
+
isAction,
|
|
288
|
+
isValidPluginConfig,
|
|
236
289
|
}
|
package/kernel/prototype.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path')
|
|
|
3
3
|
const { glob, sync, hasMagic } = require('glob-gitignore')
|
|
4
4
|
const marked = require('marked')
|
|
5
5
|
const matter = require('gray-matter');
|
|
6
|
+
const PluginSources = require("./plugin_sources")
|
|
6
7
|
class Proto {
|
|
7
8
|
constructor(kernel) {
|
|
8
9
|
this.kernel = kernel
|
|
@@ -148,6 +149,9 @@ class Proto {
|
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
if (response) {
|
|
152
|
+
if (response.success) {
|
|
153
|
+
response.success = PluginSources.normalizeLauncherSuccessPlugin(response.success, req.params.tool)
|
|
154
|
+
}
|
|
151
155
|
return response
|
|
152
156
|
} else {
|
|
153
157
|
return { success: "/p/" + name + "/dev" }
|
package/kernel/shell.js
CHANGED
|
@@ -294,6 +294,8 @@ class Shell {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
setDefaultEnvValue(this.env, "HF_HUB_DISABLE_UPDATE_CHECK", "1")
|
|
298
|
+
|
|
297
299
|
if (this.platform === "win32") {
|
|
298
300
|
// Hugging Face file symlinks regularly fail on non-admin Windows setups.
|
|
299
301
|
// Default to no-symlink cache mode unless the user/app explicitly overrides it.
|
package/kernel/watch/index.js
CHANGED
|
@@ -18,7 +18,34 @@ class WatchManager {
|
|
|
18
18
|
|
|
19
19
|
hasHandler(script, handlerName) {
|
|
20
20
|
const watches = script && Array.isArray(script.watch) ? script.watch : []
|
|
21
|
-
return watches.some((watch) =>
|
|
21
|
+
return watches.some((watch) => {
|
|
22
|
+
const resolved = this.resolveHandlerMethod(watch)
|
|
23
|
+
return resolved.handlerName === handlerName
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
resolveHandlerMethod(declaration) {
|
|
28
|
+
if (!declaration || typeof declaration !== "object") {
|
|
29
|
+
return { handlerName: "", methodName: "" }
|
|
30
|
+
}
|
|
31
|
+
const method = typeof declaration.method === "string" ? declaration.method.trim() : ""
|
|
32
|
+
if (declaration.handler) {
|
|
33
|
+
return {
|
|
34
|
+
handlerName: String(declaration.handler).trim(),
|
|
35
|
+
methodName: method
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!declaration.uri && method.includes(".")) {
|
|
39
|
+
const parts = method.split(".")
|
|
40
|
+
return {
|
|
41
|
+
handlerName: parts.slice(0, -1).join(".").trim(),
|
|
42
|
+
methodName: parts[parts.length - 1].trim()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
handlerName: "",
|
|
47
|
+
methodName: method
|
|
48
|
+
}
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
renderDeclaration(raw, memory) {
|
|
@@ -105,10 +132,10 @@ class WatchManager {
|
|
|
105
132
|
input,
|
|
106
133
|
args
|
|
107
134
|
})
|
|
108
|
-
const
|
|
135
|
+
const { handlerName, methodName } = this.resolveHandlerMethod(declaration)
|
|
109
136
|
let handler = null
|
|
110
|
-
if (
|
|
111
|
-
handler = this.handlers.get(
|
|
137
|
+
if (handlerName) {
|
|
138
|
+
handler = this.handlers.get(handlerName)
|
|
112
139
|
} else if (declaration.uri) {
|
|
113
140
|
handler = await this.resolveExternalHandler(ctx, declaration.uri)
|
|
114
141
|
}
|
package/package.json
CHANGED
package/server/features/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { createNoteFeature } = require("./notes")
|
|
2
2
|
|
|
3
3
|
async function mountFeatures(options = {}) {
|
|
4
|
-
const
|
|
5
|
-
await
|
|
4
|
+
const notes = createNoteFeature(options)
|
|
5
|
+
await notes.start()
|
|
6
6
|
return {
|
|
7
|
-
|
|
7
|
+
notes
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|