pinokiod 7.3.14 → 7.4.0

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 (51) hide show
  1. package/kernel/api/index.js +111 -15
  2. package/kernel/api/script/index.js +10 -0
  3. package/kernel/autolaunch.js +111 -0
  4. package/kernel/environment.js +89 -1
  5. package/kernel/git.js +9 -19
  6. package/kernel/index.js +142 -43
  7. package/kernel/launch_requirements.js +1115 -0
  8. package/kernel/ready.js +231 -0
  9. package/kernel/script.js +16 -0
  10. package/kernel/shells.js +9 -1
  11. package/kernel/workspace_status.js +111 -45
  12. package/package.json +1 -1
  13. package/server/autolaunch.js +725 -0
  14. package/server/index.js +244 -411
  15. package/server/public/logs.js +15 -1
  16. package/server/public/style.css +99 -31
  17. package/server/views/app.ejs +263 -159
  18. package/server/views/autolaunch.ejs +363 -75
  19. package/server/views/index.ejs +550 -26
  20. package/server/views/logs.ejs +14 -12
  21. package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
  22. package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
  23. package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
  24. package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
  25. package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
  26. package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
  27. package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
  28. package/server/views/partials/home_action_modal.ejs +4 -1
  29. package/server/views/partials/launch_requirements_status_client.ejs +271 -0
  30. package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
  31. package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
  32. package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
  33. package/server/views/terminal.ejs +196 -2
  34. package/test/home-autolaunch-live-ui.test.js +455 -0
  35. package/test/launch-requirements-browser.test.js +579 -0
  36. package/test/launch-requirements-contract-coverage.test.js +627 -0
  37. package/test/launch-requirements-real-browser.js +625 -0
  38. package/test/launch-requirements-status-client.test.js +132 -0
  39. package/test/launch-requirements.test.js +1806 -0
  40. package/test/launch-settings-ui.test.js +370 -0
  41. package/test/ready-state.test.js +49 -0
  42. package/test/server-autolaunch.test.js +1052 -0
  43. package/test/startup-git-index-benchmark.js +409 -0
  44. package/test/startup-git-index-browser.js +320 -0
  45. package/test/startup-git-index-performance.test.js +380 -0
  46. package/test/startup-git-index-refactor.test.js +450 -0
  47. package/test/startup-git-index-route.test.js +588 -0
  48. package/test/universal-launcher.smoke.spec.js +10 -9
  49. package/test/workspace-gitignore-benchmark.js +815 -0
  50. package/test/workspace-gitignore-path-scoped.test.js +256 -0
  51. package/spec/INSTRUCTION_SYNC.md +0 -432
