pinokiod 5.1.5 → 5.1.11

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 (54) hide show
  1. package/kernel/api/fs/download_worker.js +158 -0
  2. package/kernel/api/fs/index.js +95 -91
  3. package/kernel/api/index.js +3 -0
  4. package/kernel/bin/index.js +5 -2
  5. package/kernel/environment.js +19 -2
  6. package/kernel/git.js +972 -1
  7. package/kernel/index.js +65 -30
  8. package/kernel/peer.js +1 -2
  9. package/kernel/plugin.js +0 -8
  10. package/kernel/procs.js +92 -36
  11. package/kernel/prototype.js +45 -22
  12. package/kernel/shells.js +30 -6
  13. package/kernel/sysinfo.js +33 -13
  14. package/kernel/util.js +61 -24
  15. package/kernel/workspace_status.js +131 -7
  16. package/package.json +1 -1
  17. package/pipe/index.js +1 -1
  18. package/server/index.js +1173 -348
  19. package/server/public/create-launcher.js +157 -2
  20. package/server/public/install.js +135 -41
  21. package/server/public/style.css +32 -1
  22. package/server/public/tab-link-popover.js +45 -14
  23. package/server/public/terminal-settings.js +51 -35
  24. package/server/public/urldropdown.css +89 -3
  25. package/server/socket.js +12 -7
  26. package/server/views/agents.ejs +4 -3
  27. package/server/views/app.ejs +798 -30
  28. package/server/views/bootstrap.ejs +2 -1
  29. package/server/views/checkpoints.ejs +1014 -0
  30. package/server/views/checkpoints_registry_beta.ejs +260 -0
  31. package/server/views/columns.ejs +4 -4
  32. package/server/views/connect.ejs +1 -0
  33. package/server/views/d.ejs +74 -4
  34. package/server/views/download.ejs +28 -28
  35. package/server/views/editor.ejs +4 -5
  36. package/server/views/env_editor.ejs +1 -1
  37. package/server/views/file_explorer.ejs +1 -1
  38. package/server/views/index.ejs +3 -1
  39. package/server/views/init/index.ejs +2 -1
  40. package/server/views/install.ejs +2 -1
  41. package/server/views/net.ejs +9 -7
  42. package/server/views/network.ejs +15 -14
  43. package/server/views/pro.ejs +5 -2
  44. package/server/views/prototype/index.ejs +2 -1
  45. package/server/views/registry_link.ejs +76 -0
  46. package/server/views/rows.ejs +4 -4
  47. package/server/views/screenshots.ejs +1 -0
  48. package/server/views/settings.ejs +1 -0
  49. package/server/views/shell.ejs +4 -6
  50. package/server/views/terminal.ejs +528 -38
  51. package/server/views/tools.ejs +1 -0
  52. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764297248545 +0 -45
  53. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/events +0 -4
  54. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/latest +0 -45
