pinokiod 7.2.6 → 7.2.8

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.
@@ -11,6 +11,7 @@ const fastq = require('fastq')
11
11
  const Loader = require("../loader")
12
12
  const Environment = require("../environment")
13
13
  const Util = require('../util')
14
+ const ShellRunTemplate = require('./shell_run_template')
14
15
 
15
16
  class Api {
16
17
  constructor(kernel) {
@@ -1010,6 +1011,7 @@ class Api {
1010
1011
  }
1011
1012
  // replace {{{ }}} with {{ }}
1012
1013
  rpc = this.kernel.template.flatten(rpc)
1014
+ rpc = ShellRunTemplate.renderEnvArgs(this.kernel, rpc, memory)
1013
1015
 
1014
1016
  // 6. rpc must have method names
1015
1017
  if (rpc.method) {
@@ -0,0 +1,273 @@
1
+ function isCmdShellName(shellName) {
2
+ const name = (shellName || '').toLowerCase()
3
+ return name.includes('cmd.exe') || name === 'cmd'
4
+ }
5
+
6
+ function isPowerShellName(shellName) {
7
+ const name = (shellName || '').toLowerCase()
8
+ return name.includes('powershell') || name.includes('pwsh')
9
+ }
10
+
11
+ const ENV_ARG_MARKER_RE = /__PINOKIO_ENVARG_(\d+)__/g
12
+
13
+ function envArgMarker(index) {
14
+ return `__PINOKIO_ENVARG_${index}__`
15
+ }
16
+
17
+ function isPinokioEnvArgKey(key) {
18
+ return /^PINOKIO_ARG_\d+$/.test(key || "")
19
+ }
20
+
21
+ function hasEnvArgMarker(value) {
22
+ ENV_ARG_MARKER_RE.lastIndex = 0
23
+ return ENV_ARG_MARKER_RE.test(String(value))
24
+ }
25
+
26
+ function quotePosixLiteral(value) {
27
+ const input = value == null ? "" : String(value)
28
+ return `'${input.split("'").join("'\"'\"'")}'`
29
+ }
30
+
31
+ function quotePowerShellComposite(value) {
32
+ const input = value == null ? "" : String(value)
33
+ ENV_ARG_MARKER_RE.lastIndex = 0
34
+ let output = '"'
35
+ let lastIndex = 0
36
+ for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
37
+ const literal = input.slice(lastIndex, match.index)
38
+ output += literal.replace(/[`"$]/g, (char) => "`" + char)
39
+ output += "${env:PINOKIO_ARG_" + match[1] + "}"
40
+ lastIndex = match.index + match[0].length
41
+ }
42
+ output += input.slice(lastIndex).replace(/[`"$]/g, (char) => "`" + char)
43
+ output += '"'
44
+ return output
45
+ }
46
+
47
+ function quoteCmdComposite(value) {
48
+ const input = value == null ? "" : String(value)
49
+ ENV_ARG_MARKER_RE.lastIndex = 0
50
+ let output = '"'
51
+ let lastIndex = 0
52
+ for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
53
+ const literal = input.slice(lastIndex, match.index)
54
+ output += literal.replace(/([()%!^"<>&|])/g, '^$1')
55
+ output += "!PINOKIO_ARG_" + match[1] + "!"
56
+ lastIndex = match.index + match[0].length
57
+ }
58
+ output += input.slice(lastIndex).replace(/([()%!^"<>&|])/g, '^$1')
59
+ output += '"'
60
+ return output
61
+ }
62
+
63
+ function quoteEnvArgComposite(value, shellName) {
64
+ const input = value == null ? "" : String(value)
65
+ if (isCmdShellName(shellName)) {
66
+ return quoteCmdComposite(input)
67
+ }
68
+ if (isPowerShellName(shellName)) {
69
+ return quotePowerShellComposite(input)
70
+ }
71
+
72
+ ENV_ARG_MARKER_RE.lastIndex = 0
73
+ const parts = []
74
+ let lastIndex = 0
75
+ for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
76
+ const literal = input.slice(lastIndex, match.index)
77
+ if (literal) {
78
+ parts.push(quotePosixLiteral(literal))
79
+ }
80
+ parts.push('"$PINOKIO_ARG_' + match[1] + '"')
81
+ lastIndex = match.index + match[0].length
82
+ }
83
+ const tail = input.slice(lastIndex)
84
+ if (tail) {
85
+ parts.push(quotePosixLiteral(tail))
86
+ }
87
+ return parts.length > 0 ? parts.join("") : "''"
88
+ }
89
+
90
+ function shellNameFor(kernel, params) {
91
+ let shellName = kernel && kernel.platform === "win32" ? "cmd.exe" : "bash"
92
+ if (params && typeof params.shell === "string" && params.shell.trim()) {
93
+ shellName = params.shell
94
+ }
95
+ return shellName
96
+ }
97
+
98
+ function isPlainObject(value) {
99
+ return value && value.constructor === Object
100
+ }
101
+
102
+ function hasMultiline(value) {
103
+ return typeof value === "string" && /[\r\n]/.test(value)
104
+ }
105
+
106
+ function isStructuredArgvMessage(value) {
107
+ return isPlainObject(value) && Array.isArray(value._)
108
+ }
109
+
110
+ function hasStructuredArgvMessage(value) {
111
+ if (isStructuredArgvMessage(value)) {
112
+ return true
113
+ }
114
+ if (Array.isArray(value)) {
115
+ return value.some((item) => isStructuredArgvMessage(item))
116
+ }
117
+ return false
118
+ }
119
+
120
+ function protectStructuredString(value, state) {
121
+ if (!hasMultiline(value)) {
122
+ return value
123
+ }
124
+ const name = `PINOKIO_ARG_${state.args.length}`
125
+ state.args.push({
126
+ name,
127
+ value: value == null ? "" : String(value)
128
+ })
129
+ return envArgMarker(state.args.length - 1)
130
+ }
131
+
132
+ function protectStructuredValue(value, state) {
133
+ if (typeof value === "string") {
134
+ return protectStructuredString(value, state)
135
+ }
136
+ if (Array.isArray(value)) {
137
+ return value.map((item) => protectStructuredValue(item, state))
138
+ }
139
+ if (isPlainObject(value)) {
140
+ const rendered = {}
141
+ for (const [key, item] of Object.entries(value)) {
142
+ rendered[key] = protectStructuredValue(item, state)
143
+ }
144
+ return rendered
145
+ }
146
+ return value
147
+ }
148
+
149
+ function protectStructuredMessage(value, state) {
150
+ if (isStructuredArgvMessage(value)) {
151
+ return protectStructuredValue(value, state)
152
+ }
153
+ if (Array.isArray(value)) {
154
+ return value.map((item) => isStructuredArgvMessage(item) ? protectStructuredValue(item, state) : item)
155
+ }
156
+ return value
157
+ }
158
+
159
+ function renderEnvArgs(kernel, rpc, memory) {
160
+ if (!rpc || rpc.method !== "shell.run" || !rpc.params || !hasStructuredArgvMessage(rpc.params.message)) {
161
+ return rpc
162
+ }
163
+
164
+ const shellName = shellNameFor(kernel, rpc.params)
165
+ const state = { args: [] }
166
+ const message = protectStructuredMessage(rpc.params.message, state)
167
+
168
+ if (state.args.length === 0) {
169
+ return rpc
170
+ }
171
+
172
+ const env = Object.assign({}, rpc.params.env || {})
173
+ for (const arg of state.args) {
174
+ env[arg.name] = arg.value
175
+ }
176
+
177
+ return {
178
+ ...rpc,
179
+ params: {
180
+ ...rpc.params,
181
+ message,
182
+ env,
183
+ _pinokio_env_args: state.args,
184
+ _pinokio_cmd_delayed_expansion: isCmdShellName(shellName)
185
+ }
186
+ }
187
+ }
188
+
189
+ function envArgDetails(value) {
190
+ const normalized = String(value == null ? "" : value)
191
+ .replace(/\r\n/g, "\n")
192
+ .replace(/\r/g, "\n")
193
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "?")
194
+ const lines = normalized.split("\n")
195
+ const previewLines = []
196
+ const maxLines = 8
197
+ const maxChars = 800
198
+ let used = 0
199
+
200
+ for (const line of lines.slice(0, maxLines)) {
201
+ const remaining = maxChars - used
202
+ if (remaining <= 0) {
203
+ break
204
+ }
205
+ if (line.length > remaining) {
206
+ previewLines.push(line.slice(0, remaining) + "...")
207
+ used = maxChars
208
+ break
209
+ }
210
+ previewLines.push(line)
211
+ used += line.length + 1
212
+ }
213
+
214
+ const preview = previewLines.join("\n")
215
+ const truncated = lines.length > previewLines.length || normalized.length > preview.length
216
+ if (truncated && previewLines[previewLines.length - 1] !== "...") {
217
+ previewLines.push("...")
218
+ }
219
+
220
+ return {
221
+ lineCount: normalized.length === 0 ? 0 : lines.length,
222
+ previewLines,
223
+ truncated
224
+ }
225
+ }
226
+
227
+ function formatEnvArgsPreview(args) {
228
+ if (!Array.isArray(args) || args.length === 0) {
229
+ return ""
230
+ }
231
+ const lines = ["\r\nPinokio shell args", ""]
232
+ for (const arg of args) {
233
+ const details = envArgDetails(arg.value)
234
+ lines.push(`${arg.name} ${details.lineCount} lines`)
235
+ for (const previewLine of details.previewLines) {
236
+ lines.push(` ${previewLine}`)
237
+ }
238
+ lines.push("")
239
+ }
240
+ return lines.join("\r\n") + "\r\n"
241
+ }
242
+
243
+ function envArgSummary(value) {
244
+ const details = envArgDetails(value)
245
+ return {
246
+ type: "pinokio env arg",
247
+ lines: details.lineCount,
248
+ preview: details.previewLines.join("\n"),
249
+ truncated: details.truncated
250
+ }
251
+ }
252
+
253
+ function redactEnvArgs(env) {
254
+ if (!env || typeof env !== "object") {
255
+ return env
256
+ }
257
+ const redacted = { ...env }
258
+ for (const key of Object.keys(redacted)) {
259
+ if (isPinokioEnvArgKey(key)) {
260
+ redacted[key] = envArgSummary(redacted[key])
261
+ }
262
+ }
263
+ return redacted
264
+ }
265
+
266
+ module.exports = {
267
+ renderEnvArgs,
268
+ quoteEnvArgComposite,
269
+ hasEnvArgMarker,
270
+ isPinokioEnvArgKey,
271
+ formatEnvArgsPreview,
272
+ redactEnvArgs
273
+ }
package/kernel/shell.js CHANGED
@@ -20,6 +20,7 @@ const { applyWindowsNodePackageManagerEnv } = require('./windows_node_package_ma
20
20
  const ShellParser = require('./shell_parser')
21
21
  const AnsiStreamTracker = require('./ansi_stream_tracker')
22
22
  const ShellStateSync = require('./shell_state_sync')
23
+ const ShellRunTemplate = require('./api/shell_run_template')
23
24
  const home = os.homedir()
24
25
 
25
26
  function normalizeComparablePath(filePath, platform) {
@@ -67,6 +68,18 @@ function stripInheritedCondaActivationState(env) {
67
68
  }
68
69
  }
69
70
 
71
+ function setDefaultEnvValue(env, key, value) {
72
+ const existingKey = Object.keys(env).find((envKey) => envKey.toLowerCase() === key.toLowerCase())
73
+ if (!existingKey) {
74
+ env[key] = value
75
+ return
76
+ }
77
+ const existingValue = env[existingKey]
78
+ if (typeof existingValue !== "string" || !existingValue.trim()) {
79
+ env[existingKey] = value
80
+ }
81
+ }
82
+
70
83
  // xterm.js currently ignores DECSYNCTERM (CSI ? 2026 h/l) and renders it as text on Windows.
71
84
  // filterDecsync() removes these sequences so they do not pollute the terminal output.
72
85
  class Shell {
@@ -281,6 +294,13 @@ class Shell {
281
294
  }
282
295
  }
283
296
 
297
+ if (this.platform === "win32") {
298
+ // Hugging Face file symlinks regularly fail on non-admin Windows setups.
299
+ // Default to no-symlink cache mode unless the user/app explicitly overrides it.
300
+ setDefaultEnvValue(this.env, "HF_HUB_DISABLE_SYMLINKS", "1")
301
+ setDefaultEnvValue(this.env, "HF_HUB_DISABLE_SYMLINKS_WARNING", "1")
302
+ }
303
+
284
304
  stripInheritedCondaActivationState(this.env)
285
305
  this.env[PATH_KEY] = stripBluefairyShimPaths(this.env[PATH_KEY], this.platform)
286
306
 
@@ -289,7 +309,7 @@ class Shell {
289
309
  delete this.env[key]
290
310
  }
291
311
  let val = this.env[key]
292
- if (/[\r\n]/.test(val)) {
312
+ if (!ShellRunTemplate.isPinokioEnvArgKey(key) && /[\r\n]/.test(val)) {
293
313
  const replaced = val.replaceAll(/[\r\n]+/g, ' ');
294
314
  this.env[key] = replaced
295
315
  // delete this.env[key]
@@ -343,6 +363,9 @@ class Shell {
343
363
  }
344
364
  quoteArgForShell(value, shellName=this.shell) {
345
365
  const input = value == null ? "" : String(value)
366
+ if (ShellRunTemplate.hasEnvArgMarker(input)) {
367
+ return ShellRunTemplate.quoteEnvArgComposite(input, shellName)
368
+ }
346
369
  if (this.isCmdShell(shellName)) {
347
370
  return `"${input.replace(/([()%!^"<>&|])/g, '^$1')}"`
348
371
  }
@@ -390,6 +413,7 @@ class Shell {
390
413
  this.userActive = false
391
414
  this.decsyncBuffer = ''
392
415
  this.stateSync.reset()
416
+ this.envArgsPreviewed = false
393
417
 
394
418
  /*
395
419
  params := {
@@ -521,6 +545,9 @@ class Shell {
521
545
  //}
522
546
  }
523
547
  }
548
+ if (params._pinokio_cmd_delayed_expansion && this.isCmdShell(this.shell) && !this.args.includes("/V:ON")) {
549
+ this.args.push("/V:ON")
550
+ }
524
551
 
525
552
  // 3. path => path can be http, relative, absolute
526
553
  this.path = params.path
@@ -738,6 +765,16 @@ class Shell {
738
765
  }
739
766
  this.stateSync.invalidate({ clearTail: true })
740
767
  }
768
+ emitEnvArgsPreview(params) {
769
+ if (!params || !Array.isArray(params._pinokio_env_args) || params._pinokio_env_args.length === 0 || this.envArgsPreviewed) {
770
+ return
771
+ }
772
+ this.envArgsPreviewed = true
773
+ const raw = ShellRunTemplate.formatEnvArgsPreview(params._pinokio_env_args)
774
+ if (raw && this.ondata) {
775
+ this.ondata({ raw })
776
+ }
777
+ }
741
778
  async run(params, cb) {
742
779
  let r = await this.request(params, cb)
743
780
  return r
@@ -1343,6 +1380,7 @@ class Shell {
1343
1380
  }
1344
1381
  async exec(params) {
1345
1382
  this.parser = new ShellParser()
1383
+ this.emitEnvArgsPreview(params)
1346
1384
  params = await this.activate(params)
1347
1385
  this.cmd = this.build(params)
1348
1386
  let res = await new Promise((resolve, reject) => {
@@ -1710,7 +1748,7 @@ class Shell {
1710
1748
  cmd: this.cmd,
1711
1749
  index: this.index,
1712
1750
  group: this.group,
1713
- env: this.env,
1751
+ env: ShellRunTemplate.redactEnvArgs(this.env),
1714
1752
  done: this.done,
1715
1753
  ready: this.ready,
1716
1754
  id: this.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.2.6",
3
+ "version": "7.2.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -28,6 +28,8 @@ const system = require('systeminformation')
28
28
  const serveIndex = require('./serveIndex')
29
29
  const registerFileRoutes = require('./routes/files')
30
30
  const registerAppRoutes = require('./routes/apps')
31
+ const registerWorkspacesRoutes = require('./routes/workspaces')
32
+ const registerDraftImportRoutes = require('./routes/draft_import')
31
33
  const Git = require("../kernel/git")
32
34
  const TerminalApi = require('../kernel/api/terminal')
33
35
 
@@ -78,6 +80,9 @@ const { createDesktopEventRouter } = require("./lib/desktop_event_router")
78
80
  const { createInjectRouter, resolveInjectList } = require("./lib/inject_router")
79
81
  const { createTaskPackageService } = require("./lib/task_packages")
80
82
  const { createTaskWorkspaceLinkService } = require("./lib/task_workspace_links")
83
+ const { createDraftService } = require("./lib/drafts")
84
+ const { createWorkspaceRuntimeService } = require("./lib/workspace_runtime")
85
+ const { createWorkspaceCatalogService } = require("./lib/workspace_catalog")
81
86
  const { createContentValidationService } = require("./lib/content_validation")
82
87
  const { buildSecureRouterDebugSnapshot, createSecureRouterDebugStore } = require("./lib/secure_router_debug")
83
88
  const AppRegistryService = require("./lib/app_registry")
@@ -8403,6 +8408,35 @@ class Server {
8403
8408
  const taskWorkspaceLinks = createTaskWorkspaceLinkService({
8404
8409
  kernel: this.kernel
8405
8410
  })
8411
+ const workspaceRuntime = createWorkspaceRuntimeService({
8412
+ kernel: this.kernel
8413
+ })
8414
+ this.workspaceRuntime = workspaceRuntime
8415
+ const drafts = createDraftService({
8416
+ kernel: this.kernel,
8417
+ taskWorkspaceLinks
8418
+ })
8419
+ this.drafts = drafts
8420
+ const workspaceCatalog = createWorkspaceCatalogService({
8421
+ kernel: this.kernel,
8422
+ workspaceRuntime,
8423
+ drafts
8424
+ })
8425
+ drafts.start().catch((error) => {
8426
+ console.warn("[drafts] failed to start", error && error.message ? error.message : error)
8427
+ })
8428
+ registerDraftImportRoutes(this.app, {
8429
+ drafts,
8430
+ defaultRegistryUrl: DEFAULT_REGISTRY_URL
8431
+ })
8432
+ registerWorkspacesRoutes(this.app, {
8433
+ workspaceCatalog,
8434
+ composePeerAccessPayload: () => this.composePeerAccessPayload(),
8435
+ getTheme: () => this.theme,
8436
+ getPeers: () => this.getPeers(),
8437
+ getCurrentHost: () => this.kernel.peer.host,
8438
+ getPortal: () => this.portal
8439
+ })
8406
8440
  const TASK_INPUT_NAME_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/
8407
8441
  const suggestTaskFolderName = async (rootDir, preferredName) => {
8408
8442
  const normalizedRoot = path.resolve(rootDir)
@@ -9836,6 +9870,9 @@ class Server {
9836
9870
  }
9837
9871
  try {
9838
9872
  await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
9873
+ drafts.trackWorkspace({ taskId: task.id, ref: workspaceRef }).catch((error) => {
9874
+ console.warn("[drafts] failed to track workspace", error && error.message ? error.message : error)
9875
+ })
9839
9876
  } catch (error) {
9840
9877
  await taskPackages.deleteTaskPackage(task.id).catch(() => {})
9841
9878
  throw error
@@ -10179,6 +10216,9 @@ class Server {
10179
10216
  }
10180
10217
  try {
10181
10218
  await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
10219
+ drafts.trackWorkspace({ taskId: task.id, ref: workspaceRef }).catch((error) => {
10220
+ console.warn("[drafts] failed to track workspace", error && error.message ? error.message : error)
10221
+ })
10182
10222
  } catch (error) {
10183
10223
  await renderTaskLaunchPage(req, res, task, {
10184
10224
  selectedTool,
@@ -10198,6 +10238,20 @@ class Server {
10198
10238
  })
10199
10239
  this.app.post("/task/start", handleTaskStartRequest)
10200
10240
 
10241
+ this.app.get("/drafts", ex(async (req, res) => {
10242
+ const cwd = typeof req.query.cwd === "string" ? req.query.cwd : ""
10243
+ const items = await drafts.listPending({ cwd })
10244
+ res.json({
10245
+ ok: true,
10246
+ items
10247
+ })
10248
+ }))
10249
+
10250
+ this.app.post("/drafts/:id/dismiss", ex(async (req, res) => {
10251
+ const ok = await drafts.dismiss(req.params.id)
10252
+ res.json({ ok })
10253
+ }))
10254
+
10201
10255
  this.app.get("/home", renderHomePage)
10202
10256
 
10203
10257
  const normalizePathForComparison = (value) => {
@@ -10594,6 +10648,9 @@ class Server {
10594
10648
  }
10595
10649
 
10596
10650
  await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
10651
+ drafts.trackWorkspace({ taskId: task.id, ref: workspaceRef }).catch((error) => {
10652
+ console.warn("[drafts] failed to track workspace", error && error.message ? error.message : error)
10653
+ })
10597
10654
 
10598
10655
  const params = new URLSearchParams()
10599
10656
  params.set("cwd", targetPath)
@@ -11851,11 +11908,14 @@ class Server {
11851
11908
  // but allow it when the request originates from the local machine
11852
11909
  if (payload.audience === 'device' && typeof payload.device_id === 'string' && payload.device_id) {
11853
11910
  try {
11854
- if (this.socket && typeof this.socket.isLocalDevice === 'function') {
11855
- payload.host = !!this.socket.isLocalDevice(payload.device_id)
11856
- } else {
11857
- payload.host = false
11858
- }
11911
+ const remoteAddress = req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : req.ip
11912
+ const isLocalRequest = this.socket && typeof this.socket.isLocalAddress === 'function'
11913
+ ? this.socket.isLocalAddress(remoteAddress)
11914
+ : false
11915
+ const isLocalDevice = this.socket && typeof this.socket.isLocalDevice === 'function'
11916
+ ? this.socket.isLocalDevice(payload.device_id)
11917
+ : false
11918
+ payload.host = !!(isLocalRequest || isLocalDevice)
11859
11919
  } catch (_) {
11860
11920
  payload.host = false
11861
11921
  }