@talex-touch/utils 1.0.40 → 1.0.44
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/.eslintcache +1 -0
- package/__tests__/cloud-sync-sdk.test.ts +442 -0
- package/__tests__/icons/icons.test.ts +84 -0
- package/__tests__/plugin-sdk-lifecycle.test.ts +130 -0
- package/__tests__/power-sdk.test.ts +143 -0
- package/__tests__/preset-export-types.test.ts +108 -0
- package/__tests__/search/fuzzy-match.test.ts +137 -0
- package/__tests__/transport/port-policy.test.ts +44 -0
- package/__tests__/transport-domain-sdks.test.ts +152 -0
- package/__tests__/types/update.test.ts +67 -0
- package/account/account-sdk.ts +915 -0
- package/account/index.ts +2 -0
- package/account/types.ts +321 -0
- package/analytics/client.ts +136 -0
- package/analytics/index.ts +2 -0
- package/analytics/types.ts +156 -0
- package/animation/auto-resize.ts +322 -0
- package/animation/window-node.ts +26 -19
- package/auth/clerk-types.ts +12 -30
- package/auth/index.ts +0 -2
- package/auth/useAuthState.ts +6 -14
- package/base/index.ts +2 -0
- package/base/log-level.ts +105 -0
- package/channel/index.ts +170 -69
- package/cloud-sync/cloud-sync-sdk.ts +450 -0
- package/cloud-sync/index.ts +1 -0
- package/common/file-scan-utils.ts +17 -9
- package/common/index.ts +4 -0
- package/common/logger/index.ts +46 -0
- package/common/logger/logger-manager.ts +303 -0
- package/common/logger/module-logger.ts +270 -0
- package/common/logger/transport-logger.ts +234 -0
- package/common/logger/types.ts +93 -0
- package/common/search/gather.ts +48 -6
- package/common/search/index.ts +8 -0
- package/common/storage/constants.ts +13 -0
- package/common/storage/entity/app-settings.ts +245 -0
- package/common/storage/entity/index.ts +3 -0
- package/common/storage/entity/layout-atom-types.ts +147 -0
- package/common/storage/entity/openers.ts +1 -0
- package/common/storage/entity/preset-cloud-api.ts +132 -0
- package/common/storage/entity/preset-export-types.ts +256 -0
- package/common/storage/entity/shortcut-settings.ts +1 -0
- package/common/storage/shortcut-storage.ts +11 -0
- package/common/utils/clone-diagnostics.ts +105 -0
- package/common/utils/file.ts +16 -8
- package/common/utils/index.ts +6 -2
- package/common/utils/payload-preview.ts +173 -0
- package/common/utils/polling.ts +167 -13
- package/common/utils/safe-path.ts +103 -0
- package/common/utils/safe-shell.ts +115 -0
- package/common/utils/task-queue.ts +4 -1
- package/core-box/builder/tuff-builder.ts +0 -1
- package/core-box/index.ts +1 -1
- package/core-box/recommendation.ts +38 -1
- package/core-box/tuff/tuff-dsl.ts +97 -0
- package/electron/download-manager.ts +10 -7
- package/electron/env-tool.ts +42 -40
- package/electron/index.ts +0 -1
- package/env/index.ts +156 -0
- package/eslint.config.js +55 -0
- package/i18n/index.ts +62 -0
- package/i18n/locales/en.json +226 -0
- package/i18n/locales/zh.json +226 -0
- package/i18n/message-keys.ts +236 -0
- package/i18n/resolver.ts +181 -0
- package/icons/index.ts +257 -0
- package/icons/svg.ts +69 -0
- package/index.ts +9 -1
- package/intelligence/client.ts +72 -42
- package/market/constants.ts +21 -3
- package/market/index.ts +1 -1
- package/market/types.ts +20 -5
- package/package.json +15 -5
- package/permission/index.ts +143 -46
- package/permission/legacy.ts +26 -0
- package/permission/registry.ts +304 -0
- package/permission/types.ts +164 -0
- package/plugin/channel.ts +68 -39
- package/plugin/index.ts +82 -8
- package/plugin/install.ts +3 -0
- package/plugin/log/types.ts +22 -5
- package/plugin/node/logger-manager.ts +11 -3
- package/plugin/node/logger.ts +24 -17
- package/plugin/preload.ts +25 -2
- package/plugin/providers/index.ts +4 -0
- package/plugin/providers/market-client.ts +218 -0
- package/plugin/providers/npm-provider.ts +228 -0
- package/plugin/providers/tpex-provider.ts +297 -0
- package/plugin/providers/tpex-types.ts +34 -0
- package/plugin/sdk/box-items.ts +14 -0
- package/plugin/sdk/box-sdk.ts +64 -0
- package/plugin/sdk/channel.ts +119 -4
- package/plugin/sdk/clipboard.ts +26 -12
- package/plugin/sdk/cloud-sync.ts +113 -0
- package/plugin/sdk/common.ts +19 -11
- package/plugin/sdk/core-box.ts +6 -15
- package/plugin/sdk/division-box.ts +160 -65
- package/plugin/sdk/examples/storage-onDidChange-example.js +5 -2
- package/plugin/sdk/feature-sdk.ts +111 -76
- package/plugin/sdk/flow.ts +146 -45
- package/plugin/sdk/hooks/bridge.ts +113 -49
- package/plugin/sdk/hooks/life-cycle.ts +35 -16
- package/plugin/sdk/index.ts +14 -3
- package/plugin/sdk/intelligence.ts +87 -0
- package/plugin/sdk/meta/README.md +179 -0
- package/plugin/sdk/meta-sdk.ts +244 -0
- package/plugin/sdk/notification.ts +9 -0
- package/plugin/sdk/performance.ts +1 -16
- package/plugin/sdk/plugin-info.ts +64 -0
- package/plugin/sdk/power.ts +155 -0
- package/plugin/sdk/recommend.ts +21 -0
- package/plugin/sdk/service/index.ts +12 -8
- package/plugin/sdk/sqlite.ts +141 -0
- package/plugin/sdk/storage.ts +2 -6
- package/plugin/sdk/system.ts +2 -9
- package/plugin/sdk/temp-files.ts +41 -0
- package/plugin/sdk/touch-sdk.ts +18 -0
- package/plugin/sdk/types.ts +44 -4
- package/plugin/sdk/window/index.ts +12 -9
- package/plugin/sdk-version.ts +231 -0
- package/preload/renderer.ts +3 -2
- package/renderer/hooks/arg-mapper.ts +34 -6
- package/renderer/hooks/index.ts +13 -0
- package/renderer/hooks/initialize.ts +2 -1
- package/renderer/hooks/use-agent-market-sdk.ts +7 -0
- package/renderer/hooks/use-agent-market.ts +106 -0
- package/renderer/hooks/use-agents-sdk.ts +7 -0
- package/renderer/hooks/use-app-sdk.ts +7 -0
- package/renderer/hooks/use-channel.ts +33 -4
- package/renderer/hooks/use-download-sdk.ts +21 -0
- package/renderer/hooks/use-intelligence-sdk.ts +7 -0
- package/renderer/hooks/use-intelligence-stats.ts +290 -0
- package/renderer/hooks/use-intelligence.ts +202 -104
- package/renderer/hooks/use-market-sdk.ts +16 -0
- package/renderer/hooks/use-notification-sdk.ts +7 -0
- package/renderer/hooks/use-permission-sdk.ts +7 -0
- package/renderer/hooks/use-permission.ts +325 -0
- package/renderer/hooks/use-platform-sdk.ts +7 -0
- package/renderer/hooks/use-plugin-sdk.ts +16 -0
- package/renderer/hooks/use-settings-sdk.ts +7 -0
- package/renderer/hooks/use-update-sdk.ts +21 -0
- package/renderer/index.ts +1 -0
- package/renderer/ref.ts +19 -10
- package/renderer/shared/components/SharedPluginDetailContent.vue +84 -0
- package/renderer/shared/components/SharedPluginDetailHeader.vue +116 -0
- package/renderer/shared/components/SharedPluginDetailMetaList.vue +39 -0
- package/renderer/shared/components/SharedPluginDetailReadme.vue +45 -0
- package/renderer/shared/components/SharedPluginDetailVersions.vue +98 -0
- package/renderer/shared/components/index.ts +5 -0
- package/renderer/shared/components/shims-vue.d.ts +5 -0
- package/renderer/shared/index.ts +2 -0
- package/renderer/shared/plugin-detail.ts +62 -0
- package/renderer/storage/app-settings.ts +3 -1
- package/renderer/storage/base-storage.ts +508 -82
- package/renderer/storage/intelligence-storage.ts +37 -46
- package/renderer/storage/openers.ts +3 -1
- package/renderer/storage/storage-subscription.ts +126 -42
- package/renderer/touch-sdk/env.ts +10 -10
- package/renderer/touch-sdk/index.ts +114 -18
- package/renderer/touch-sdk/terminal.ts +24 -13
- package/search/feature-matcher.ts +279 -0
- package/search/fuzzy-match.ts +64 -34
- package/search/index.ts +10 -0
- package/search/levenshtein-utils.ts +17 -11
- package/transport/errors.ts +310 -0
- package/transport/event/builder.ts +378 -0
- package/transport/event/index.ts +7 -0
- package/transport/event/types.ts +292 -0
- package/transport/events/index.ts +2670 -0
- package/transport/events/meta-overlay.ts +79 -0
- package/transport/events/types/agents.ts +177 -0
- package/transport/events/types/app-index.ts +9 -0
- package/transport/events/types/app.ts +475 -0
- package/transport/events/types/box-item.ts +222 -0
- package/transport/events/types/clipboard.ts +80 -0
- package/transport/events/types/core-box.ts +534 -0
- package/transport/events/types/device-idle.ts +7 -0
- package/transport/events/types/division-box.ts +99 -0
- package/transport/events/types/download.ts +115 -0
- package/transport/events/types/file-index.ts +73 -0
- package/transport/events/types/flow.ts +149 -0
- package/transport/events/types/index.ts +70 -0
- package/transport/events/types/market.ts +39 -0
- package/transport/events/types/meta-overlay.ts +184 -0
- package/transport/events/types/notification.ts +140 -0
- package/transport/events/types/permission.ts +90 -0
- package/transport/events/types/platform.ts +8 -0
- package/transport/events/types/plugin.ts +620 -0
- package/transport/events/types/sentry.ts +20 -0
- package/transport/events/types/storage.ts +208 -0
- package/transport/events/types/transport.ts +60 -0
- package/transport/events/types/tray.ts +16 -0
- package/transport/events/types/update.ts +78 -0
- package/transport/index.ts +139 -0
- package/transport/main.ts +2 -0
- package/transport/sdk/constants.ts +29 -0
- package/transport/sdk/domains/agents-market.ts +47 -0
- package/transport/sdk/domains/agents.ts +62 -0
- package/transport/sdk/domains/app.ts +48 -0
- package/transport/sdk/domains/disposable.ts +35 -0
- package/transport/sdk/domains/download.ts +139 -0
- package/transport/sdk/domains/index.ts +13 -0
- package/transport/sdk/domains/intelligence.ts +616 -0
- package/transport/sdk/domains/market.ts +35 -0
- package/transport/sdk/domains/notification.ts +62 -0
- package/transport/sdk/domains/permission.ts +85 -0
- package/transport/sdk/domains/platform.ts +19 -0
- package/transport/sdk/domains/plugin.ts +144 -0
- package/transport/sdk/domains/settings.ts +92 -0
- package/transport/sdk/domains/update.ts +64 -0
- package/transport/sdk/index.ts +60 -0
- package/transport/sdk/main-transport.ts +710 -0
- package/transport/sdk/main.ts +9 -0
- package/transport/sdk/plugin-transport.ts +654 -0
- package/transport/sdk/port-policy.ts +38 -0
- package/transport/sdk/renderer-transport.ts +1165 -0
- package/transport/types.ts +605 -0
- package/types/agent.ts +399 -0
- package/types/cloud-sync.ts +157 -0
- package/types/division-box.ts +47 -27
- package/types/download.ts +1 -0
- package/types/flow.ts +63 -12
- package/types/icon.ts +2 -1
- package/types/index.ts +5 -0
- package/types/intelligence.ts +1492 -81
- package/types/modules/base.ts +2 -0
- package/types/path-browserify.d.ts +5 -0
- package/types/platform.ts +12 -0
- package/types/startup-info.ts +32 -0
- package/types/touch-app-core.ts +8 -8
- package/types/update.ts +94 -1
- package/vitest.config.ts +25 -0
- package/auth/useClerkConfig.ts +0 -40
- package/auth/useClerkProvider.ts +0 -52
package/common/utils/polling.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
/**
|
|
3
2
|
* @module polling
|
|
4
3
|
* A high-precision, efficient, singleton polling service for scheduling periodic tasks.
|
|
@@ -13,7 +12,16 @@ interface PollingTask {
|
|
|
13
12
|
nextRunMs: number
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
type
|
|
15
|
+
type PollingTaskStats = {
|
|
16
|
+
count: number
|
|
17
|
+
lastStartAt: number
|
|
18
|
+
lastEndAt: number
|
|
19
|
+
lastDurationMs: number
|
|
20
|
+
maxDurationMs: number
|
|
21
|
+
lastMeta?: Record<string, unknown>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type TimeUnit = 'milliseconds' | 'seconds' | 'minutes' | 'hours'
|
|
17
25
|
|
|
18
26
|
export class PollingService {
|
|
19
27
|
private static instance: PollingService
|
|
@@ -21,6 +29,9 @@ export class PollingService {
|
|
|
21
29
|
private timerId: NodeJS.Timeout | null = null
|
|
22
30
|
private isRunning = false
|
|
23
31
|
private quitListenerCleanup?: () => void
|
|
32
|
+
private activeTasks = new Map<string, number>()
|
|
33
|
+
private startAttempts = new Map<string, { count: number, lastAt: number }>()
|
|
34
|
+
private taskStats = new Map<string, PollingTaskStats>()
|
|
24
35
|
|
|
25
36
|
private constructor() {
|
|
26
37
|
// Private constructor to enforce singleton pattern
|
|
@@ -35,6 +46,8 @@ export class PollingService {
|
|
|
35
46
|
|
|
36
47
|
private convertToMs(interval: number, unit: TimeUnit): number {
|
|
37
48
|
switch (unit) {
|
|
49
|
+
case 'milliseconds':
|
|
50
|
+
return interval
|
|
38
51
|
case 'seconds':
|
|
39
52
|
return interval * 1000
|
|
40
53
|
case 'minutes':
|
|
@@ -55,10 +68,17 @@ export class PollingService {
|
|
|
55
68
|
public register(
|
|
56
69
|
id: string,
|
|
57
70
|
callback: () => void | Promise<void>,
|
|
58
|
-
options: {
|
|
71
|
+
options: {
|
|
72
|
+
interval: number
|
|
73
|
+
unit: TimeUnit
|
|
74
|
+
runImmediately?: boolean
|
|
75
|
+
initialDelayMs?: number
|
|
76
|
+
},
|
|
59
77
|
): void {
|
|
60
78
|
if (this.tasks.has(id)) {
|
|
61
|
-
|
|
79
|
+
if (this.shouldVerboseLog()) {
|
|
80
|
+
console.warn(`[PollingService] Task with ID '${id}' is already registered. Overwriting.`)
|
|
81
|
+
}
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
const intervalMs = this.convertToMs(options.interval, options.unit)
|
|
@@ -67,7 +87,12 @@ export class PollingService {
|
|
|
67
87
|
return
|
|
68
88
|
}
|
|
69
89
|
|
|
70
|
-
const
|
|
90
|
+
const now = Date.now()
|
|
91
|
+
const nextRunMs = options.runImmediately
|
|
92
|
+
? now
|
|
93
|
+
: typeof options.initialDelayMs === 'number'
|
|
94
|
+
? now + Math.max(0, options.initialDelayMs)
|
|
95
|
+
: now + intervalMs
|
|
71
96
|
|
|
72
97
|
this.tasks.set(id, {
|
|
73
98
|
id,
|
|
@@ -76,26 +101,45 @@ export class PollingService {
|
|
|
76
101
|
nextRunMs,
|
|
77
102
|
})
|
|
78
103
|
|
|
79
|
-
|
|
104
|
+
if (this.shouldVerboseLog()) {
|
|
105
|
+
console.debug(`[PollingService] Task '${id}' registered to run every ${options.interval} ${options.unit}.`)
|
|
106
|
+
}
|
|
80
107
|
|
|
81
108
|
if (this.isRunning) {
|
|
82
109
|
this._reschedule()
|
|
83
110
|
}
|
|
84
111
|
}
|
|
85
112
|
|
|
113
|
+
public setTaskMeta(id: string, meta: Record<string, unknown> | null): void {
|
|
114
|
+
if (!meta) return
|
|
115
|
+
const stats = this.taskStats.get(id) ?? {
|
|
116
|
+
count: 0,
|
|
117
|
+
lastStartAt: 0,
|
|
118
|
+
lastEndAt: 0,
|
|
119
|
+
lastDurationMs: 0,
|
|
120
|
+
maxDurationMs: 0,
|
|
121
|
+
}
|
|
122
|
+
stats.lastMeta = meta
|
|
123
|
+
this.taskStats.set(id, stats)
|
|
124
|
+
}
|
|
125
|
+
|
|
86
126
|
/**
|
|
87
127
|
* Unregisters a task, preventing it from being executed in the future.
|
|
88
128
|
* @param id - The unique identifier of the task to remove.
|
|
89
129
|
*/
|
|
90
130
|
public unregister(id: string): void {
|
|
91
131
|
if (this.tasks.delete(id)) {
|
|
92
|
-
|
|
132
|
+
if (this.shouldVerboseLog()) {
|
|
133
|
+
console.debug(`[PollingService] Task '${id}' unregistered.`)
|
|
134
|
+
}
|
|
93
135
|
if (this.isRunning) {
|
|
94
136
|
this._reschedule()
|
|
95
137
|
}
|
|
96
138
|
}
|
|
97
139
|
else {
|
|
98
|
-
|
|
140
|
+
if (this.shouldVerboseLog()) {
|
|
141
|
+
console.warn(`[PollingService] Attempted to unregister a non-existent task with ID '${id}'.`)
|
|
142
|
+
}
|
|
99
143
|
}
|
|
100
144
|
}
|
|
101
145
|
|
|
@@ -114,11 +158,13 @@ export class PollingService {
|
|
|
114
158
|
*/
|
|
115
159
|
public start(): void {
|
|
116
160
|
if (this.isRunning) {
|
|
117
|
-
|
|
161
|
+
this.recordStartAttempt()
|
|
118
162
|
return
|
|
119
163
|
}
|
|
120
164
|
this.isRunning = true
|
|
121
|
-
|
|
165
|
+
if (this.shouldVerboseLog()) {
|
|
166
|
+
console.log('[PollingService] Polling service started')
|
|
167
|
+
}
|
|
122
168
|
this._setupQuitListener()
|
|
123
169
|
this._reschedule()
|
|
124
170
|
}
|
|
@@ -151,7 +197,7 @@ export class PollingService {
|
|
|
151
197
|
}
|
|
152
198
|
}
|
|
153
199
|
}
|
|
154
|
-
catch
|
|
200
|
+
catch {
|
|
155
201
|
// Not in Electron environment or Electron not available
|
|
156
202
|
// This is fine, just skip the quit listener setup
|
|
157
203
|
}
|
|
@@ -178,10 +224,14 @@ export class PollingService {
|
|
|
178
224
|
}
|
|
179
225
|
|
|
180
226
|
if (reason) {
|
|
181
|
-
|
|
227
|
+
if (this.shouldVerboseLog()) {
|
|
228
|
+
console.log(`[PollingService] Stopping polling service: ${reason}`)
|
|
229
|
+
}
|
|
182
230
|
}
|
|
183
231
|
else {
|
|
184
|
-
|
|
232
|
+
if (this.shouldVerboseLog()) {
|
|
233
|
+
console.log('[PollingService] Polling service stopped')
|
|
234
|
+
}
|
|
185
235
|
}
|
|
186
236
|
}
|
|
187
237
|
|
|
@@ -217,12 +267,35 @@ export class PollingService {
|
|
|
217
267
|
if (tasksToRun.length > 0) {
|
|
218
268
|
// console.debug(`[PollingService] Executing ${tasksToRun.length} tasks.`);
|
|
219
269
|
for (const task of tasksToRun) {
|
|
270
|
+
const startAt = Date.now()
|
|
271
|
+
this.activeTasks.set(task.id, startAt)
|
|
220
272
|
try {
|
|
221
273
|
await task.callback()
|
|
222
274
|
}
|
|
223
275
|
catch (error) {
|
|
224
276
|
console.error(`[PollingService] Error executing task '${task.id}':`, error)
|
|
225
277
|
}
|
|
278
|
+
finally {
|
|
279
|
+
const endAt = Date.now()
|
|
280
|
+
const durationMs = Math.max(0, endAt - startAt)
|
|
281
|
+
const stats = this.taskStats.get(task.id) ?? {
|
|
282
|
+
count: 0,
|
|
283
|
+
lastStartAt: 0,
|
|
284
|
+
lastEndAt: 0,
|
|
285
|
+
lastDurationMs: 0,
|
|
286
|
+
maxDurationMs: 0,
|
|
287
|
+
}
|
|
288
|
+
if (this.taskStats.get(task.id)?.lastMeta) {
|
|
289
|
+
stats.lastMeta = this.taskStats.get(task.id)?.lastMeta
|
|
290
|
+
}
|
|
291
|
+
stats.count += 1
|
|
292
|
+
stats.lastStartAt = startAt
|
|
293
|
+
stats.lastEndAt = endAt
|
|
294
|
+
stats.lastDurationMs = durationMs
|
|
295
|
+
stats.maxDurationMs = Math.max(stats.maxDurationMs, durationMs)
|
|
296
|
+
this.taskStats.set(task.id, stats)
|
|
297
|
+
this.activeTasks.delete(task.id)
|
|
298
|
+
}
|
|
226
299
|
// Update next run time based on its last scheduled run time, not 'now'.
|
|
227
300
|
// This prevents drift if a task takes a long time to execute.
|
|
228
301
|
task.nextRunMs += task.intervalMs
|
|
@@ -231,6 +304,87 @@ export class PollingService {
|
|
|
231
304
|
|
|
232
305
|
this._reschedule()
|
|
233
306
|
}
|
|
307
|
+
|
|
308
|
+
public getDiagnostics(): {
|
|
309
|
+
activeTasks: Array<{
|
|
310
|
+
id: string
|
|
311
|
+
startedAt: number
|
|
312
|
+
ageMs: number
|
|
313
|
+
intervalMs?: number
|
|
314
|
+
nextRunMs?: number
|
|
315
|
+
lastDurationMs?: number
|
|
316
|
+
maxDurationMs?: number
|
|
317
|
+
count?: number
|
|
318
|
+
lastMeta?: Record<string, unknown>
|
|
319
|
+
}>
|
|
320
|
+
recentTasks: Array<{
|
|
321
|
+
id: string
|
|
322
|
+
lastDurationMs: number
|
|
323
|
+
lastEndAt: number
|
|
324
|
+
count: number
|
|
325
|
+
maxDurationMs: number
|
|
326
|
+
intervalMs?: number
|
|
327
|
+
nextRunMs?: number
|
|
328
|
+
lastMeta?: Record<string, unknown>
|
|
329
|
+
}>
|
|
330
|
+
startAttempts: Array<{ caller: string, count: number, ageMs: number }>
|
|
331
|
+
} {
|
|
332
|
+
const now = Date.now()
|
|
333
|
+
const activeTasks = Array.from(this.activeTasks.entries())
|
|
334
|
+
.map(([id, startedAt]) => ({
|
|
335
|
+
id,
|
|
336
|
+
startedAt,
|
|
337
|
+
ageMs: Math.max(0, now - startedAt),
|
|
338
|
+
intervalMs: this.tasks.get(id)?.intervalMs,
|
|
339
|
+
nextRunMs: this.tasks.get(id)?.nextRunMs,
|
|
340
|
+
lastDurationMs: this.taskStats.get(id)?.lastDurationMs,
|
|
341
|
+
maxDurationMs: this.taskStats.get(id)?.maxDurationMs,
|
|
342
|
+
count: this.taskStats.get(id)?.count,
|
|
343
|
+
lastMeta: this.taskStats.get(id)?.lastMeta,
|
|
344
|
+
}))
|
|
345
|
+
.sort((a, b) => b.ageMs - a.ageMs)
|
|
346
|
+
.slice(0, 6)
|
|
347
|
+
const recentTasks = Array.from(this.taskStats.entries())
|
|
348
|
+
.map(([id, stat]) => ({
|
|
349
|
+
id,
|
|
350
|
+
lastDurationMs: stat.lastDurationMs,
|
|
351
|
+
lastEndAt: stat.lastEndAt,
|
|
352
|
+
count: stat.count,
|
|
353
|
+
maxDurationMs: stat.maxDurationMs,
|
|
354
|
+
intervalMs: this.tasks.get(id)?.intervalMs,
|
|
355
|
+
nextRunMs: this.tasks.get(id)?.nextRunMs,
|
|
356
|
+
lastMeta: stat.lastMeta,
|
|
357
|
+
}))
|
|
358
|
+
.sort((a, b) => b.lastEndAt - a.lastEndAt)
|
|
359
|
+
.slice(0, 6)
|
|
360
|
+
const startAttempts = Array.from(this.startAttempts.entries())
|
|
361
|
+
.map(([caller, stat]) => ({
|
|
362
|
+
caller,
|
|
363
|
+
count: stat.count,
|
|
364
|
+
ageMs: Math.max(0, now - stat.lastAt),
|
|
365
|
+
}))
|
|
366
|
+
.sort((a, b) => b.count - a.count)
|
|
367
|
+
.slice(0, 5)
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
activeTasks,
|
|
371
|
+
recentTasks,
|
|
372
|
+
startAttempts,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private recordStartAttempt(): void {
|
|
377
|
+
const stack = new Error().stack
|
|
378
|
+
const caller = stack?.split('\n')[2]?.trim() ?? 'unknown'
|
|
379
|
+
const entry = this.startAttempts.get(caller) ?? { count: 0, lastAt: 0 }
|
|
380
|
+
entry.count += 1
|
|
381
|
+
entry.lastAt = Date.now()
|
|
382
|
+
this.startAttempts.set(caller, entry)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private shouldVerboseLog(): boolean {
|
|
386
|
+
return (globalThis as any).__TALEX_VERBOSE_LOGS__ === true
|
|
387
|
+
}
|
|
234
388
|
}
|
|
235
389
|
|
|
236
390
|
export const pollingService = PollingService.getInstance()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import pathBrowserify from 'path-browserify'
|
|
2
|
+
import { hasWindow } from '../../env'
|
|
3
|
+
|
|
4
|
+
const path = (() => {
|
|
5
|
+
if (hasWindow()) {
|
|
6
|
+
return pathBrowserify
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const nodeRequire = typeof require === 'function' ? require : null
|
|
10
|
+
if (nodeRequire) {
|
|
11
|
+
try {
|
|
12
|
+
return nodeRequire('node:path')
|
|
13
|
+
} catch {
|
|
14
|
+
return pathBrowserify
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return pathBrowserify
|
|
19
|
+
})()
|
|
20
|
+
|
|
21
|
+
export interface SafePathResult {
|
|
22
|
+
resolvedPath: string | null
|
|
23
|
+
error?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SafePathOptions {
|
|
27
|
+
allowAbsolute?: boolean
|
|
28
|
+
allowRoot?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const NULL_BYTE_PATTERN = /\0/
|
|
32
|
+
|
|
33
|
+
export function isAbsolutePath(value: string): boolean {
|
|
34
|
+
return path.isAbsolute(value) || path.win32.isAbsolute(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isSafePathSegment(value: string): boolean {
|
|
38
|
+
const trimmed = value.trim()
|
|
39
|
+
if (!trimmed || trimmed === '.' || trimmed === '..') return false
|
|
40
|
+
if (NULL_BYTE_PATTERN.test(trimmed)) return false
|
|
41
|
+
if (trimmed.includes('/') || trimmed.includes('\\')) return false
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function normalizeAbsolutePath(value: string): string | null {
|
|
46
|
+
const trimmed = value.trim()
|
|
47
|
+
if (!trimmed) return null
|
|
48
|
+
if (!isAbsolutePath(trimmed)) return null
|
|
49
|
+
if (NULL_BYTE_PATTERN.test(trimmed)) return null
|
|
50
|
+
return path.normalize(trimmed)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function resolveSafePath(
|
|
54
|
+
baseDir: string,
|
|
55
|
+
targetPath: string,
|
|
56
|
+
options: SafePathOptions = {}
|
|
57
|
+
): SafePathResult {
|
|
58
|
+
const allowAbsolute = options.allowAbsolute ?? false
|
|
59
|
+
const allowRoot = options.allowRoot ?? false
|
|
60
|
+
|
|
61
|
+
const base = baseDir.trim()
|
|
62
|
+
const target = targetPath.trim()
|
|
63
|
+
if (!base || !target) {
|
|
64
|
+
return { resolvedPath: null, error: 'PATH_EMPTY' }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (NULL_BYTE_PATTERN.test(target)) {
|
|
68
|
+
return { resolvedPath: null, error: 'PATH_NULL_BYTE' }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!allowAbsolute && isAbsolutePath(target)) {
|
|
72
|
+
return { resolvedPath: null, error: 'PATH_ABSOLUTE_NOT_ALLOWED' }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const resolvedBase = path.resolve(base)
|
|
76
|
+
const resolvedTarget = allowAbsolute && isAbsolutePath(target)
|
|
77
|
+
? path.normalize(target)
|
|
78
|
+
: path.resolve(resolvedBase, target)
|
|
79
|
+
|
|
80
|
+
if (resolvedTarget === resolvedBase) {
|
|
81
|
+
return allowRoot
|
|
82
|
+
? { resolvedPath: resolvedTarget }
|
|
83
|
+
: { resolvedPath: null, error: 'PATH_ROOT_NOT_ALLOWED' }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!resolvedTarget.startsWith(`${resolvedBase}${path.sep}`)) {
|
|
87
|
+
return { resolvedPath: null, error: 'PATH_TRAVERSAL' }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { resolvedPath: resolvedTarget }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function assertSafePath(
|
|
94
|
+
baseDir: string,
|
|
95
|
+
targetPath: string,
|
|
96
|
+
options: SafePathOptions = {}
|
|
97
|
+
): string {
|
|
98
|
+
const result = resolveSafePath(baseDir, targetPath, options)
|
|
99
|
+
if (!result.resolvedPath) {
|
|
100
|
+
throw new Error(result.error ?? 'PATH_INVALID')
|
|
101
|
+
}
|
|
102
|
+
return result.resolvedPath
|
|
103
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChildProcess,
|
|
3
|
+
ExecException,
|
|
4
|
+
ExecFileOptions,
|
|
5
|
+
SpawnOptionsWithoutStdio,
|
|
6
|
+
} from 'node:child_process'
|
|
7
|
+
import { hasWindow } from '../../env'
|
|
8
|
+
|
|
9
|
+
type ExecFileCallback = (error: ExecException | null, stdout: string, stderr: string) => void
|
|
10
|
+
|
|
11
|
+
const NULL_BYTE_PATTERN = /\0/
|
|
12
|
+
const NEWLINE_PATTERN = /[\r\n]/
|
|
13
|
+
|
|
14
|
+
const childProcess = (() => {
|
|
15
|
+
if (hasWindow()) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const nodeRequire = typeof require === 'function' ? require : null
|
|
20
|
+
if (!nodeRequire) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
return nodeRequire('node:child_process') as typeof import('node:child_process')
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
})()
|
|
30
|
+
|
|
31
|
+
const execFileImpl = childProcess?.execFile
|
|
32
|
+
const spawnImpl = childProcess?.spawn
|
|
33
|
+
|
|
34
|
+
function assertShellValue(value: string, label: string): string {
|
|
35
|
+
const trimmed = value.trim()
|
|
36
|
+
if (!trimmed) {
|
|
37
|
+
throw new Error(`${label}_EMPTY`)
|
|
38
|
+
}
|
|
39
|
+
if (NULL_BYTE_PATTERN.test(trimmed)) {
|
|
40
|
+
throw new Error(`${label}_NULL_BYTE`)
|
|
41
|
+
}
|
|
42
|
+
if (NEWLINE_PATTERN.test(trimmed)) {
|
|
43
|
+
throw new Error(`${label}_NEWLINE`)
|
|
44
|
+
}
|
|
45
|
+
return trimmed
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertShellArg(value: string): string {
|
|
49
|
+
if (NULL_BYTE_PATTERN.test(value)) {
|
|
50
|
+
throw new Error('ARG_NULL_BYTE')
|
|
51
|
+
}
|
|
52
|
+
return value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requireChildProcess(): {
|
|
56
|
+
execFile: (file: string, args: string[], options: ExecFileOptions, callback: ExecFileCallback) => ChildProcess
|
|
57
|
+
spawn: (command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio) => ChildProcess
|
|
58
|
+
} {
|
|
59
|
+
if (!execFileImpl || !spawnImpl) {
|
|
60
|
+
throw new Error('CHILD_PROCESS_UNAVAILABLE')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
execFile: execFileImpl,
|
|
65
|
+
spawn: spawnImpl,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function execFileSafe(
|
|
70
|
+
command: string,
|
|
71
|
+
args: string[] = [],
|
|
72
|
+
options: ExecFileOptions = {}
|
|
73
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
74
|
+
const { execFile } = requireChildProcess()
|
|
75
|
+
const safeCommand = assertShellValue(command, 'COMMAND')
|
|
76
|
+
const safeArgs = args.map(assertShellArg)
|
|
77
|
+
|
|
78
|
+
return await new Promise((resolve, reject) => {
|
|
79
|
+
execFile(
|
|
80
|
+
safeCommand,
|
|
81
|
+
safeArgs,
|
|
82
|
+
{
|
|
83
|
+
...options,
|
|
84
|
+
shell: false,
|
|
85
|
+
},
|
|
86
|
+
(error, stdout, stderr) => {
|
|
87
|
+
if (error) {
|
|
88
|
+
reject(error)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
resolve({ stdout, stderr })
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function spawnSafe(
|
|
98
|
+
command: string,
|
|
99
|
+
args: string[] = [],
|
|
100
|
+
options: SpawnOptionsWithoutStdio = {}
|
|
101
|
+
): ChildProcess {
|
|
102
|
+
const { spawn } = requireChildProcess()
|
|
103
|
+
const safeCommand = assertShellValue(command, 'COMMAND')
|
|
104
|
+
const safeArgs = args.map(assertShellArg)
|
|
105
|
+
return spawn(safeCommand, safeArgs, { ...options, shell: false })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function spawnShellCommand(
|
|
109
|
+
command: string,
|
|
110
|
+
options: SpawnOptionsWithoutStdio = {}
|
|
111
|
+
): ChildProcess {
|
|
112
|
+
const { spawn } = requireChildProcess()
|
|
113
|
+
const safeCommand = assertShellValue(command, 'COMMAND')
|
|
114
|
+
return spawn(safeCommand, [], { ...options, shell: true })
|
|
115
|
+
}
|
|
@@ -80,7 +80,10 @@ export async function runAdaptiveTaskQueue<T>(
|
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
for (let index = 0; index < total; index++) {
|
|
83
|
-
|
|
83
|
+
const item = items[index]
|
|
84
|
+
if (item === undefined)
|
|
85
|
+
continue
|
|
86
|
+
await handler(item, index)
|
|
84
87
|
|
|
85
88
|
const processed = index + 1
|
|
86
89
|
if (processed % batchSize === 0 && processed < total) {
|
package/core-box/index.ts
CHANGED
|
@@ -51,10 +51,47 @@ export interface ScoredItem {
|
|
|
51
51
|
sourceId: string
|
|
52
52
|
itemId: string
|
|
53
53
|
score: number
|
|
54
|
-
source: 'frequent' | 'time-based' | 'recent' | 'trending' | 'context'
|
|
54
|
+
source: 'frequent' | 'time-based' | 'recent' | 'trending' | 'context' | 'plugin'
|
|
55
55
|
reason?: string
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Candidate item returned by a plugin recommend provider.
|
|
60
|
+
* Unlike internal candidates, these do not require usageStats.
|
|
61
|
+
*/
|
|
62
|
+
export interface PluginRecommendCandidate {
|
|
63
|
+
/** Provider ID (auto-filled from provider.id) */
|
|
64
|
+
providerId?: string
|
|
65
|
+
/** Unique item ID */
|
|
66
|
+
id: string
|
|
67
|
+
/** Display title */
|
|
68
|
+
title: string
|
|
69
|
+
/** Subtitle / description */
|
|
70
|
+
subtitle?: string
|
|
71
|
+
/** Icon configuration */
|
|
72
|
+
icon?: { type: string; value: string }
|
|
73
|
+
/** Priority 0-100, higher = more prominent */
|
|
74
|
+
priority?: number
|
|
75
|
+
/** Action key passed back to the plugin */
|
|
76
|
+
action: string
|
|
77
|
+
/** Additional data */
|
|
78
|
+
data?: Record<string, unknown>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Provider interface for plugins to supply custom recommendations.
|
|
83
|
+
*/
|
|
84
|
+
export interface RecommendProvider {
|
|
85
|
+
/** Unique provider ID */
|
|
86
|
+
id: string
|
|
87
|
+
/** Display name */
|
|
88
|
+
name: string
|
|
89
|
+
/** Whether this provider can supply recommendations for the given context */
|
|
90
|
+
canProvide(context: ContextSignal): boolean
|
|
91
|
+
/** Return recommendation candidates */
|
|
92
|
+
getCandidates(context: ContextSignal): PluginRecommendCandidate[] | Promise<PluginRecommendCandidate[]>
|
|
93
|
+
}
|
|
94
|
+
|
|
58
95
|
/**
|
|
59
96
|
* Recommendation badge display configuration for UI rendering.
|
|
60
97
|
*/
|