@@ -0,0 +1,158 @@
1
+ const fs = require('fs')
2
+ const { DownloaderHelper } = require('node-downloader-helper')
3
+ const randomUseragent = require('random-useragent')
4
+
5
+ const safeSend = (msg) => {
6
+ try {
7
+ if (process.send) {
8
+ process.send(msg)
9
+ }
10
+ } catch (_) {
11
+ // ignore IPC errors
12
+ }
13
+ }
14
+
15
+ process.on('message', async (config) => {
16
+ const { url, folder, fileName, stallMs } = config || {}
17
+
18
+ if (!url || !folder) {
19
+ safeSend({
20
+ type: 'error',
21
+ error: { message: 'download_worker: invalid config (url/folder required)', stack: '' }
22
+ })
23
+ process.exit(1)
24
+ return
25
+ }
26
+
27
+ try {
28
+ await fs.promises.mkdir(folder, { recursive: true }).catch(() => {})
29
+ } catch (_) {
30
+ // if mkdir fails, DownloaderHelper will surface an error later
31
+ }
32
+
33
+ const userAgent = randomUseragent.getRandom((ua) => ua.browserName === 'Chrome') ||
34
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36'
35
+
36
+ const options = {
37
+ headers: {
38
+ 'user-agent': userAgent
39
+ },
40
+ override: {
41
+ skip: true,
42
+ skipSmaller: false
43
+ },
44
+ resumeIfFileExists: true,
45
+ removeOnStop: false,
46
+ removeOnFail: false,
47
+ retry: { maxRetries: 10, delay: 5000 }
48
+ }
49
+
50
+ if (fileName) {
51
+ options.fileName = fileName
52
+ }
53
+
54
+ const dl = new DownloaderHelper(url, folder, options)
55
+
56
+ const STALL_MS = typeof stallMs === 'number' ? stallMs : 60000
57
+ let lastProgressAt = Date.now()
58
+ let finished = false
59
+ let stallTimer = setInterval(() => {
60
+ if (finished) return
61
+ const elapsed = Date.now() - lastProgressAt
62
+ if (elapsed > STALL_MS) {
63
+ safeSend({
64
+ type: 'stall',
65
+ elapsed
66
+ })
67
+ try {
68
+ dl.stop()
69
+ } catch (_) {
70
+ }
71
+ }
72
+ }, STALL_MS)
73
+
74
+ const cleanup = () => {
75
+ finished = true
76
+ if (stallTimer) {
77
+ clearInterval(stallTimer)
78
+ stallTimer = null
79
+ }
80
+ }
81
+
82
+ dl.on('download', (downloadInfo) => {
83
+ safeSend({ type: 'download', info: downloadInfo })
84
+ })
85
+
86
+ // High-frequency progress updates stay in the worker to drive the stall watchdog,
87
+ // but we only forward throttled updates to the parent to avoid flooding IPC.
88
+ dl.on('progress', (stats) => {
89
+ lastProgressAt = Date.now()
90
+ })
91
+
92
+ dl.on('progress.throttled', (stats) => {
93
+ lastProgressAt = Date.now()
94
+ safeSend({ type: 'progress.throttled', stats })
95
+ })
96
+
97
+ dl.on('stateChanged', (state) => {
98
+ safeSend({ type: 'stateChanged', state })
99
+ })
100
+
101
+ dl.on('redirected', (newUrl, oldUrl) => {
102
+ safeSend({ type: 'redirected', newUrl, oldUrl })
103
+ })
104
+
105
+ dl.on('resume', (isResumed) => {
106
+ safeSend({ type: 'resume', isResumed })
107
+ })
108
+
109
+ dl.on('timeout', () => {
110
+ safeSend({ type: 'timeout' })
111
+ })
112
+
113
+ dl.on('warning', (err) => {
114
+ safeSend({
115
+ type: 'warning',
116
+ error: {
117
+ message: err && err.message ? err.message : String(err),
118
+ stack: err && err.stack ? err.stack : ''
119
+ }
120
+ })
121
+ })
122
+
123
+ dl.on('skip', (skipInfo) => {
124
+ cleanup()
125
+ safeSend({ type: 'skip', info: skipInfo })
126
+ process.exit(0)
127
+ })
128
+
129
+ dl.on('end', (downloadInfo) => {
130
+ cleanup()
131
+ safeSend({ type: 'end', info: downloadInfo })
132
+ process.exit(0)
133
+ })
134
+
135
+ dl.on('error', (err) => {
136
+ cleanup()
137
+ safeSend({
138
+ type: 'error',
139
+ error: {
140
+ message: err && err.message ? err.message : String(err),
141
+ stack: err && err.stack ? err.stack : String(err)
142
+ }
143
+ })
144
+ process.exit(1)
145
+ })
146
+
147
+ dl.start().catch((err) => {
148
+ cleanup()
149
+ safeSend({
150
+ type: 'error',
151
+ error: {
152
+ message: err && err.message ? err.message : String(err),
153
+ stack: err && err.stack ? err.stack : String(err)
154
+ }
155
+ })
156
+ process.exit(1)
157
+ })
158
+ })
@@ -4,11 +4,10 @@ const fs = require("fs")
4
4
  const fse = require('fs-extra')
