pinokiod 7.3.0 → 7.3.3

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 (125) hide show
  1. package/kernel/api/github/index.js +444 -0
  2. package/kernel/api/index.js +199 -11
  3. package/kernel/api/process/index.js +124 -44
  4. package/kernel/api/shell_run_template.js +273 -0
  5. package/kernel/api/uri/index.js +51 -0
  6. package/kernel/bin/{conda-python.js → conda-pins.js} +23 -0
  7. package/kernel/bin/conda.js +15 -5
  8. package/kernel/bin/git.js +9 -10
  9. package/kernel/bin/huggingface.js +1 -1
  10. package/kernel/bin/index.js +5 -2
  11. package/kernel/bin/zip.js +9 -1
  12. package/kernel/connect/providers/github/README.md +5 -4
  13. package/kernel/environment.js +195 -92
  14. package/kernel/git.js +98 -19
  15. package/kernel/gitconfig_template +7 -0
  16. package/kernel/gpu/amd.js +72 -0
  17. package/kernel/gpu/apple.js +8 -0
  18. package/kernel/gpu/common.js +12 -0
  19. package/kernel/gpu/intel.js +47 -0
  20. package/kernel/gpu/nvidia.js +8 -0
  21. package/kernel/index.js +11 -1
  22. package/kernel/managed_skills.js +871 -0
  23. package/kernel/plugin.js +6 -58
  24. package/kernel/plugin_sources.js +316 -0
  25. package/kernel/resource_usage/gpu.js +349 -0
  26. package/kernel/resource_usage/index.js +322 -0
  27. package/kernel/resource_usage/macos_footprint.js +197 -0
  28. package/kernel/resource_usage/preferences.js +92 -0
  29. package/kernel/resource_usage/process_tree.js +303 -0
  30. package/kernel/scripts/git/create +4 -4
  31. package/kernel/scripts/git/fork +7 -8
  32. package/kernel/shell.js +23 -2
  33. package/kernel/shells.js +41 -0
  34. package/kernel/sysinfo.js +62 -9
  35. package/kernel/util.js +60 -0
  36. package/package.json +1 -1
  37. package/server/index.js +984 -156
  38. package/server/lib/app_log_report.js +543 -0
  39. package/server/lib/content_validation.js +55 -33
  40. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  41. package/server/lib/terminal_session_helpers.js +0 -3
  42. package/server/public/common.js +77 -31
  43. package/server/public/create-launcher.js +4 -32
  44. package/server/public/logs.js +1428 -0
  45. package/server/public/nav.js +7 -0
  46. package/server/public/plugin-detail.js +93 -10
  47. package/server/public/privacy_filter_worker.js +391 -0
  48. package/server/public/style.css +1104 -154
  49. package/server/public/task-launcher.js +8 -29
  50. package/server/public/universal-launcher.css +8 -6
  51. package/server/public/universal-launcher.js +3 -27
  52. package/server/routes/apps.js +195 -1
  53. package/server/views/app.ejs +3041 -717
  54. package/server/views/autolaunch.ejs +917 -0
  55. package/server/views/bootstrap.ejs +7 -1
  56. package/server/views/d.ejs +408 -65
  57. package/server/views/editor.ejs +85 -19
  58. package/server/views/index.ejs +661 -111
  59. package/server/views/init/index.ejs +1 -1
  60. package/server/views/install.ejs +1 -1
  61. package/server/views/logs.ejs +164 -86
  62. package/server/views/net.ejs +7 -1
  63. package/server/views/partials/d_terminal_column.ejs +2 -2
  64. package/server/views/partials/d_terminal_options.ejs +0 -8
  65. package/server/views/partials/fs_status.ejs +47 -0
  66. package/server/views/partials/home_action_modal.ejs +86 -0
  67. package/server/views/partials/home_run_menu.ejs +87 -0
  68. package/server/views/partials/main_sidebar.ejs +2 -0
  69. package/server/views/partials/menu.ejs +1 -1
  70. package/server/views/plugin_detail.ejs +19 -4
  71. package/server/views/plugins.ejs +201 -3
  72. package/server/views/pre.ejs +1 -1
  73. package/server/views/pro.ejs +1 -1
  74. package/server/views/shell.ejs +40 -18
  75. package/server/views/skills.ejs +506 -0
  76. package/server/views/terminal.ejs +45 -19
  77. package/spec/INSTRUCTION_SYNC.md +20 -10
  78. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  79. package/system/plugin/antigravity-cli/common.js +155 -0
  80. package/system/plugin/antigravity-cli/install.js +272 -0
  81. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  82. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  83. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  84. package/system/plugin/claude/claude.png +0 -0
  85. package/system/plugin/claude/pinokio.js +47 -0
  86. package/system/plugin/claude-auto/claude.png +0 -0
  87. package/system/plugin/claude-auto/pinokio.js +58 -0
  88. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  89. package/system/plugin/claude-desktop/pinokio.js +23 -0
  90. package/system/plugin/codex/openai.webp +0 -0
  91. package/system/plugin/codex/pinokio.js +42 -0
  92. package/system/plugin/codex-auto/openai.webp +0 -0
  93. package/system/plugin/codex-auto/pinokio.js +49 -0
  94. package/system/plugin/codex-desktop/icon.png +0 -0
  95. package/system/plugin/codex-desktop/pinokio.js +23 -0
  96. package/system/plugin/crush/crush.png +0 -0
  97. package/system/plugin/crush/pinokio.js +15 -0
  98. package/system/plugin/cursor/cursor.jpeg +0 -0
  99. package/system/plugin/cursor/pinokio.js +23 -0
  100. package/system/plugin/qwen/pinokio.js +34 -0
  101. package/system/plugin/qwen/qwen.png +0 -0
  102. package/system/plugin/vscode/pinokio.js +20 -0
  103. package/system/plugin/vscode/vscode.png +0 -0
  104. package/system/plugin/windsurf/pinokio.js +23 -0
  105. package/system/plugin/windsurf/windsurf.png +0 -0
  106. package/test/antigravity-cli-plugin.test.js +185 -0
  107. package/test/app-api.test.js +239 -0
  108. package/test/app-log-report.test.js +67 -0
  109. package/test/environment-cache-preflight.test.js +98 -0
  110. package/test/git-bin.test.js +59 -0
  111. package/test/git-defaults.test.js +97 -0
  112. package/test/github-api.test.js +158 -0
  113. package/test/github-connection.test.js +117 -0
  114. package/test/huggingface-bin.test.js +25 -0
  115. package/test/managed-skills.test.js +351 -0
  116. package/test/plugin-action-functions.test.js +337 -0
  117. package/test/plugin-dev-iframe.test.js +17 -0
  118. package/test/plugin-sources.test.js +203 -0
  119. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  120. package/test/process-wait.test.js +169 -0
  121. package/test/script-api.test.js +97 -0
  122. package/test/shell-api.test.js +134 -0
  123. package/test/shell-run-template.test.js +209 -0
  124. package/test/storage-api.test.js +137 -0
  125. package/test/uri-api.test.js +100 -0
