pinokiod 7.3.1 → 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.
- package/kernel/api/github/index.js +444 -0
- package/kernel/api/index.js +199 -11
- package/kernel/api/process/index.js +124 -44
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/api/uri/index.js +51 -0
- package/kernel/bin/git.js +9 -10
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/bin/zip.js +9 -1
- package/kernel/connect/providers/github/README.md +5 -4
- package/kernel/environment.js +195 -92
- package/kernel/git.js +98 -19
- package/kernel/gitconfig_template +7 -0
- package/kernel/gpu/amd.js +72 -0
- package/kernel/gpu/apple.js +8 -0
- package/kernel/gpu/common.js +12 -0
- package/kernel/gpu/intel.js +47 -0
- package/kernel/gpu/nvidia.js +8 -0
- package/kernel/index.js +11 -1
- package/kernel/managed_skills.js +871 -0
- package/kernel/plugin.js +6 -58
- package/kernel/plugin_sources.js +316 -0
- package/kernel/resource_usage/gpu.js +349 -0
- package/kernel/resource_usage/index.js +322 -0
- package/kernel/resource_usage/macos_footprint.js +197 -0
- package/kernel/resource_usage/preferences.js +92 -0
- package/kernel/resource_usage/process_tree.js +303 -0
- package/kernel/scripts/git/create +4 -4
- package/kernel/scripts/git/fork +7 -8
- package/kernel/shell.js +23 -2
- package/kernel/shells.js +41 -0
- package/kernel/sysinfo.js +62 -9
- package/kernel/util.js +60 -0
- package/package.json +1 -1
- package/server/index.js +984 -156
- package/server/lib/app_log_report.js +543 -0
- package/server/lib/content_validation.js +55 -33
- package/server/lib/launcher_instruction_bootstrap.js +4 -96
- package/server/lib/terminal_session_helpers.js +0 -3
- package/server/public/common.js +77 -31
- package/server/public/create-launcher.js +4 -32
- package/server/public/logs.js +1428 -0
- package/server/public/nav.js +7 -0
- package/server/public/plugin-detail.js +93 -10
- package/server/public/privacy_filter_worker.js +391 -0
- package/server/public/style.css +1104 -154
- package/server/public/task-launcher.js +8 -29
- package/server/public/universal-launcher.css +8 -6
- package/server/public/universal-launcher.js +3 -27
- package/server/routes/apps.js +195 -1
- package/server/views/app.ejs +3041 -717
- package/server/views/autolaunch.ejs +917 -0
- package/server/views/bootstrap.ejs +7 -1
- package/server/views/d.ejs +408 -65
- package/server/views/editor.ejs +85 -19
- package/server/views/index.ejs +661 -111
- package/server/views/init/index.ejs +1 -1
- package/server/views/install.ejs +1 -1
- package/server/views/logs.ejs +164 -86
- package/server/views/net.ejs +7 -1
- package/server/views/partials/d_terminal_column.ejs +2 -2
- package/server/views/partials/d_terminal_options.ejs +0 -8
- package/server/views/partials/fs_status.ejs +47 -0
- package/server/views/partials/home_action_modal.ejs +86 -0
- package/server/views/partials/home_run_menu.ejs +87 -0
- package/server/views/partials/main_sidebar.ejs +2 -0
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/plugin_detail.ejs +19 -4
- package/server/views/plugins.ejs +201 -3
- package/server/views/pre.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/shell.ejs +40 -18
- package/server/views/skills.ejs +506 -0
- package/server/views/terminal.ejs +45 -19
- package/spec/INSTRUCTION_SYNC.md +20 -10
- package/system/plugin/antigravity-cli/antigravity.png +0 -0
- package/system/plugin/antigravity-cli/common.js +155 -0
- package/system/plugin/antigravity-cli/install.js +272 -0
- package/system/plugin/antigravity-cli/pinokio.js +13 -0
- package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
- package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
- package/system/plugin/claude/claude.png +0 -0
- package/system/plugin/claude/pinokio.js +47 -0
- package/system/plugin/claude-auto/claude.png +0 -0
- package/system/plugin/claude-auto/pinokio.js +58 -0
- package/system/plugin/claude-desktop/icon.jpeg +0 -0
- package/system/plugin/claude-desktop/pinokio.js +23 -0
- package/system/plugin/codex/openai.webp +0 -0
- package/system/plugin/codex/pinokio.js +42 -0
- package/system/plugin/codex-auto/openai.webp +0 -0
- package/system/plugin/codex-auto/pinokio.js +49 -0
- package/system/plugin/codex-desktop/icon.png +0 -0
- package/system/plugin/codex-desktop/pinokio.js +23 -0
- package/system/plugin/crush/crush.png +0 -0
- package/system/plugin/crush/pinokio.js +15 -0
- package/system/plugin/cursor/cursor.jpeg +0 -0
- package/system/plugin/cursor/pinokio.js +23 -0
- package/system/plugin/qwen/pinokio.js +34 -0
- package/system/plugin/qwen/qwen.png +0 -0
- package/system/plugin/vscode/pinokio.js +20 -0
- package/system/plugin/vscode/vscode.png +0 -0
- package/system/plugin/windsurf/pinokio.js +23 -0
- package/system/plugin/windsurf/windsurf.png +0 -0
- package/test/antigravity-cli-plugin.test.js +185 -0
- package/test/app-api.test.js +239 -0
- package/test/app-log-report.test.js +67 -0
- package/test/environment-cache-preflight.test.js +98 -0
- package/test/git-bin.test.js +59 -0
- package/test/git-defaults.test.js +97 -0
- package/test/github-api.test.js +158 -0
- package/test/github-connection.test.js +117 -0
- package/test/huggingface-bin.test.js +25 -0
- package/test/managed-skills.test.js +351 -0
- package/test/plugin-action-functions.test.js +337 -0
- package/test/plugin-dev-iframe.test.js +17 -0
- package/test/plugin-sources.test.js +203 -0
- package/test/privacy-filter-worker-heuristics.test.js +69 -0
- package/test/process-wait.test.js +169 -0
- package/test/script-api.test.js +97 -0
- package/test/shell-api.test.js +134 -0
- package/test/shell-run-template.test.js +209 -0
- package/test/storage-api.test.js +137 -0
- package/test/uri-api.test.js +100 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const os = require("os")
|
|
4
|
+
const { execFileText, normalizePid } = require("./process_tree")
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 2000
|
|
7
|
+
const DEFAULT_TTL_MS = 15000
|
|
8
|
+
const DEFAULT_FAILURE_BACKOFF_MS = 30000
|
|
9
|
+
const FOOTPRINT_COMMAND = "/usr/bin/footprint"
|
|
10
|
+
|
|
11
|
+
function parseFootprintBytes(stdout) {
|
|
12
|
+
const text = String(stdout || "")
|
|
13
|
+
const summaryMatch = /Summary Footprint:\s+(\d+)\s+B/i.exec(text)
|
|
14
|
+
if (summaryMatch) {
|
|
15
|
+
const bytes = Number.parseInt(summaryMatch[1], 10)
|
|
16
|
+
return Number.isFinite(bytes) && bytes > 0 ? bytes : 0
|
|
17
|
+
}
|
|
18
|
+
const physicalMatch = /phys_footprint:\s+(\d+)\s+B/i.exec(text)
|
|
19
|
+
if (physicalMatch) {
|
|
20
|
+
const bytes = Number.parseInt(physicalMatch[1], 10)
|
|
21
|
+
return Number.isFinite(bytes) && bytes > 0 ? bytes : 0
|
|
22
|
+
}
|
|
23
|
+
const footprintMatch = /Footprint:\s+(\d+)\s+B/i.exec(text)
|
|
24
|
+
if (footprintMatch) {
|
|
25
|
+
const bytes = Number.parseInt(footprintMatch[1], 10)
|
|
26
|
+
return Number.isFinite(bytes) && bytes > 0 ? bytes : 0
|
|
27
|
+
}
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseFootprintPidBytes(stdout) {
|
|
32
|
+
const processes = new Map()
|
|
33
|
+
const text = String(stdout || "")
|
|
34
|
+
const pattern = /\[(\d+)\]:[^\n]*?\bFootprint:\s+(\d+)\s+B/gi
|
|
35
|
+
let match = pattern.exec(text)
|
|
36
|
+
while (match) {
|
|
37
|
+
const pid = normalizePid(match[1])
|
|
38
|
+
const bytes = Number.parseInt(match[2], 10)
|
|
39
|
+
if (pid && Number.isFinite(bytes) && bytes > 0) {
|
|
40
|
+
processes.set(pid, bytes)
|
|
41
|
+
}
|
|
42
|
+
match = pattern.exec(text)
|
|
43
|
+
}
|
|
44
|
+
return processes
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizePidList(pids) {
|
|
48
|
+
const normalized = []
|
|
49
|
+
const seen = new Set()
|
|
50
|
+
for (const value of pids || []) {
|
|
51
|
+
const pid = normalizePid(value)
|
|
52
|
+
if (!pid || seen.has(pid)) continue
|
|
53
|
+
seen.add(pid)
|
|
54
|
+
normalized.push(pid)
|
|
55
|
+
}
|
|
56
|
+
return normalized
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function errorMessage(error) {
|
|
60
|
+
return error && error.message ? error.message : String(error)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class MacFootprintSampler {
|
|
64
|
+
constructor(options = {}) {
|
|
65
|
+
this.platform = options.platform || os.platform()
|
|
66
|
+
this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS
|
|
67
|
+
this.ttlMs = options.ttlMs || DEFAULT_TTL_MS
|
|
68
|
+
this.failureBackoffMs = options.failureBackoffMs || DEFAULT_FAILURE_BACKOFF_MS
|
|
69
|
+
this.cache = new Map()
|
|
70
|
+
this.inFlight = new Map()
|
|
71
|
+
this.failures = new Map()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getFootprintByPid(pids) {
|
|
75
|
+
if (this.platform !== "darwin") {
|
|
76
|
+
return {
|
|
77
|
+
available: false,
|
|
78
|
+
stale: false,
|
|
79
|
+
bytes: 0,
|
|
80
|
+
perPid: new Map(),
|
|
81
|
+
collectedAt: Date.now(),
|
|
82
|
+
pids: [],
|
|
83
|
+
error: null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const selectedPids = normalizePidList(pids)
|
|
88
|
+
if (selectedPids.length === 0) {
|
|
89
|
+
return {
|
|
90
|
+
available: false,
|
|
91
|
+
stale: false,
|
|
92
|
+
bytes: 0,
|
|
93
|
+
perPid: new Map(),
|
|
94
|
+
collectedAt: Date.now(),
|
|
95
|
+
pids: [],
|
|
96
|
+
error: null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const key = selectedPids.join(",")
|
|
101
|
+
const now = Date.now()
|
|
102
|
+
const cached = this.cache.get(key)
|
|
103
|
+
if (cached && now - cached.collectedAt < this.ttlMs) {
|
|
104
|
+
return cached
|
|
105
|
+
}
|
|
106
|
+
const failure = this.failures.get(key)
|
|
107
|
+
if (failure && now < failure.until) {
|
|
108
|
+
if (cached) {
|
|
109
|
+
return {
|
|
110
|
+
...cached,
|
|
111
|
+
stale: true,
|
|
112
|
+
error: failure.error
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
available: false,
|
|
117
|
+
stale: true,
|
|
118
|
+
bytes: 0,
|
|
119
|
+
perPid: new Map(),
|
|
120
|
+
collectedAt: now,
|
|
121
|
+
pids: selectedPids,
|
|
122
|
+
error: failure.error
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (this.inFlight.has(key)) {
|
|
126
|
+
return this.inFlight.get(key)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const args = []
|
|
130
|
+
for (const pid of selectedPids) {
|
|
131
|
+
args.push("-pid", String(pid))
|
|
132
|
+
}
|
|
133
|
+
args.push("-f", "bytes", "--noCategories")
|
|
134
|
+
|
|
135
|
+
const run = execFileText(FOOTPRINT_COMMAND, args, {
|
|
136
|
+
timeoutMs: this.timeoutMs,
|
|
137
|
+
maxBuffer: 512 * 1024
|
|
138
|
+
}).then(({ stdout }) => {
|
|
139
|
+
const perPid = parseFootprintPidBytes(stdout)
|
|
140
|
+
let bytes = 0
|
|
141
|
+
for (const value of perPid.values()) {
|
|
142
|
+
bytes += value
|
|
143
|
+
}
|
|
144
|
+
if (bytes <= 0) {
|
|
145
|
+
bytes = parseFootprintBytes(stdout)
|
|
146
|
+
}
|
|
147
|
+
const snapshot = {
|
|
148
|
+
available: bytes > 0,
|
|
149
|
+
stale: false,
|
|
150
|
+
bytes,
|
|
151
|
+
perPid,
|
|
152
|
+
collectedAt: Date.now(),
|
|
153
|
+
pids: selectedPids,
|
|
154
|
+
error: bytes > 0 ? null : "Unable to parse footprint output"
|
|
155
|
+
}
|
|
156
|
+
if (snapshot.available) {
|
|
157
|
+
this.cache.set(key, snapshot)
|
|
158
|
+
this.failures.delete(key)
|
|
159
|
+
}
|
|
160
|
+
return snapshot
|
|
161
|
+
}).catch((error) => {
|
|
162
|
+
const message = errorMessage(error)
|
|
163
|
+
this.failures.set(key, {
|
|
164
|
+
until: Date.now() + this.failureBackoffMs,
|
|
165
|
+
error: message
|
|
166
|
+
})
|
|
167
|
+
if (cached) {
|
|
168
|
+
return {
|
|
169
|
+
...cached,
|
|
170
|
+
stale: true,
|
|
171
|
+
error: message
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
available: false,
|
|
176
|
+
stale: false,
|
|
177
|
+
bytes: 0,
|
|
178
|
+
perPid: new Map(),
|
|
179
|
+
collectedAt: Date.now(),
|
|
180
|
+
pids: selectedPids,
|
|
181
|
+
error: message
|
|
182
|
+
}
|
|
183
|
+
}).finally(() => {
|
|
184
|
+
this.inFlight.delete(key)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
this.inFlight.set(key, run)
|
|
188
|
+
return run
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
MacFootprintSampler,
|
|
195
|
+
parseFootprintBytes,
|
|
196
|
+
parseFootprintPidBytes
|
|
197
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const fs = require("fs")
|
|
4
|
+
const path = require("path")
|
|
5
|
+
const os = require("os")
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PREFERENCES = Object.freeze({
|
|
8
|
+
show_ram: true,
|
|
9
|
+
show_vram: true,
|
|
10
|
+
show_cpu: false
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
class ResourceUsagePreferences {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.kernel = options.kernel || null
|
|
16
|
+
this.writeQueue = Promise.resolve()
|
|
17
|
+
this.cache = null
|
|
18
|
+
this.cacheLoadedAt = 0
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getPreferencesPath() {
|
|
22
|
+
if (this.kernel && this.kernel.homedir && typeof this.kernel.path === "function") {
|
|
23
|
+
return this.kernel.path("cache", "resource_usage", "preferences.json")
|
|
24
|
+
}
|
|
25
|
+
const configuredHome = (
|
|
26
|
+
(this.kernel && this.kernel.store && typeof this.kernel.store.get === "function" ? this.kernel.store.get("home") : null)
|
|
27
|
+
|| process.env.PINOKIO_HOME
|
|
28
|
+
)
|
|
29
|
+
if (configuredHome) {
|
|
30
|
+
return path.resolve(configuredHome, "cache", "resource_usage", "preferences.json")
|
|
31
|
+
}
|
|
32
|
+
return path.resolve(os.homedir(), "pinokio", "cache", "resource_usage", "preferences.json")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
coerce(value = {}) {
|
|
36
|
+
const source = value && typeof value === "object" ? value : {}
|
|
37
|
+
return {
|
|
38
|
+
show_ram: Object.prototype.hasOwnProperty.call(source, "show_ram") ? Boolean(source.show_ram) : DEFAULT_PREFERENCES.show_ram,
|
|
39
|
+
show_vram: Object.prototype.hasOwnProperty.call(source, "show_vram") ? Boolean(source.show_vram) : DEFAULT_PREFERENCES.show_vram,
|
|
40
|
+
show_cpu: Object.prototype.hasOwnProperty.call(source, "show_cpu") ? Boolean(source.show_cpu) : DEFAULT_PREFERENCES.show_cpu
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async read() {
|
|
45
|
+
if (this.cache && Date.now() - this.cacheLoadedAt < 5000) {
|
|
46
|
+
return this.cache
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const raw = await fs.promises.readFile(this.getPreferencesPath(), "utf8")
|
|
50
|
+
const parsed = JSON.parse(raw)
|
|
51
|
+
this.cache = this.coerce(parsed && parsed.preferences ? parsed.preferences : parsed)
|
|
52
|
+
} catch (_) {
|
|
53
|
+
this.cache = this.coerce({})
|
|
54
|
+
}
|
|
55
|
+
this.cacheLoadedAt = Date.now()
|
|
56
|
+
return this.cache
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
withWriteLock(task) {
|
|
60
|
+
const run = this.writeQueue.then(() => task())
|
|
61
|
+
this.writeQueue = run.catch(() => {})
|
|
62
|
+
return run
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async update(updates = {}) {
|
|
66
|
+
return this.withWriteLock(async () => {
|
|
67
|
+
const current = await this.read()
|
|
68
|
+
const next = this.coerce({ ...current, ...(updates && typeof updates === "object" ? updates : {}) })
|
|
69
|
+
const preferencesPath = this.getPreferencesPath()
|
|
70
|
+
await fs.promises.mkdir(path.dirname(preferencesPath), { recursive: true })
|
|
71
|
+
const payload = {
|
|
72
|
+
updated_at: new Date().toISOString(),
|
|
73
|
+
preferences: next
|
|
74
|
+
}
|
|
75
|
+
const tmpPath = `${preferencesPath}.${process.pid}-${Date.now()}.tmp`
|
|
76
|
+
try {
|
|
77
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(payload, null, 2), "utf8")
|
|
78
|
+
await fs.promises.rename(tmpPath, preferencesPath)
|
|
79
|
+
} finally {
|
|
80
|
+
await fs.promises.rm(tmpPath, { force: true }).catch(() => {})
|
|
81
|
+
}
|
|
82
|
+
this.cache = next
|
|
83
|
+
this.cacheLoadedAt = Date.now()
|
|
84
|
+
return next
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
DEFAULT_PREFERENCES,
|
|
91
|
+
ResourceUsagePreferences
|
|
92
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const os = require("os")
|
|
4
|
+
const { execFile } = require("child_process")
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 2500
|
|
7
|
+
const DEFAULT_TTL_MS = 4000
|
|
8
|
+
const MAX_BUFFER = 8 * 1024 * 1024
|
|
9
|
+
|
|
10
|
+
function execFileText(command, args, options = {}) {
|
|
11
|
+
const timeoutMs = Math.max(250, Number(options.timeoutMs || DEFAULT_TIMEOUT_MS))
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
execFile(command, args, {
|
|
14
|
+
encoding: "utf8",
|
|
15
|
+
maxBuffer: options.maxBuffer || MAX_BUFFER,
|
|
16
|
+
timeout: timeoutMs,
|
|
17
|
+
windowsHide: true
|
|
18
|
+
}, (error, stdout, stderr) => {
|
|
19
|
+
if (error) {
|
|
20
|
+
error.stdout = stdout
|
|
21
|
+
error.stderr = stderr
|
|
22
|
+
reject(error)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
resolve({ stdout: stdout || "", stderr: stderr || "" })
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizePid(value) {
|
|
31
|
+
const pid = Number.parseInt(String(value == null ? "" : value).trim(), 10)
|
|
32
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseCpuTimeSeconds(value) {
|
|
36
|
+
const raw = String(value == null ? "" : value).trim()
|
|
37
|
+
if (!raw) return 0
|
|
38
|
+
const daySplit = raw.split("-")
|
|
39
|
+
let days = 0
|
|
40
|
+
let timePart = raw
|
|
41
|
+
if (daySplit.length === 2) {
|
|
42
|
+
days = Number.parseInt(daySplit[0], 10) || 0
|
|
43
|
+
timePart = daySplit[1]
|
|
44
|
+
}
|
|
45
|
+
const parts = timePart.split(":").map((part) => Number.parseFloat(part))
|
|
46
|
+
if (parts.some((part) => !Number.isFinite(part))) {
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
let seconds = 0
|
|
50
|
+
if (parts.length === 3) {
|
|
51
|
+
seconds = (parts[0] * 3600) + (parts[1] * 60) + parts[2]
|
|
52
|
+
} else if (parts.length === 2) {
|
|
53
|
+
seconds = (parts[0] * 60) + parts[1]
|
|
54
|
+
} else if (parts.length === 1) {
|
|
55
|
+
seconds = parts[0]
|
|
56
|
+
}
|
|
57
|
+
return (days * 86400) + seconds
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parsePsOutput(stdout) {
|
|
61
|
+
const processes = new Map()
|
|
62
|
+
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
63
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s*(.*)$/.exec(line)
|
|
64
|
+
if (!match) continue
|
|
65
|
+
const pid = normalizePid(match[1])
|
|
66
|
+
const ppid = normalizePid(match[2])
|
|
67
|
+
if (!pid) continue
|
|
68
|
+
const rssKb = Number.parseInt(match[3], 10)
|
|
69
|
+
processes.set(pid, {
|
|
70
|
+
pid,
|
|
71
|
+
ppid,
|
|
72
|
+
rssBytes: Number.isFinite(rssKb) && rssKb > 0 ? rssKb * 1024 : 0,
|
|
73
|
+
cpuSeconds: parseCpuTimeSeconds(match[4]),
|
|
74
|
+
name: match[5] || ""
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
return processes
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseWindowsProcessJson(stdout) {
|
|
81
|
+
const parsed = JSON.parse(stdout || "[]")
|
|
82
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed]
|
|
83
|
+
const processes = new Map()
|
|
84
|
+
for (const row of rows) {
|
|
85
|
+
if (!row || typeof row !== "object") continue
|
|
86
|
+
const pid = normalizePid(row.ProcessId)
|
|
87
|
+
if (!pid) continue
|
|
88
|
+
const ppid = normalizePid(row.ParentProcessId)
|
|
89
|
+
const workingSet = Number(row.WorkingSetSize)
|
|
90
|
+
const kernelTime = Number(row.KernelModeTime)
|
|
91
|
+
const userTime = Number(row.UserModeTime)
|
|
92
|
+
const cpuTicks = (Number.isFinite(kernelTime) ? kernelTime : 0) + (Number.isFinite(userTime) ? userTime : 0)
|
|
93
|
+
processes.set(pid, {
|
|
94
|
+
pid,
|
|
95
|
+
ppid,
|
|
96
|
+
rssBytes: Number.isFinite(workingSet) && workingSet > 0 ? workingSet : 0,
|
|
97
|
+
cpuSeconds: cpuTicks / 10000000,
|
|
98
|
+
name: typeof row.Name === "string" ? row.Name : ""
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
return processes
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function collectPosixProcesses(timeoutMs) {
|
|
105
|
+
const { stdout } = await execFileText("ps", ["-axo", "pid=,ppid=,rss=,time=,comm="], { timeoutMs })
|
|
106
|
+
return parsePsOutput(stdout)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function collectWindowsProcesses(timeoutMs) {
|
|
110
|
+
const script = [
|
|
111
|
+
"$ProgressPreference = 'SilentlyContinue';",
|
|
112
|
+
"Get-CimInstance Win32_Process |",
|
|
113
|
+
"Select-Object ProcessId,ParentProcessId,WorkingSetSize,KernelModeTime,UserModeTime,Name |",
|
|
114
|
+
"ConvertTo-Json -Compress"
|
|
115
|
+
].join(" ")
|
|
116
|
+
const candidates = ["powershell.exe", "powershell", "pwsh"]
|
|
117
|
+
let lastError = null
|
|
118
|
+
for (const command of candidates) {
|
|
119
|
+
try {
|
|
120
|
+
const { stdout } = await execFileText(command, [
|
|
121
|
+
"-NoProfile",
|
|
122
|
+
"-NonInteractive",
|
|
123
|
+
"-ExecutionPolicy",
|
|
124
|
+
"Bypass",
|
|
125
|
+
"-Command",
|
|
126
|
+
script
|
|
127
|
+
], { timeoutMs: Math.max(timeoutMs, 3000) })
|
|
128
|
+
return parseWindowsProcessJson(stdout)
|
|
129
|
+
} catch (error) {
|
|
130
|
+
lastError = error
|
|
131
|
+
if (error && error.code !== "ENOENT") {
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw lastError || new Error("Unable to collect Windows process list")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function collectProcessSnapshot(options = {}) {
|
|
140
|
+
const platform = options.platform || os.platform()
|
|
141
|
+
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS
|
|
142
|
+
const processes = platform === "win32"
|
|
143
|
+
? await collectWindowsProcesses(timeoutMs)
|
|
144
|
+
: await collectPosixProcesses(timeoutMs)
|
|
145
|
+
return {
|
|
146
|
+
available: true,
|
|
147
|
+
stale: false,
|
|
148
|
+
collectedAt: Date.now(),
|
|
149
|
+
processes,
|
|
150
|
+
error: null,
|
|
151
|
+
elapsedMs: null
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function annotateCpuRates(snapshot, previous, cpuCount) {
|
|
156
|
+
if (!snapshot || !previous || !snapshot.processes || !previous.processes) {
|
|
157
|
+
return snapshot
|
|
158
|
+
}
|
|
159
|
+
const elapsedMs = Math.max(0, snapshot.collectedAt - previous.collectedAt)
|
|
160
|
+
snapshot.elapsedMs = elapsedMs
|
|
161
|
+
if (elapsedMs < 250) {
|
|
162
|
+
return snapshot
|
|
163
|
+
}
|
|
164
|
+
const elapsedSeconds = elapsedMs / 1000
|
|
165
|
+
const cores = Math.max(1, cpuCount || 1)
|
|
166
|
+
for (const [pid, entry] of snapshot.processes.entries()) {
|
|
167
|
+
const prev = previous.processes.get(pid)
|
|
168
|
+
if (!prev) continue
|
|
169
|
+
const delta = entry.cpuSeconds - prev.cpuSeconds
|
|
170
|
+
if (!Number.isFinite(delta) || delta < 0) continue
|
|
171
|
+
entry.cpuPercentCores = (delta / elapsedSeconds) * 100
|
|
172
|
+
entry.cpuPercent = entry.cpuPercentCores / cores
|
|
173
|
+
}
|
|
174
|
+
return snapshot
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class ProcessSampler {
|
|
178
|
+
constructor(options = {}) {
|
|
179
|
+
this.platform = options.platform || os.platform()
|
|
180
|
+
this.timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS
|
|
181
|
+
this.ttlMs = options.ttlMs || DEFAULT_TTL_MS
|
|
182
|
+
this.cpuCount = Math.max(1, Array.isArray(os.cpus()) ? os.cpus().length : 1)
|
|
183
|
+
this.current = null
|
|
184
|
+
this.inFlight = null
|
|
185
|
+
this.failureUntil = 0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getSnapshot() {
|
|
189
|
+
const now = Date.now()
|
|
190
|
+
if (this.current && now - this.current.collectedAt < this.ttlMs) {
|
|
191
|
+
return this.current
|
|
192
|
+
}
|
|
193
|
+
if (this.inFlight) {
|
|
194
|
+
return this.inFlight
|
|
195
|
+
}
|
|
196
|
+
if (this.failureUntil && now < this.failureUntil) {
|
|
197
|
+
if (this.current) {
|
|
198
|
+
return { ...this.current, stale: true }
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
available: false,
|
|
202
|
+
stale: true,
|
|
203
|
+
collectedAt: now,
|
|
204
|
+
processes: new Map(),
|
|
205
|
+
error: "Process snapshot unavailable",
|
|
206
|
+
elapsedMs: null
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.inFlight = collectProcessSnapshot({
|
|
210
|
+
platform: this.platform,
|
|
211
|
+
timeoutMs: this.timeoutMs
|
|
212
|
+
}).then((snapshot) => {
|
|
213
|
+
annotateCpuRates(snapshot, this.current, this.cpuCount)
|
|
214
|
+
this.current = snapshot
|
|
215
|
+
this.failureUntil = 0
|
|
216
|
+
return snapshot
|
|
217
|
+
}).catch((error) => {
|
|
218
|
+
this.failureUntil = Date.now() + 15000
|
|
219
|
+
if (this.current) {
|
|
220
|
+
return {
|
|
221
|
+
...this.current,
|
|
222
|
+
stale: true,
|
|
223
|
+
error: error && error.message ? error.message : String(error)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
available: false,
|
|
228
|
+
stale: false,
|
|
229
|
+
collectedAt: Date.now(),
|
|
230
|
+
processes: new Map(),
|
|
231
|
+
error: error && error.message ? error.message : String(error),
|
|
232
|
+
elapsedMs: null
|
|
233
|
+
}
|
|
234
|
+
}).finally(() => {
|
|
235
|
+
this.inFlight = null
|
|
236
|
+
})
|
|
237
|
+
return this.inFlight
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getDescendantPids(snapshot, rootPids = []) {
|
|
242
|
+
const processes = snapshot && snapshot.processes instanceof Map ? snapshot.processes : new Map()
|
|
243
|
+
const childrenByParent = new Map()
|
|
244
|
+
for (const entry of processes.values()) {
|
|
245
|
+
if (!entry || !entry.ppid) continue
|
|
246
|
+
if (!childrenByParent.has(entry.ppid)) {
|
|
247
|
+
childrenByParent.set(entry.ppid, [])
|
|
248
|
+
}
|
|
249
|
+
childrenByParent.get(entry.ppid).push(entry.pid)
|
|
250
|
+
}
|
|
251
|
+
const visited = new Set()
|
|
252
|
+
const queue = []
|
|
253
|
+
for (const rootPid of rootPids) {
|
|
254
|
+
const pid = normalizePid(rootPid)
|
|
255
|
+
if (!pid || visited.has(pid)) continue
|
|
256
|
+
visited.add(pid)
|
|
257
|
+
queue.push(pid)
|
|
258
|
+
}
|
|
259
|
+
for (let i = 0; i < queue.length; i += 1) {
|
|
260
|
+
const pid = queue[i]
|
|
261
|
+
const children = childrenByParent.get(pid) || []
|
|
262
|
+
for (const childPid of children) {
|
|
263
|
+
if (visited.has(childPid)) continue
|
|
264
|
+
visited.add(childPid)
|
|
265
|
+
queue.push(childPid)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return visited
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function sumProcessMetrics(snapshot, pids) {
|
|
272
|
+
const processes = snapshot && snapshot.processes instanceof Map ? snapshot.processes : new Map()
|
|
273
|
+
let rssBytes = 0
|
|
274
|
+
let cpuPercent = 0
|
|
275
|
+
let cpuPercentCores = 0
|
|
276
|
+
let hasCpu = false
|
|
277
|
+
let processCount = 0
|
|
278
|
+
for (const pid of pids || []) {
|
|
279
|
+
const entry = processes.get(pid)
|
|
280
|
+
if (!entry) continue
|
|
281
|
+
processCount += 1
|
|
282
|
+
rssBytes += entry.rssBytes || 0
|
|
283
|
+
if (Number.isFinite(entry.cpuPercent)) {
|
|
284
|
+
hasCpu = true
|
|
285
|
+
cpuPercent += entry.cpuPercent
|
|
286
|
+
cpuPercentCores += Number.isFinite(entry.cpuPercentCores) ? entry.cpuPercentCores : 0
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
processCount,
|
|
291
|
+
rssBytes,
|
|
292
|
+
cpuPercent: hasCpu ? cpuPercent : null,
|
|
293
|
+
cpuPercentCores: hasCpu ? cpuPercentCores : null
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
ProcessSampler,
|
|
299
|
+
execFileText,
|
|
300
|
+
getDescendantPids,
|
|
301
|
+
normalizePid,
|
|
302
|
+
sumProcessMetrics
|
|
303
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"run": [{
|
|
3
|
-
"method": "
|
|
3
|
+
"method": "github.create",
|
|
4
4
|
"params": {
|
|
5
5
|
"path": "{{args.cwd}}",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"cwd": "{{args.cwd}}",
|
|
7
|
+
"name": "{{args.name}}",
|
|
8
|
+
"visibility": "{{args.visibility}}"
|
|
9
9
|
}
|
|
10
10
|
}]
|
|
11
11
|
}
|
package/kernel/scripts/git/fork
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"run": [{
|
|
3
3
|
"when": "{{args.org}}",
|
|
4
|
-
"method": "
|
|
4
|
+
"method": "github.fork",
|
|
5
5
|
"params": {
|
|
6
6
|
"path": "{{args.cwd}}",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
"cwd": "{{args.cwd}}",
|
|
8
|
+
"name": "{{args.name}}",
|
|
9
|
+
"org": "{{args.org}}"
|
|
10
10
|
}
|
|
11
11
|
}, {
|
|
12
12
|
"when": "{{!args.org}}",
|
|
13
|
-
"method": "
|
|
13
|
+
"method": "github.fork",
|
|
14
14
|
"params": {
|
|
15
15
|
"path": "{{args.cwd}}",
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
]
|
|
16
|
+
"cwd": "{{args.cwd}}",
|
|
17
|
+
"name": "{{args.name}}"
|
|
19
18
|
}
|
|
20
19
|
}]
|
|
21
20
|
}
|