5
5
  const { rimraf } = require('rimraf')
6
6
  const Pdrive = require('pdrive')
7
- const { DownloaderHelper } = require('node-downloader-helper');
8
- const randomUseragent = require('random-useragent');
9
7
  const symlinkDir = require('symlink-dir')
10
8
  const retry = require('async-retry');
11
9
  const { createHash } = require('crypto');
10
+ const { fork } = require('child_process')
12
11
  const Environment = require("../../environment")
13
12
  const Util = require("../../util")
14
13
 
@@ -559,108 +558,113 @@ class FS {
559
558
  path: <the eact file path to store the file under>
560
559
  }
561
560
  */
562
- let params = req.params
563
- let url = params.url || params.uri
564
- let dl
565
- let folder
566
- let userAgent = randomUseragent.getRandom((ua) => {
567
- return ua.browserName === 'Chrome';
568
- });
561
+ const params = req.params || {}
562
+ const url = params.url || params.uri
563
+ if (!url) {
564
+ throw new Error('fs.download: url or uri is required')
565
+ }
569
566
 
567
+ let folder
568
+ let filename
570
569
  if (params.dir) {
571
570
  folder = kernel.api.filePath(params.dir, req.cwd)
572
- await fs.promises.mkdir(folder, { recursive: true }).catch((e) => { })
573
- dl = new DownloaderHelper(url, folder, {
574
- headers: {
575
- "user-agent": userAgent,
576
- },
577
- // httpsRequestOptions: {
578
- // rejectUnauthorized: false
579
- // },
580
- override: {
581
- skip: true,
582
- skipSmaller: false,
583
- },
584
- resumeIfFileExists: true,
585
- removeOnStop: false,
586
- removeOnFail: false,
587
- retry: { maxRetries: 10, delay: 5000 },
588
- })
589
571
  } else if (params.path) {
590
- let filepath = kernel.api.filePath(params.path, req.cwd)
572
+ const filepath = kernel.api.filePath(params.path, req.cwd)
591
573
  folder = path.dirname(filepath)
592
- await fs.promises.mkdir(folder, { recursive: true }).catch((e) => { })
593
- let filename = path.basename(filepath)
594
- dl = new DownloaderHelper(url, folder, {
595
- headers: {
596
- "user-agent": userAgent,
597
- },
598
- // httpsRequestOptions: {
599
- // rejectUnauthorized: false
600
- // },
601
- override: {
602
- skip: true,
603
- skipSmaller: false,
604
- },
605
- fileName: filename,
606
- resumeIfFileExists: true,
607
- removeOnStop: false,
608
- removeOnFail: false,
609
- retry: { maxRetries: 10, delay: 5000 },
610
- })
574
+ filename = path.basename(filepath)
575
+ } else {
576
+ throw new Error('fs.download: either dir or path must be specified')
611
577
  }
578
+
612
579
  ondata({ raw: `\r\nDownloading ${url} to ${folder}...\r\n` })