@@ -0,0 +1,347 @@
1
+ body.autolaunch-page .autolaunch-dependency-list {
2
+ display: grid;
3
+ gap: 8px;
4
+ }
5
+ body.autolaunch-page .autolaunch-dependency-stack {
6
+ border: 1px solid var(--task-border);
7
+ border-radius: 8px;
8
+ background: color-mix(in srgb, var(--task-panel) 98%, var(--task-soft));
9
+ overflow: hidden;
10
+ }
11
+ body.autolaunch-page .autolaunch-dependency-selected {
12
+ display: grid;
13
+ }
14
+ body.autolaunch-page .autolaunch-dependency-row {
15
+ border-bottom: 1px solid var(--task-border);
16
+ color: var(--task-text);
17
+ display: grid;
18
+ grid-template-columns: 30px minmax(0, 1fr) auto;
19
+ align-items: center;
20
+ gap: 9px;
21
+ min-height: 48px;
22
+ padding: 8px 10px;
23
+ }
24
+ body.autolaunch-page .autolaunch-dependency-row:last-child {
25
+ border-bottom: 0;
26
+ }
27
+ body.autolaunch-page .autolaunch-dependency-row img {
28
+ width: 30px;
29
+ height: 30px;
30
+ border-radius: 7px;
31
+ object-fit: cover;
32
+ }
33
+ body.autolaunch-page .autolaunch-dependency-row-empty {
34
+ min-height: 42px;
35
+ display: flex;
36
+ align-items: center;
37
+ color: var(--task-muted);
38
+ font-size: 12px;
39
+ padding: 0 10px;
40
+ }
41
+ body.autolaunch-page .autolaunch-dependency-remove {
42
+ width: 26px;
43
+ height: 26px;
44
+ border: 0;
45
+ border-radius: 6px;
46
+ background: transparent;
47
+ color: var(--task-muted);
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ padding: 0;
52
+ cursor: pointer;
53
+ }
54
+ body.autolaunch-page .autolaunch-dependency-remove:hover {
55
+ background: color-mix(in srgb, var(--task-border) 70%, transparent);
56
+ color: var(--task-text);
57
+ }
58
+ body.autolaunch-page .autolaunch-dependency-picker {
59
+ display: grid;
60
+ gap: 0;
61
+ }
62
+ body.autolaunch-page .autolaunch-dependency-trigger {
63
+ width: 100%;
64
+ min-height: 40px;
65
+ border: 0;
66
+ border-top: 1px solid var(--task-border);
67
+ border-radius: 0;
68
+ background: transparent;
69
+ color: var(--task-text);
70
+ padding: 0 10px;
71
+ display: flex;
72
+ align-items: center;
73
+ gap: 8px;
74
+ text-align: left;
75
+ font-size: 12px;
76
+ font-weight: 650;
77
+ cursor: pointer;
78
+ }
79
+ body.autolaunch-page .autolaunch-dependency-trigger:hover {
80
+ background: color-mix(in srgb, var(--task-soft) 70%, var(--task-panel));
81
+ }
82
+ body.autolaunch-page .autolaunch-dependency-trigger:disabled {
83
+ cursor: not-allowed;
84
+ opacity: 0.5;
85
+ }
86
+ body.autolaunch-page .autolaunch-dependency-trigger span {
87
+ flex: 1;
88
+ min-width: 0;
89
+ }
90
+ body.autolaunch-page .autolaunch-dependency-menu {
91
+ border-top: 1px solid var(--task-border);
92
+ background: transparent;
93
+ overflow: hidden;
94
+ }
95
+ body.autolaunch-page .autolaunch-dependency-filter {
96
+ min-height: 34px;
97
+ border-bottom: 1px solid var(--task-border);
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 8px;
101
+ padding: 0 10px;
102
+ color: var(--task-muted);
103
+ }
104
+ body.autolaunch-page .autolaunch-dependency-filter input {
105
+ width: 100%;
106
+ min-width: 0;
107
+ border: 0;
108
+ outline: none;
109
+ background: transparent;
110
+ color: var(--task-text);
111
+ font: inherit;
112
+ font-size: 12px;
113
+ }
114
+ body.autolaunch-page .autolaunch-dependency-options {
115
+ max-height: 320px;
116
+ overflow: auto;
117
+ }
118
+ body.autolaunch-page .autolaunch-dependency-option {
119
+ width: 100%;
120
+ border: 0;
121
+ border-bottom: 1px solid var(--task-border);
122
+ border-radius: 0;
123
+ background: transparent;
124
+ color: var(--task-text);
125
+ display: grid;
126
+ grid-template-columns: 28px minmax(0, 1fr);
127
+ gap: 9px;
128
+ align-items: center;
129
+ padding: 8px 10px;
130
+ text-align: left;
131
+ cursor: pointer;
132
+ }
133
+ body.autolaunch-page .autolaunch-dependency-option:last-child {
134
+ border-bottom: 0;
135
+ }
136
+ body.autolaunch-page .autolaunch-dependency-option:hover,
137
+ body.autolaunch-page .autolaunch-dependency-option:focus-visible {
138
+ background: color-mix(in srgb, var(--task-accent) 8%, transparent);
139
+ outline: none;
140
+ }
141
+ body.autolaunch-page .autolaunch-dependency-option[hidden] {
142
+ display: none;
143
+ }
144
+ body.autolaunch-page .autolaunch-dependency-option img {
145
+ width: 28px;
146
+ height: 28px;
147
+ border-radius: 6px;
148
+ object-fit: cover;
149
+ }
150
+ body.autolaunch-page .autolaunch-dependency-option-meta {
151
+ min-width: 0;
152
+ display: grid;
153
+ gap: 2px;
154
+ }
155
+ body.autolaunch-page .autolaunch-dependency-option-title {
156
+ font-size: 12px;
157
+ font-weight: 700;
158
+ overflow: hidden;
159
+ text-overflow: ellipsis;
160
+ white-space: nowrap;
161
+ }
162
+ body.autolaunch-page .autolaunch-dependency-option-path {
163
+ color: var(--task-muted);
164
+ direction: rtl;
165
+ font-size: 11px;
166
+ line-height: 1.25;
167
+ overflow: hidden;
168
+ text-align: left;
169
+ text-overflow: ellipsis;
170
+ unicode-bidi: plaintext;
171
+ white-space: nowrap;
172
+ }
173
+ body.autolaunch-page .autolaunch-dependency-picker-empty {
174
+ color: var(--task-muted);
175
+ font-size: 11px;
176
+ padding: 10px;
177
+ }
178
+ body.autolaunch-page .autolaunch-dependency-configure {
179
+ width: fit-content;
180
+ border: 1px solid color-mix(in srgb, #b45309 24%, var(--task-border));
181
+ border-radius: 6px;
182
+ background: color-mix(in srgb, #b45309 8%, var(--task-panel));
183
+ color: var(--task-text);
184
+ display: inline-flex;
185
+ align-items: center;
186
+ gap: 5px;
187
+ min-height: 24px;
188
+ padding: 0 7px;
189
+ font-size: 11px;
190
+ font-weight: 700;
191
+ cursor: pointer;
192
+ white-space: nowrap;
193
+ }
194
+ body.autolaunch-page .autolaunch-dependency-configure:disabled {
195
+ cursor: wait;
196
+ opacity: 0.6;
197
+ }
198
+ body.autolaunch-page .autolaunch-icon-button {
199
+ width: 34px;
200
+ height: 34px;
201
+ border: 1px solid var(--task-border-strong);
202
+ border-radius: 7px;
203
+ background: color-mix(in srgb, var(--task-panel) 94%, white);
204
+ color: var(--task-muted);
205
+ display: inline-flex;
206
+ align-items: center;
207
+ justify-content: center;
208
+ cursor: pointer;
209
+ }
210
+ body.dark.autolaunch-page .autolaunch-icon-button {
211
+ background: color-mix(in srgb, var(--task-panel) 92%, black);
212
+ }
213
+ body.autolaunch-page .autolaunch-icon-button:hover {
214
+ color: var(--task-text);
215
+ border-color: var(--task-border-strong);
216
+ }
217
+ body.autolaunch-page .autolaunch-icon-button:disabled {
218
+ cursor: wait;
219
+ opacity: 0.6;
220
+ }
221
+ body.autolaunch-page .autolaunch-dependency-script-modal {
222
+ position: fixed;
223
+ inset: 0;
224
+ z-index: 10000003;
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ padding: 24px;
229
+ background: rgba(2, 6, 23, 0.55);
230
+ box-sizing: border-box;
231
+ }
232
+ body.autolaunch-page .autolaunch-dependency-script-panel {
233
+ width: min(520px, calc(100vw - 32px));
234
+ max-height: min(620px, calc(100vh - 48px));
235
+ overflow: auto;
236
+ border: 1px solid var(--task-border-strong);
237
+ border-radius: 8px;
238
+ background: color-mix(in srgb, var(--task-panel) 98%, white);
239
+ color: var(--task-text);
240
+ box-shadow: 0 24px 72px rgba(2, 6, 23, 0.42);
241
+ box-sizing: border-box;
242
+ }
243
+ body.dark.autolaunch-page .autolaunch-dependency-script-panel {
244
+ background: color-mix(in srgb, var(--task-panel) 96%, black);
245
+ box-shadow: 0 28px 80px rgba(0, 0, 0, 0.64);
246
+ }
247
+ body.autolaunch-page .autolaunch-dependency-script-head {
248
+ display: flex;
249
+ align-items: flex-start;
250
+ justify-content: space-between;
251
+ gap: 12px;
252
+ padding: 16px;
253
+ border-bottom: 1px solid var(--task-border);
254
+ }
255
+ body.autolaunch-page .autolaunch-dependency-script-app {
256
+ display: grid;
257
+ grid-template-columns: 42px minmax(0, 1fr);
258
+ align-items: center;
259
+ gap: 12px;
260
+ padding: 14px 16px;
261
+ border-bottom: 1px solid var(--task-border);
262
+ }
263
+ body.autolaunch-page .autolaunch-dependency-script-app img {
264
+ width: 42px;
265
+ height: 42px;
266
+ border-radius: 8px;
267
+ object-fit: cover;
268
+ }
269
+ body.autolaunch-page .autolaunch-dependency-script-body {
270
+ padding: 14px 16px 16px;
271
+ }
272
+ body.autolaunch-page .autolaunch-dependency-script-actions {
273
+ position: sticky;
274
+ bottom: 0;
275
+ padding: 12px 16px;
276
+ border-top: 1px solid var(--task-border);
277
+ background: color-mix(in srgb, var(--task-panel) 98%, white);
278
+ }
279
+ body.dark.autolaunch-page .autolaunch-dependency-script-actions {
280
+ background: color-mix(in srgb, var(--task-panel) 96%, black);
281
+ }
282
+ body.autolaunch-page .autolaunch-primary {
283
+ appearance: none;
284
+ width: 100%;
285
+ min-height: 38px;
286
+ border: 1px solid color-mix(in srgb, var(--task-accent) 72%, var(--task-border));
287
+ border-radius: 7px;
288
+ background: var(--task-accent);
289
+ color: #fff;
290
+ display: inline-flex;
291
+ align-items: center;
292
+ justify-content: center;
293
+ gap: 8px;
294
+ padding: 0 12px;
295
+ font: inherit;
296
+ font-size: 12px;
297
+ font-weight: 800;
298
+ line-height: 1;
299
+ cursor: pointer;
300
+ white-space: nowrap;
301
+ }
302
+ body.autolaunch-page .autolaunch-primary:hover {
303
+ filter: brightness(0.95);
304
+ }
305
+ body.autolaunch-page .autolaunch-primary:focus-visible {
306
+ outline: 2px solid color-mix(in srgb, var(--task-accent) 36%, transparent);
307
+ outline-offset: 2px;
308
+ }
309
+ body.autolaunch-page .autolaunch-primary:disabled {
310
+ border-color: var(--task-border);
311
+ background: color-mix(in srgb, var(--task-soft) 80%, var(--task-panel));
312
+ color: var(--task-muted);
313
+ cursor: not-allowed;
314
+ filter: none;
315
+ }
316
+ body.autolaunch-page .autolaunch-primary-script {
317
+ max-width: 180px;
318
+ overflow: hidden;
319
+ text-overflow: ellipsis;
320
+ padding: 3px 6px;
321
+ border-radius: 5px;
322
+ background: rgba(255, 255, 255, 0.2);
323
+ font-weight: 800;
324
+ }
325
+ body.autolaunch-page .autolaunch-primary:disabled .autolaunch-primary-script {
326
+ background: transparent;
327
+ }
328
+ body.autolaunch-page .autolaunch-manual {
329
+ padding: 16px;
330
+ border-top: 1px solid var(--task-border);
331
+ }
332
+ body.autolaunch-page .autolaunch-manual-row {
333
+ display: flex;
334
+ align-items: center;
335
+ gap: 8px;
336
+ }
337
+ body.autolaunch-page .autolaunch-manual input {
338
+ flex: 1 1 auto;
339
+ min-height: 32px;
340
+ padding: 0 10px;
341
+ border: 1px solid var(--task-border-strong);
342
+ border-radius: 7px;
343
+ background: color-mix(in srgb, var(--task-panel) 98%, white);
344
+ }
345
+ body.dark.autolaunch-page .autolaunch-manual input {
346
+ background: color-mix(in srgb, var(--task-panel) 95%, black);
347
+ }
@@ -11,7 +11,10 @@
11
11
  <div class='home-actions-heading'>
12
12
  <div class='home-actions-title-row'>
13
13
  <h2 id='<%= id %>-title'><%= item.name %></h2>
14
- <% if (item.running) { %>
14
+ <% if (item.autolaunch_starting) { %>
15
+ <% const homeActionsAutolaunchLabel = item.autolaunch_status_label || (item.autolaunch_blocked ? "Blocked" : item.autolaunch_waiting ? "Waiting" : "Starting") %>
16
+ <span class='home-actions-status home-actions-status--starting' title='<%= homeActionsAutolaunchLabel %>'><%= homeActionsAutolaunchLabel %></span>
17
+ <% } else if (item.running) { %>
15
18
  <span class='home-actions-status'>Running</span>
16
19
  <% } %>
17
20
  </div>
@@ -0,0 +1,271 @@
1
+ ;(function() {
2
+ const root = document.querySelector("[data-launch-requirements-status]")
3
+ const appId = <%- JSON.stringify((typeof name !== "undefined" && name) ? name : "") %>
4
+ const appTitle = <%- JSON.stringify((typeof config !== "undefined" && config && config.title) || (typeof name !== "undefined" && name) || "app") %>
5
+ const initialEnabled = <%- JSON.stringify(!!(typeof launch_requirements_status_enabled !== "undefined" && launch_requirements_status_enabled)) %>
6
+ if (!root || !appId) return
7
+ const shouldDeferToParentLaunchRequirements = () => {
8
+ try {
9
+ if (window.self === window.top) return false
10
+ const parentDocument = window.parent && window.parent.document
11
+ return !!(parentDocument && parentDocument.querySelector("[data-launch-requirements-status]"))
12
+ } catch (_) {
13
+ return false
14
+ }
15
+ }
16
+ if (shouldDeferToParentLaunchRequirements()) return
17
+ let cancelling = false
18
+ const escapeHtml = (value) => {
19
+ if (value === null || value === undefined) return ""
20
+ return String(value).replace(/[&<>"']/g, (match) => {
21
+ switch (match) {
22
+ case "&": return "&amp;"
23
+ case "<": return "&lt;"
24
+ case ">": return "&gt;"
25
+ case "\"": return "&quot;"
26
+ case "'": return "&#39;"
27
+ default: return match
28
+ }
29
+ })
30
+ }
31
+ const stepLabel = (row) => {
32
+ const current = Number(row && row.step_current)
33
+ const total = Number(row && row.step_total)
34
+ return Number.isInteger(current) && Number.isInteger(total) && total > 1 ? ` (${current}/${total})` : ""
35
+ }
36
+ const titleForId = (id, titleById) => {
37
+ if (!id) return ""
38
+ return titleById[id] || id
39
+ }
40
+ const waitingForLabel = (row, titleById) => {
41
+ const waitingFor = row && Array.isArray(row.waiting_for) ? row.waiting_for : []
42
+ const names = waitingFor
43
+ .map((id) => titleForId(id, titleById))
44
+ .filter(Boolean)
45
+ return names.length > 0 ? `Waiting for ${names.join(", ")}` : "Waiting"
46
+ }
47
+ const stateLabel = (row, titleById) => {
48
+ const script = row && row.script ? row.script : "script"
49
+ if (row && row.state === "ready") return "Ready"
50
+ if (row && row.state === "blocked") return row.blocked_reason || "Needs setup"
51
+ if (row && row.state === "waiting") return waitingForLabel(row, titleById)
52
+ if (row && row.state === "pending") return "Pending"
53
+ return `Starting ${script}${stepLabel(row)}`
54
+ }
55
+ const isActiveRow = (row) => {
56
+ if (!row) return false
57
+ return row.state === "starting"
58
+ }
59
+ const isReadyRow = (row) => {
60
+ if (!row) return false
61
+ return row.state === "ready"
62
+ }
63
+ const stateClass = (row) => {
64
+ if (isActiveRow(row)) return " is-active"
65
+ if (isReadyRow(row)) return " is-ready"
66
+ return ""
67
+ }
68
+ const stateIcon = (row) => {
69
+ if (isActiveRow(row)) {
70
+ return '<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i>'
71
+ }
72
+ if (isReadyRow(row)) {
73
+ return '<i class="fa-solid fa-check" aria-hidden="true"></i>'
74
+ }
75
+ return ""
76
+ }
77
+ const render = (status) => {
78
+ if (!status) {
79
+ root.hidden = true
80
+ root.innerHTML = ""
81
+ return
82
+ }
83
+ const rawRequirements = Array.isArray(status.requirements) ? status.requirements : []
84
+ const requirements = rawRequirements
85
+ if (requirements.length === 0 && status.state !== "blocked") {
86
+ root.hidden = true
87
+ root.innerHTML = ""
88
+ return
89
+ }
90
+ const waiting = requirements.filter((row) => row && row.state !== "ready")
91
+ if (waiting.length === 0 && status.state !== "blocked") {
92
+ root.hidden = true
93
+ root.innerHTML = ""
94
+ return
95
+ }
96
+ const title = status.state === "blocked" ? "Launch needs setup" : "Preparing required apps"
97
+ const detail = status.state === "blocked" && status.blocked_reason
98
+ ? status.blocked_reason
99
+ : waiting.length > 0
100
+ ? `Waiting for ${waiting.map((row) => row.title || row.id).join(", ")}.`
101
+ : (status.blocked_reason || `${status.title || appTitle} will start after its requirements are ready.`)
102
+ const titleById = requirements.reduce((acc, row) => {
103
+ if (row && row.id) {
104
+ acc[row.id] = row.title || row.id
105
+ }
106
+ return acc
107
+ }, {})
108
+ const canCancel = true
109
+ root.innerHTML = `
110
+ <div class="launch-requirements-card">
111
+ <div class="launch-requirements-title">
112
+ <span>${escapeHtml(title)}</span>
113
+ </div>
114
+ <div class="launch-requirements-detail">${escapeHtml(detail)}</div>
115
+ ${requirements.length > 0 ? `
116
+ <div class="launch-requirements-list">
117
+ ${requirements.map((row) => `
118
+ <div class="launch-requirements-row">
119
+ <img src="${escapeHtml(row.icon || "/pinokio-black.png")}" alt="" onerror="this.onerror=null; this.src='/pinokio-black.png'">
120
+ <div class="launch-requirements-name">${escapeHtml(row.title || row.id)}</div>
121
+ <div class="launch-requirements-state${stateClass(row)}">
122
+ ${stateIcon(row)}
123
+ <span>${escapeHtml(stateLabel(row, titleById))}</span>
124
+ ${row && row.state === "blocked" ? `
125
+ <button type="button" class="launch-requirements-choose-script" data-launch-requirements-choose-script="${escapeHtml(row.id || "")}">
126
+ <i class="fa-solid fa-file-code" aria-hidden="true"></i>
127
+ <span>Choose script</span>
128
+ </button>
129
+ ` : ""}
130
+ </div>
131
+ </div>
132
+ `).join("")}
133
+ </div>
134
+ ` : ""}
135
+ ${canCancel ? `
136
+ <div class="launch-requirements-actions">
137
+ <button type="button" class="launch-requirements-stop" data-launch-requirements-stop ${cancelling ? "disabled" : ""}>
138
+ <i class="fa-solid ${cancelling ? "fa-circle-notch fa-spin" : "fa-stop"}" aria-hidden="true"></i>
139
+ <span>${cancelling ? "Stopping launch..." : "Stop launch"}</span>
140
+ </button>
141
+ </div>
142
+ ` : ""}
143
+ </div>
144
+ `
145
+ root.hidden = false
146
+ }
147
+ const notifyLaunchRequirementsControl = (detail = {}) => {
148
+ const payload = {
149
+ type: "pinokio:launch-requirements-control",
150
+ detail
151
+ }
152
+ try {
153
+ window.dispatchEvent(new CustomEvent(payload.type, { detail }))
154
+ } catch (_) {}
155
+ try {
156
+ for (let i = 0; i < window.frames.length; i++) {
157
+ window.frames[i].postMessage(payload, "*")
158
+ }
159
+ } catch (_) {}
160
+ }
161
+ const cancelLaunch = async () => {
162
+ if (cancelling) return
163
+ cancelling = true
164
+ const button = root.querySelector("[data-launch-requirements-stop]")
165
+ if (button) {
166
+ button.disabled = true
167
+ button.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i><span>Stopping launch...</span>'
168
+ }
169
+ try {
170
+ const response = await fetch(`/pinokio/launch-requirements/${encodeURIComponent(appId)}/cancel`, {
171
+ method: "POST",
172
+ headers: { "Accept": "application/json" },
173
+ cache: "no-store"
174
+ })
175
+ const data = await response.json()
176
+ if (!response.ok || !data || !data.ok) {
177
+ throw new Error(data && data.error ? data.error : "Failed to stop launch.")
178
+ }
179
+ notifyLaunchRequirementsControl({
180
+ action: "cancelled",
181
+ app_id: appId
182
+ })
183
+ cancelling = false
184
+ render(null)
185
+ try {
186
+ if (typeof refresh === "function") {
187
+ refresh(true, { nodelay: true })
188
+ }
189
+ } catch (_) {}
190
+ } catch (_) {
191
+ cancelling = false
192
+ fetchInitialStatus()
193
+ }
194
+ }
195
+ root.addEventListener("click", (event) => {
196
+ const chooseScript = event.target && event.target.closest("[data-launch-requirements-choose-script]")
197
+ if (chooseScript) {
198
+ event.preventDefault()
199
+ const dependencyApp = chooseScript.getAttribute("data-launch-requirements-choose-script") || ""
200
+ if (!dependencyApp) return
201
+ const chooseEvent = new CustomEvent("pinokio:launch-requirements-choose-script", {
202
+ cancelable: true,
203
+ detail: {
204
+ app: dependencyApp
205
+ }
206
+ })
207
+ window.dispatchEvent(chooseEvent)
208
+ if (!chooseEvent.defaultPrevented) {
209
+ window.location.href = `/v/${encodeURIComponent(dependencyApp)}?autolaunch=1`
210
+ }
211
+ return
212
+ }
213
+ const stop = event.target && event.target.closest("[data-launch-requirements-stop]")
214
+ if (!stop) return
215
+ event.preventDefault()
216
+ cancelLaunch()
217
+ })
218
+ const renderStatusPayload = (data) => {
219
+ const status = data && data.ok ? data.status : null
220
+ if (status) {
221
+ cancelling = false
222
+ render(status)
223
+ return
224
+ }
225
+ render(null)
226
+ }
227
+ const fetchInitialStatus = async () => {
228
+ try {
229
+ const response = await fetch(`/pinokio/launch-requirements/${encodeURIComponent(appId)}`, {
230
+ headers: { "Accept": "application/json" },
231
+ cache: "no-store"
232
+ })
233
+ const data = await response.json()
234
+ renderStatusPayload(response.ok && data ? data : null)
235
+ } catch (_) {
236
+ renderStatusPayload(null)
237
+ }
238
+ }
239
+ const startStatusClient = () => {
240
+ if (root.dataset.launchRequirementsClient === "started") return
241
+ root.dataset.launchRequirementsClient = "started"
242
+ fetchInitialStatus()
243
+ const channel = `kernel.launch_requirements:${appId}`
244
+ let socket = null
245
+ try {
246
+ socket = new Socket()
247
+ socket.run({
248
+ method: channel,
249
+ mode: "listen"
250
+ }, (packet) => {
251
+ if (!packet || packet.type !== "launch.requirements") {
252
+ return
253
+ }
254
+ renderStatusPayload({
255
+ ok: true,
256
+ status: packet.data ? packet.data.status : null
257
+ })
258
+ }).catch(() => {})
259
+ window.addEventListener("beforeunload", () => {
260
+ if (socket && typeof socket.close === "function") {
261
+ socket.close()
262
+ }
263
+ }, { once: true })
264
+ } catch (_) {}
265
+ }
266
+ if (initialEnabled) {
267
+ startStatusClient()
268
+ } else {
269
+ window.addEventListener("pinokio:launch-requirements-configured", startStatusClient, { once: true })
270
+ }
271
+ })()