@@ -0,0 +1,203 @@
1
+ const assert = require("assert")
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
+ const PluginSources = require("../kernel/plugin_sources")
7
+
8
+ test("resolveLauncherPluginSelection returns app-dev plugin query paths", () => {
9
+ assert.strictEqual(
10
+ PluginSources.resolveLauncherPluginSelection("claude"),
11
+ "/pinokio/run/plugin/claude/pinokio.js"
12
+ )
13
+ assert.strictEqual(
14
+ PluginSources.resolveLauncherPluginSelection("antigravity-cli"),
15
+ "/pinokio/run/plugin/antigravity-cli/pinokio.js"
16
+ )
17
+ assert.strictEqual(
18
+ PluginSources.resolveLauncherPluginSelection("codex-auto"),
19
+ "/plugin/codex-auto/pinokio.js"
20
+ )
21
+ assert.strictEqual(
22
+ PluginSources.resolveLauncherPluginSelection("plugin/local-tool"),
23
+ "/plugin/local-tool/pinokio.js"
24
+ )
25
+ assert.strictEqual(
26
+ PluginSources.resolveLauncherPluginSelection("api/my-app/plugins/helper"),
27
+ "/api/my-app/plugins/helper/pinokio.js"
28
+ )
29
+ assert.strictEqual(
30
+ PluginSources.resolveLauncherPluginSelection("pinokio/run/plugin/codex-auto"),
31
+ "/pinokio/run/plugin/codex-auto/pinokio.js"
32
+ )
33
+ })
34
+
35
+ test("normalizeActionPathComponents maps action URLs back to filesystem roots", () => {
36
+ assert.deepStrictEqual(
37
+ PluginSources.normalizeActionPathComponents(["pinokio", "run", "plugin", "antigravity-cli", "pinokio.js"]),
38
+ {
39
+ system: true,
40
+ pathComponents: ["plugin", "antigravity-cli", "pinokio.js"],
41
+ }
42
+ )
43
+ assert.deepStrictEqual(
44
+ PluginSources.normalizeActionPathComponents(["plugin", "local-tool", "pinokio.js"]),
45
+ {
46
+ system: false,
47
+ pathComponents: ["plugin", "local-tool", "pinokio.js"],
48
+ }
49
+ )
50
+ assert.deepStrictEqual(
51
+ PluginSources.normalizeActionPathComponents(["run", "plugin", "local-tool", "pinokio.js"]),
52
+ {
53
+ system: false,
54
+ pathComponents: ["plugin", "local-tool", "pinokio.js"],
55
+ }
56
+ )
57
+ assert.deepStrictEqual(
58
+ PluginSources.normalizeActionPathComponents(["api", "my-app", "plugins", "helper", "pinokio.js"]),
59
+ {
60
+ system: false,
61
+ pathComponents: ["api", "my-app", "plugins", "helper", "pinokio.js"],
62
+ }
63
+ )
64
+ })
65
+
66
+ test("normalizeLauncherSuccessPlugin rewrites prototype plugin redirects", () => {
67
+ assert.strictEqual(
68
+ PluginSources.normalizeLauncherSuccessPlugin(
69
+ "/p/example/dev?plugin=/plugin/pinokio/run/plugin/codex-auto/pinokio.js&prompt=build",
70
+ "pinokio/run/plugin/codex-auto"
71
+ ),
72
+ "/p/example/dev?plugin=%2Fpinokio%2Frun%2Fplugin%2Fcodex-auto%2Fpinokio.js&prompt=build"
73
+ )
74
+
75
+ assert.strictEqual(
76
+ PluginSources.normalizeLauncherSuccessPlugin(
77
+ "/p/example/dev?plugin=/plugin/api/my-app/plugins/helper/pinokio.js",
78
+ "api/my-app/plugins/helper"
79
+ ),
80
+ "/p/example/dev?plugin=%2Fapi%2Fmy-app%2Fplugins%2Fhelper%2Fpinokio.js"
81
+ )
82
+
83
+ assert.strictEqual(
84
+ PluginSources.normalizeLauncherSuccessPlugin("/p/example/dev", "pinokio/run/plugin/codex-auto"),
85
+ "/p/example/dev"
86
+ )
87
+ })
88
+
89
+ test("isValidPluginConfig accepts only action functions and valid standalone plugin paths", () => {
90
+ assert.strictEqual(
91
+ PluginSources.isValidPluginConfig({ run: async () => [] }),
92
+ true
93
+ )
94
+
95
+ assert.strictEqual(
96
+ PluginSources.isValidPluginConfig({
97
+ run: [{ method: "shell.run", params: { message: "echo ok" } }],
98
+ install: async () => [],
99
+ update: async () => [],
100
+ uninstall: async () => [],
101
+ installed: async () => true,
102
+ }),
103
+ true
104
+ )
105
+
106
+ assert.strictEqual(
107
+ PluginSources.isValidPluginConfig({
108
+ run: [{ method: "shell.run", params: { message: "echo ok" } }],
109
+ helper: () => [],
110
+ }),
111
+ false
112
+ )
113
+
114
+ assert.strictEqual(
115
+ PluginSources.isValidPluginConfig({
116
+ run: [{ method: "shell.run", params: { message: "echo ok" } }],
117
+ install: "install.js",
118
+ }),
119
+ false
120
+ )
121
+
122
+ assert.strictEqual(
123
+ PluginSources.isValidPluginConfig({
124
+ run: [{ method: "shell.run", params: { message: "echo ok" } }],
125
+ installed: true,
126
+ }),
127
+ false
128
+ )
129
+
130
+ assert.strictEqual(
131
+ PluginSources.isValidPluginConfig(
132
+ { path: "plugin", run: async () => [] },
133
+ { standalone: true }
134
+ ),
135
+ true
136
+ )
137
+
138
+ assert.strictEqual(
139
+ PluginSources.isValidPluginConfig(
140
+ { path: "api/example/plugins/tool", run: async () => [] },
141
+ { standalone: true }
142
+ ),
143
+ false
144
+ )
145
+ })
146
+
147
+ test("loadPluginMenu merges bundled plugins and valid standalone local plugins", async () => {
148
+ const homedir = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-plugin-menu-"))
149
+ try {
150
+ await fs.mkdir(path.join(homedir, "plugin", "local-tool"), { recursive: true })
151
+ await fs.writeFile(path.join(homedir, "plugin", "local-tool", "pinokio.js"), `
152
+ module.exports = {
153
+ path: "plugin",
154
+ title: "Local Tool",
155
+ icon: "local.png",
156
+ run: [{ method: "shell.run", params: { message: "echo local" } }]
157
+ }
158
+ `)
159
+
160
+ await fs.mkdir(path.join(homedir, "plugin", "code", "legacy"), { recursive: true })
161
+ await fs.writeFile(path.join(homedir, "plugin", "code", "legacy", "pinokio.js"), `
162
+ module.exports = {
163
+ path: "plugin",
164
+ title: "Legacy Code Plugin",
165
+ run: [{ method: "shell.run", params: { message: "echo legacy" } }]
166
+ }
167
+ `)
168
+
169
+ await fs.mkdir(path.join(homedir, "plugin", "app-owned"), { recursive: true })
170
+ await fs.writeFile(path.join(homedir, "plugin", "app-owned", "pinokio.js"), `
171
+ module.exports = {
172
+ path: "api/example/plugins/tool",
173
+ title: "App Owned",
174
+ run: [{ method: "shell.run", params: { message: "echo app" } }]
175
+ }
176
+ `)
177
+
178
+ const repoRoot = path.resolve(__dirname, "..")
179
+ const kernel = {
180
+ homedir,
181
+ systemPath: (...parts) => path.join(repoRoot, "system", ...parts),
182
+ require: async (filepath) => {
183
+ delete require.cache[require.resolve(filepath)]
184
+ return require(filepath)
185
+ }
186
+ }
187
+
188
+ const menu = await PluginSources.loadPluginMenu(kernel)
189
+ const systemPlugins = menu.filter((item) => item.source === "system")
190
+ const localPlugins = menu.filter((item) => item.source === "local")
191
+
192
+ assert.strictEqual(systemPlugins.length, 13)
193
+ assert.ok(systemPlugins.every((item) => item.system === true))
194
+ assert.ok(systemPlugins.every((item) => item.href.startsWith("/pinokio/run/plugin/")))
195
+ assert.ok(systemPlugins.every((item) => item.image.startsWith("/pinokio/asset/plugin/")))
196
+
197
+ assert.deepStrictEqual(localPlugins.map((item) => item.title), ["Local Tool"])
198
+ assert.strictEqual(localPlugins[0].href, "/run/plugin/local-tool/pinokio.js")
199
+ assert.strictEqual(localPlugins[0].image, "/asset/plugin/local-tool/local.png")
200
+ } finally {
201
+ await fs.rm(homedir, { recursive: true, force: true })
202
+ }
203
+ })
@@ -0,0 +1,69 @@
1
+ const assert = require('node:assert/strict')
2
+ const fs = require('node:fs')
3
+ const path = require('node:path')
4
+ const test = require('node:test')
5
+ const vm = require('node:vm')
6
+
7
+ function loadWorkerHelpers() {
8
+ const workerPath = path.resolve(__dirname, '..', 'server', 'public', 'privacy_filter_worker.js')
9
+ const code = `${fs.readFileSync(workerPath, 'utf8')}\n;globalThis.__test = { prepareMaskEntities, maskText }`
10
+ const sandbox = {
11
+ URL,
12
+ console,
13
+ performance: { now: () => 0 },
14
+ self: {
15
+ postMessage: () => {},
16
+ addEventListener: () => {}
17
+ }
18
+ }
19
+ vm.runInNewContext(code, sandbox, { filename: workerPath })
20
+ return sandbox.__test
21
+ }
22
+
23
+ test('privacy filter preserves URLs by default and masks URL credentials only', () => {
24
+ const { prepareMaskEntities, maskText } = loadWorkerHelpers()
25
+
26
+ const publicText = [
27
+ '+ mlx==0.31.3 (from git+https://github.com/ml-explore/mlx.git)',
28
+ '+ tool==1.0.0 (from https://example.org/packages/tool.tar.gz)',
29
+ '+ sshpkg (from git@gitlab.com:group/project.git)'
30
+ ].join('\n')
31
+ const publicStart = publicText.indexOf('https://github.com')
32
+ assert.equal(
33
+ prepareMaskEntities(publicText, [{
34
+ label: 'private_url',
35
+ start: publicStart,
36
+ end: publicText.indexOf(')', publicStart)
37
+ }]).length,
38
+ 0
39
+ )
40
+
41
+ const credentialText = 'from https://x-access-token:ghp_secret@github.com/org/private.git'
42
+ const credentialStart = credentialText.indexOf('https://')
43
+ const credentialMasked = maskText(credentialText, prepareMaskEntities(credentialText, [{
44
+ label: 'private_url',
45
+ start: credentialStart,
46
+ end: credentialText.length
47
+ }])).masked
48
+ assert.equal(credentialMasked, 'from https://[url_credential]@github.com/org/private.git')
49
+
50
+ const tokenText = 'from https://github.com/org/repo.git?token=secret'
51
+ const tokenStart = tokenText.indexOf('https://')
52
+ const tokenMasked = maskText(tokenText, prepareMaskEntities(tokenText, [{
53
+ label: 'private_url',
54
+ start: tokenStart,
55
+ end: tokenText.length
56
+ }])).masked
57
+ assert.equal(tokenMasked, 'from https://github.com/org/repo.git?token=[url_secret]')
58
+
59
+ const privateHostText = 'callback https://customer.example.test/install'
60
+ const privateHostStart = privateHostText.indexOf('https://')
61
+ assert.equal(
62
+ prepareMaskEntities(privateHostText, [{
63
+ label: 'private_url',
64
+ start: privateHostStart,
65
+ end: privateHostText.length
66
+ }]).length,
67
+ 0
68
+ )
69
+ })
@@ -0,0 +1,169 @@
1
+ const assert = require("node:assert/strict")
2
+ const test = require("node:test")
3
+
4
+ const Process = require("../kernel/api/process")
5
+
6
+ test("process.wait tracks active waits with metadata and clears them when done", async () => {
7
+ const processApi = new Process()
8
+ const waitPath = "/pinokio/api/demo/start.js"
9
+ const kernel = {
10
+ activeProcessWaits: {},
11
+ procs: {}
12
+ }
13
+ const events = []
14
+ const req = {
15
+ parent: { path: waitPath },
16
+ params: {
17
+ sec: 0.001,
18
+ title: "Launching",
19
+ description: "Waiting for app",
20
+ message: "Hold on"
21
+ }
22
+ }
23
+
24
+ const waitPromise = processApi.wait(req, (data, type) => {
25
+ events.push({ data, type })
26
+ }, kernel)
27
+
28
+ assert.equal(kernel.activeProcessWaits[waitPath].path, waitPath)
29
+ assert.equal(kernel.activeProcessWaits[waitPath].title, "Launching")
30
+ assert.equal(kernel.activeProcessWaits[waitPath].description, "Waiting for app")
31
+ assert.equal(kernel.activeProcessWaits[waitPath].message, "Hold on")
32
+ assert.equal(kernel.activeProcessWaits[waitPath].params, req.params)
33
+
34
+ await waitPromise
35
+
36
+ assert.equal(kernel.activeProcessWaits[waitPath], undefined)
37
+ assert.deepEqual(events.map((event) => event.type), [
38
+ "process.wait.start",
39
+ "process.wait.end"
40
+ ])
41
+ })
42
+
43
+ test("process.wait preserves existing no-metadata waits without footer events", async () => {
44
+ const processApi = new Process()
45
+ const waitPath = "/pinokio/api/demo/start.js"
46
+ const kernel = {
47
+ activeProcessWaits: {},
48
+ procs: {}
49
+ }
50
+ const events = []
51
+ const req = {
52
+ parent: { path: waitPath },
53
+ params: { sec: 0.001 }
54
+ }
55
+
56
+ const waitPromise = processApi.wait(req, (data, type) => {
57
+ events.push({ data, type })
58
+ }, kernel)
59
+
60
+ assert.equal(kernel.activeProcessWaits[waitPath].path, waitPath)
61
+ await waitPromise
62
+
63
+ assert.equal(kernel.activeProcessWaits[waitPath], undefined)
64
+ assert.deepEqual(events, [])
65
+ })
66
+
67
+ test("process.wait shows default footer metadata for indefinite waits", async () => {
68
+ const processApi = new Process()
69
+ const waitPath = "/pinokio/api/demo/start.js"
70
+ const kernel = {
71
+ activeProcessWaits: {},
72
+ procs: {}
73
+ }
74
+ const events = []
75
+ const req = {
76
+ parent: { path: waitPath }
77
+ }
78
+
79
+ const waitPromise = processApi.wait(req, (data, type) => {
80
+ events.push({ data, type })
81
+ }, kernel)
82
+
83
+ assert.equal(req.params.title, "Waiting")
84
+ assert.equal(req.params.description, "Click Stop when done.")
85
+ assert.equal(kernel.activeProcessWaits[waitPath].title, "Waiting")
86
+ assert.equal(kernel.activeProcessWaits[waitPath].description, "Click Stop when done.")
87
+ assert.equal(kernel.procs[waitPath], processApi)
88
+ assert.deepEqual(events.map((event) => event.type), ["process.wait.start"])
89
+
90
+ processApi.resolve()
91
+ await waitPromise
92
+
93
+ assert.equal(kernel.activeProcessWaits[waitPath], undefined)
94
+ assert.deepEqual(events.map((event) => event.type), [
95
+ "process.wait.start",
96
+ "process.wait.end"
97
+ ])
98
+ })
99
+
100
+ test("process.wait shows message-only indefinite waits in the footer", async () => {
101
+ const processApi = new Process()
102
+ const waitPath = "/pinokio/api/demo/start.js"
103
+ const kernel = {
104
+ activeProcessWaits: {},
105
+ procs: {}
106
+ }
107
+ const events = []
108
+ const req = {
109
+ parent: { path: waitPath },
110
+ params: {
111
+ message: "Waiting for external app"
112
+ }
113
+ }
114
+
115
+ const waitPromise = processApi.wait(req, (data, type) => {
116
+ events.push({ data, type })
117
+ }, kernel)
118
+
119
+ assert.equal(req.params.title, undefined)
120
+ assert.equal(req.params.description, undefined)
121
+ assert.equal(kernel.activeProcessWaits[waitPath].message, "Waiting for external app")
122
+ assert.deepEqual(events.map((event) => event.type), ["process.wait.start"])
123
+
124
+ processApi.resolve()
125
+ await waitPromise
126
+
127
+ assert.equal(kernel.activeProcessWaits[waitPath], undefined)
128
+ assert.deepEqual(events.map((event) => event.type), [
129
+ "process.wait.start",
130
+ "process.wait.end"
131
+ ])
132
+ })
133
+
134
+ test("process.wait dispatches app presence waits through AppAPI", async () => {
135
+ const processApi = new Process()
136
+ const waitPath = "/pinokio/api/demo/start.js"
137
+ const kernel = {
138
+ activeProcessWaits: {},
139
+ procs: {}
140
+ }
141
+ const calls = []
142
+ processApi.appApi = {
143
+ waitForAppPresence: async (req, ondata, kernelArg) => {
144
+ calls.push({ req, kernel: kernelArg })
145
+ return { id: req.params.app }
146
+ }
147
+ }
148
+ const req = {
149
+ parent: { path: waitPath },
150
+ params: {
151
+ app: "Native Tool",
152
+ title: "Waiting for Native Tool"
153
+ }
154
+ }
155
+ const events = []
156
+
157
+ await processApi.wait(req, (data, type) => {
158
+ events.push({ data, type })
159
+ }, kernel)
160
+
161
+ assert.equal(calls.length, 1)
162
+ assert.equal(calls[0].req, req)
163
+ assert.equal(calls[0].kernel, kernel)
164
+ assert.equal(kernel.activeProcessWaits[waitPath], undefined)
165
+ assert.deepEqual(events.map((event) => event.type), [
166
+ "process.wait.start",
167
+ "process.wait.end"
168
+ ])
169
+ })
@@ -0,0 +1,97 @@
1
+ const assert = require('node:assert/strict')
2
+ const path = require('node:path')
3
+ const test = require('node:test')
4
+
5
+ const Script = require('../kernel/api/script')
6
+
7
+ test('script.restart stops explicit target and schedules start with explicit params', async () => {
8
+ const script = new Script()
9
+ const scheduled = []
10
+ script.scheduleStart = (req) => {
11
+ scheduled.push(req)
12
+ }
13
+ const stopped = []
14
+ const kernel = {
15
+ memory: { args: {} },
16
+ api: {
17
+ filePath: (uri, cwd) => path.resolve(cwd, uri),
18
+ stop: async (req) => {
19
+ stopped.push(req)
20
+ }
21
+ }
22
+ }
23
+ const events = []
24
+
25
+ const result = await script.restart({
26
+ cwd: '/pinokio/api/demo',
27
+ parent: {
28
+ path: '/pinokio/api/demo/main.js',
29
+ id: 'main-session'
30
+ },
31
+ params: {
32
+ uri: 'child.js',
33
+ params: { prompt: 'hello' }
34
+ }
35
+ }, (data, type) => {
36
+ events.push({ data, type })
37
+ }, kernel)
38
+
39
+ assert.deepEqual(stopped, [
40
+ { params: { uri: '/pinokio/api/demo/child.js' } }
41
+ ])
42
+ assert.equal(scheduled.length, 1)
43
+ assert.equal(scheduled[0].params.uri, 'child.js')
44
+ assert.deepEqual(scheduled[0].params.params, { prompt: 'hello' })
45
+ assert.deepEqual(result, {
46
+ uri: 'child.js',
47
+ scheduled: true,
48
+ self: false,
49
+ params: { prompt: 'hello' }
50
+ })
51
+ assert.equal(events[0].type, 'restart')
52
+ assert.equal(events[0].data.id, '/pinokio/api/demo/child.js')
53
+ })
54
+
55
+ test('script.restart self restart preserves session id and parent args', async () => {
56
+ const script = new Script()
57
+ const scheduled = []
58
+ script.scheduleStart = (req) => {
59
+ scheduled.push(req)
60
+ }
61
+ const stopped = []
62
+ const kernel = {
63
+ memory: { args: {} },
64
+ api: {
65
+ filePath: (uri, cwd) => path.resolve(cwd, uri),
66
+ stop: async (req) => {
67
+ stopped.push(req)
68
+ }
69
+ }
70
+ }
71
+
72
+ const result = await script.restart({
73
+ cwd: '/pinokio/api/demo',
74
+ parent: {
75
+ path: '/pinokio/api/demo/main.js',
76
+ id: 'main-session',
77
+ caller: 'caller-session',
78
+ args: { prompt: 'again' }
79
+ },
80
+ params: {}
81
+ }, () => {}, kernel)
82
+
83
+ assert.deepEqual(stopped, [
84
+ { params: { id: 'main-session' } }
85
+ ])
86
+ assert.equal(scheduled.length, 1)
87
+ assert.equal(scheduled[0].id, 'main-session')
88
+ assert.equal(scheduled[0].caller, 'caller-session')
89
+ assert.equal(scheduled[0].params.uri, '/pinokio/api/demo/main.js')
90
+ assert.deepEqual(scheduled[0].params.params, { prompt: 'again' })
91
+ assert.deepEqual(result, {
92
+ uri: '/pinokio/api/demo/main.js',
93
+ scheduled: true,
94
+ self: true,
95
+ params: { prompt: 'again' }
96
+ })
97
+ })
@@ -0,0 +1,134 @@
1
+ const test = require('node:test')
2
+ const assert = require('node:assert/strict')
3
+ const path = require('node:path')
4
+ const ShellAPI = require('../kernel/api/shell')
5
+
6
+ function createKernel () {
7
+ const calls = []
8
+ return {
9
+ calls,
10
+ homedir: '/tmp/pinokio-home',
11
+ shell: {
12
+ start: async (params, options) => {
13
+ calls.push({ method: 'start', params: structuredClone(params), options: structuredClone(options) })
14
+ return 'shell-id'
15
+ },
16
+ enter: async (params) => {
17
+ calls.push({ method: 'enter', params: structuredClone(params) })
18
+ return 'entered'
19
+ },
20
+ write: async (params) => {
21
+ calls.push({ method: 'write', params: structuredClone(params) })
22
+ return 'written'
23
+ },
24
+ run: async (params, options) => {
25
+ calls.push({ method: 'run', params: structuredClone(params), options: structuredClone(options) })
26
+ return { stdout: 'ok' }
27
+ },
28
+ kill: async (params) => {
29
+ calls.push({ method: 'kill', params: structuredClone(params) })
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ test('shell.start forwards defaults, client size, parent, and shell options', async () => {
36
+ const api = new ShellAPI()
37
+ const kernel = createKernel()
38
+ const cwd = path.join('/tmp', 'pinokio-shell-api')
39
+ const result = await api.start({
40
+ cwd,
41
+ parent: {
42
+ id: 'parent-request',
43
+ body: { title: 'Parent Title' }
44
+ },
45
+ client: {
46
+ rows: 33,
47
+ cols: 120
48
+ }
49
+ }, () => {}, kernel)
50
+
51
+ assert.equal(result, 'shell-id')
52
+ assert.deepEqual(kernel.calls, [{
53
+ method: 'start',
54
+ params: {
55
+ id: cwd,
56
+ path: cwd,
57
+ rows: 33,
58
+ cols: 120,
59
+ $parent: {
60
+ id: 'parent-request',
61
+ body: { title: 'Parent Title' }
62
+ },
63
+ bluefairy: 'off'
64
+ },
65
+ options: {
66
+ cwd,
67
+ group: 'parent-request',
68
+ title: 'Parent Title'
69
+ }
70
+ }])
71
+ })
72
+
73
+ test('shell.enter, shell.write, and shell.stop target current cwd by default', async () => {
74
+ const api = new ShellAPI()
75
+ const kernel = createKernel()
76
+ const cwd = path.join('/tmp', 'pinokio-shell-api')
77
+ const parent = { path: path.join(cwd, 'script.js') }
78
+
79
+ assert.equal(await api.enter({ cwd, parent, params: { message: 'echo hi' } }, () => {}, kernel), 'entered')
80
+ assert.equal(await api.write({ cwd, params: { message: 'typed' } }, () => {}, kernel), 'written')
81
+ await api.stop({ cwd, params: {} }, () => {}, kernel)
82
+
83
+ assert.deepEqual(kernel.calls, [{
84
+ method: 'enter',
85
+ params: {
86
+ message: 'echo hi',
87
+ id: cwd,
88
+ $parent: parent
89
+ }
90
+ }, {
91
+ method: 'write',
92
+ params: {
93
+ message: 'typed',
94
+ id: cwd
95
+ }
96
+ }, {
97
+ method: 'kill',
98
+ params: {
99
+ id: cwd
100
+ }
101
+ }])
102
+ })
103
+
104
+ test('shell.run applies deterministic defaults and forwards execution options', async () => {
105
+ const api = new ShellAPI()
106
+ const kernel = createKernel()
107
+ const cwd = path.join('/tmp', 'pinokio-shell-api')
108
+ const result = await api.run({
109
+ cwd,
110
+ parent: {
111
+ path: path.join(cwd, 'script.js')
112
+ },
113
+ params: {
114
+ message: 'echo ok'
115
+ }
116
+ }, () => {}, kernel)
117
+
118
+ assert.deepEqual(result, { stdout: 'ok' })
119
+ assert.deepEqual(kernel.calls, [{
120
+ method: 'run',
121
+ params: {
122
+ message: 'echo ok',
123
+ path: cwd,
124
+ $parent: {
125
+ path: path.join(cwd, 'script.js')
126
+ },
127
+ bluefairy: 'off'
128
+ },
129
+ options: {
130
+ cwd,
131
+ group: path.join(cwd, 'script.js')
132
+ }
133
+ }])
134
+ })