613
- let res = await new Promise((resolve, reject) => {
614
- dl.on('end', () => {
615
- console.log('Download Completed');
616
- ondata({ raw: `\r\nDownload Complete!\r\n` })
617
- resolve()
618
- })
619
- dl.on('error', (err) => {
620
- console.log('Download Error', err)
621
- ondata({ raw: `\r\n[Download Error] ${err.stack}!\r\n` })
622
- reject(err)
623
- })
624
- dl.on('progress', (stats) => {
625
- let p = Math.floor(stats.progress)
626
- let str = ""
627
- for(let i=0; i<p; i++) {
628
- str += "#"
629
- }
630
- for(let i=p; i<100; i++) {
631
- str += "-"
580
+
581
+ const workerPath = path.resolve(__dirname, 'download_worker.js')
582
+ await fs.promises.mkdir(folder, { recursive: true }).catch(() => {})
583
+
584
+ await new Promise((resolve, reject) => {
585
+ let settled = false
586
+ const finish = (err) => {
587
+ if (settled) return
588
+ settled = true
589
+ if (err) {
590
+ reject(err)
591
+ } else {
592
+ resolve()
632
593
  }
633
- ondata({ raw: `\r${str}` })
634
- })
635
- dl.on('download', (downloadInfo) => {
636
- const msg = `\r\n[Download Started] ${JSON.stringify({ name: downloadInfo.fileName, total: downloadInfo.totalSize })}\r\n`
637
- ondata({ raw: msg })
638
- })
639
- dl.on('skip', (skipInfo) => {
640
- const msg = `\r\n[Download Skipped] File already exists: ${JSON.stringify(skipInfo)}\r\n`
641
- ondata({ raw: msg })
642
- resolve()
594
+ }
595
+
596
+ const child = fork(workerPath, [], {
597
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
643
598
  })
644
- dl.on('retry', (attempt, opts, err) => {
645
- const msg = "\r\n[Retrying] " + JSON.stringify({
646
- RetryAttempt: `${attempt}/${opts.maxRetries}`,
647
- StartsOn: `${opts.delay / 1000} secs`,
648
- Reason: err ? err.message : 'unknown'
649
- }) + "\r\n";
650
- ondata({ raw: msg })
599
+
600
+ child.on('message', (msg) => {
601
+ if (!msg || typeof msg !== 'object') return
602
+ switch (msg.type) {
603
+ case 'download': {
604
+ const payload = msg.info || {}
605
+ // Minimal start message; no extra JSON noise.
606
+ ondata({ raw: `\r\n[Download Started] ${payload.fileName || ''}\r\n` })
607
+ break
608
+ }
609
+ case 'progress.throttled': {
610
+ const stats = msg.stats || {}
611
+ const p = typeof stats.progress === 'number' ? Math.floor(stats.progress) : null
612
+ if (p !== null && p >= 0 && p <= 100) {
613
+ let bar = ''
614
+ for (let i = 0; i < p; i++) bar += '#'
615
+ for (let i = p; i < 100; i++) bar += '-'
616
+ ondata({ raw: `\r${bar}` })
617
+ }
618
+ break
619
+ }
620
+ case 'stall': {
621
+ const elapsed = msg.elapsed || 0
622
+ ondata({ raw: `\r\n[Download Watchdog] No progress for ${(elapsed / 1000).toFixed(1)}s (url: ${url})\r\n` })
623
+ break
624
+ }
625
+ case 'skip': {
626
+ const info = msg.info || {}
627
+ ondata({ raw: `\r\n[Download Skipped] File already exists: ${JSON.stringify(info)}\r\n` })
628
+ finish()
629
+ break
630
+ }
631
+ case 'end': {
632
+ const info = msg.info || {}
633
+ ondata({ raw: `\r\n[Download End] ${JSON.stringify(info)}\r\n` })
634
+ ondata({ raw: `\r\nDownload Complete!\r\n` })
635
+ finish()
636
+ break
637
+ }
638
+ case 'error': {
639
+ const err = msg.error || {}
640
+ const text = err.stack || err.message || String(err)
641
+ ondata({ raw: `\r\n[Download Error] ${text}\r\n` })
642
+ finish(new Error(err.message || 'download error'))
643
+ break
644
+ }
645
+ default:
646
+ break
647
+ }
651
648
  })
652
- dl.on('stateChanged', (state) => {
653
- const msg = "\r\n[State changed] " + state + "\r\n"
654
- ondata({ raw: msg })
649
+
650
+ child.on('exit', (code) => {
651
+ if (settled) return
652
+ if (code === 0) {
653
+ finish()
654
+ } else {
655
+ finish(new Error(`download worker exited with code ${code}`))
656
+ }
655
657
  })
656
- dl.on('redirected', (newUrl, oldUrl) => {
657
- const msg = `\r\n[Redirected] '${oldUrl}' => '${newUrl}'\r\n`
658
- ondata({ raw: msg })
658
+
659
+ child.on('error', (err) => {
660
+ if (settled) return
661
+ finish(err)
659
662
  })
660
663
 
661
- dl.start().catch((err) => {
662
- ondata({ raw: `\r\n[Download Failed] ${err.stack}!\r\n` })
663
- reject(err)
664
+ child.send({
665
+ url,
666
+ folder,
667
+ fileName: filename || null
664
668
  })
665
669
  })
666
670
  }
@@ -787,6 +787,9 @@ class Api {
787
787
 
788
788
  }
789
789
  async step (request, rawrpc, input, i, total, args) {
790
+ if (this.kernel.sysReady) {
791
+ await this.kernel.sysReady
792
+ }
790
793
  await Promise.all([this.init(), this.kernel.update_sysinfo()])
791
794
 
792
795
  // clear global regular expression object RegExp.lastMatch (memory leak prevention)
@@ -1023,6 +1023,10 @@ class Bin {
1023
1023
  return relevant.platform && relevant.arch && relevant.gpu
1024
1024
  }
1025
1025
  async check(config) {
1026
+ if (typeof this.kernel.binCheckDepth !== 'number') {
1027
+ this.kernel.binCheckDepth = 0
1028
+ }
1029
+ this.kernel.binCheckDepth++
1026
1030
  let requirements = this.requirements(config)
1027
1031
  let requirements_pending = !this.installed_initialized
1028
1032
  let install_required = true
@@ -1087,7 +1091,7 @@ class Bin {
1087
1091
  requirements = requirements.filter((r) => {
1088
1092
  return r.relevant
1089
1093
  })
1090
-
1094
+ this.kernel.binCheckDepth--
1091
1095
  return {
1092
1096
  error,
1093
1097
  title: config.bin.title,
@@ -1097,7 +1101,6 @@ class Bin {
1097
1101
  install_required,
1098
1102
  requirements_pending
1099
1103
  }
1100
-
1101
1104
  }
1102
1105
  winBuildNumber() {
1103
1106
  let osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
@@ -587,12 +587,29 @@ const init = async (options, kernel) => {
587
587
  await fs.promises.writeFile(destination, rendered_recipe)
588
588
  }
589
589
  }
590
- await fs.promises.writeFile(path.resolve(root, ".geminiignore"), `ENVIRONMENT
590
+ const geminiIgnorePath = path.resolve(root, ".geminiignore")
591
+ const geminiIgnoreContent = `ENVIRONMENT
591
592
  !/logs
592
593
  !/GEMINI.md
593
594
  !/SPEC.md
594
595
  !/app
595
- !${kernel.homedir}`)
596
+ !${kernel.homedir}`
597
+ let shouldWriteGeminiIgnore = false
598
+ try {
599
+ const existingGeminiIgnore = await fs.promises.readFile(geminiIgnorePath, "utf8")
600
+ if (existingGeminiIgnore !== geminiIgnoreContent) {
601
+ shouldWriteGeminiIgnore = true
602
+ }
603
+ } catch (error) {
604
+ if (error && error.code === "ENOENT") {
605
+ shouldWriteGeminiIgnore = true
606
+ } else {
607
+ throw error
608
+ }
609
+ }
610
+ if (shouldWriteGeminiIgnore) {
611
+ await fs.promises.writeFile(geminiIgnorePath, geminiIgnoreContent)
612
+ }
596
613
  }
597
614
 
598
615
  const gitDir = path.resolve(root, ".git")