@goodandready/dsh-image-gen 0.11.12 → 0.11.14
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/lib/client.js +31 -0
- package/lib/config-schema.js +34 -0
- package/lib/index.js +4 -1
- package/lib/providers.js +7 -3
- package/lib/register-tools.js +46 -20
- package/lib/security.js +5 -4
- package/lib/settings-route.js +1 -1
- package/lib/tools/character-sheet.js +1 -5
- package/lib/tools/diagram.js +1 -5
- package/lib/tools/editing.js +11 -15
- package/lib/tools/generation.js +44 -48
- package/lib/tools/inspect.js +7 -5
- package/lib/tools/style-matrix.js +1 -1
- package/lib/tools/ui-asset.js +1 -1
- package/lib/tools/vision-ocr.js +7 -11
- package/lib/updater.js +69 -5
- package/package.json +3 -3
package/lib/client.js
CHANGED
|
@@ -287,6 +287,11 @@ window.__ModuleLoader__.load({
|
|
|
287
287
|
{ field: 'deliverAs', spec: selectField('deliverAs', DELIVERY_MODES), kind: 'select', options: DELIVERY_MODES, labelKey: 'f.deliverAs', hintKey: 'f.deliverAsHint', tab: 'general' },
|
|
288
288
|
{ field: 'outputDir', spec: textField('outputDir'), kind: 'text', labelKey: 'f.outputDir', hintKey: 'f.outputDirHint', placeholderKey: 'p.outputDir', tab: 'general' },
|
|
289
289
|
{ field: 'historyLimit', spec: numberField('historyLimit'), kind: 'number', labelKey: 'f.historyLimit', hintKey: 'f.historyLimitHint', placeholderKey: 'p.historyLimit', tab: 'general' },
|
|
290
|
+
{ field: 'toolsetProfile', spec: selectField('toolsetProfile', ['minimal', 'all', 'custom']), kind: 'select', options: ['minimal', 'all', 'custom'], labelKey: 'f.toolsetProfile', hintKey: 'f.toolsetProfileHint', tab: 'general' },
|
|
291
|
+
{ field: 'toolsetDesign', spec: booleanField('toolsetDesign'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetDesign', hintKey: 'f.toolsetDesignHint', tab: 'general' },
|
|
292
|
+
{ field: 'toolsetProcessing', spec: booleanField('toolsetProcessing'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetProcessing', hintKey: 'f.toolsetProcessingHint', tab: 'general' },
|
|
293
|
+
{ field: 'toolsetFrontend', spec: booleanField('toolsetFrontend'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetFrontend', hintKey: 'f.toolsetFrontendHint', tab: 'general' },
|
|
294
|
+
{ field: 'toolsetCreative', spec: booleanField('toolsetCreative'), kind: 'select', options: ['true', 'false'], labelKey: 'f.toolsetCreative', hintKey: 'f.toolsetCreativeHint', tab: 'general' },
|
|
290
295
|
|
|
291
296
|
// Provider: FAL
|
|
292
297
|
{ field: 'model', spec: textField('model'), kind: 'text', labelKey: 'f.model', hintKey: 'f.modelHint', placeholderKey: 'p.model', when: ['fal'], tab: 'provider' },
|
|
@@ -2458,6 +2463,16 @@ window.__ModuleLoader__.load({
|
|
|
2458
2463
|
'p.outputDir': 'generated/images',
|
|
2459
2464
|
'f.historyLimit': 'History Limit',
|
|
2460
2465
|
'f.historyLimitHint': 'Maximum number of recent generation records kept in memory.',
|
|
2466
|
+
'f.toolsetProfile': 'Toolset Profile',
|
|
2467
|
+
'f.toolsetProfileHint': 'Select which tools to register into context: minimal (<= 3 core tools, saving ~8k prompt tokens), all (30 tools), or custom.',
|
|
2468
|
+
'f.toolsetDesign': 'Design Toolset',
|
|
2469
|
+
'f.toolsetDesignHint': 'Enable design tools: style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor.',
|
|
2470
|
+
'f.toolsetProcessing': 'Processing Toolset',
|
|
2471
|
+
'f.toolsetProcessingHint': 'Enable processing tools: remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text.',
|
|
2472
|
+
'f.toolsetFrontend': 'Frontend Toolset',
|
|
2473
|
+
'f.toolsetFrontendHint': 'Enable frontend tools: extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups.',
|
|
2474
|
+
'f.toolsetCreative': 'Creative Toolset',
|
|
2475
|
+
'f.toolsetCreativeHint': 'Enable creative tools: spritesheet, seamless_pattern, sketch_to_image, image_pack.',
|
|
2461
2476
|
'p.historyLimit': '50',
|
|
2462
2477
|
|
|
2463
2478
|
'f.model': 'FAL Model ID',
|
|
@@ -2724,6 +2739,16 @@ window.__ModuleLoader__.load({
|
|
|
2724
2739
|
'p.outputDir': 'generated/images',
|
|
2725
2740
|
'f.historyLimit': '历史记录数量',
|
|
2726
2741
|
'f.historyLimitHint': '保存在内存历史列表中的最大条目数。',
|
|
2742
|
+
'f.toolsetProfile': '工具集配置档',
|
|
2743
|
+
'f.toolsetProfileHint': '选择注册到对话上下文的工具集:minimal(<= 3个核心工具,节省~8k提示词tokens),all(所有30个工具),或custom。',
|
|
2744
|
+
'f.toolsetDesign': '设计工具集',
|
|
2745
|
+
'f.toolsetDesignHint': '启用设计工具:style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor。',
|
|
2746
|
+
'f.toolsetProcessing': '图像处理工具集',
|
|
2747
|
+
'f.toolsetProcessingHint': '启用处理工具:remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text。',
|
|
2748
|
+
'f.toolsetFrontend': '前端工具集',
|
|
2749
|
+
'f.toolsetFrontendHint': '启用前端工具:extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups。',
|
|
2750
|
+
'f.toolsetCreative': '创意工具集',
|
|
2751
|
+
'f.toolsetCreativeHint': '启用创意工具:spritesheet, seamless_pattern, sketch_to_image, image_pack。',
|
|
2727
2752
|
'p.historyLimit': '50',
|
|
2728
2753
|
|
|
2729
2754
|
'f.model': 'FAL 模型 ID',
|
|
@@ -3076,6 +3101,12 @@ window.__ModuleLoader__.load({
|
|
|
3076
3101
|
'smart_crop_image',
|
|
3077
3102
|
'export_asset_pack',
|
|
3078
3103
|
'generate_ui_asset',
|
|
3104
|
+
'generate_character_sheet',
|
|
3105
|
+
'replace_image_text',
|
|
3106
|
+
'beautify_diagram',
|
|
3107
|
+
'generate_seamless_pattern',
|
|
3108
|
+
'generate_spritesheet',
|
|
3109
|
+
'sketch_to_image',
|
|
3079
3110
|
]
|
|
3080
3111
|
|
|
3081
3112
|
for (const toolName of toolviewTools) {
|
package/lib/config-schema.js
CHANGED
|
@@ -54,6 +54,40 @@ export const Config = z.object({
|
|
|
54
54
|
.description('Default aspect ratio (square, landscape_16_9, portrait_9_16, etc.) used when prompt does not specify dimensions.')
|
|
55
55
|
.default('square')
|
|
56
56
|
.volatile(),
|
|
57
|
+
toolsetProfile: z
|
|
58
|
+
.string()
|
|
59
|
+
.description('Which toolset profile to register into conversation context: "minimal" (default, <= 3 core tools, saving ~8,000 prompt tokens), "all" (all 30 tools), or "custom".')
|
|
60
|
+
.default('minimal')
|
|
61
|
+
.volatile(),
|
|
62
|
+
toolsets: z
|
|
63
|
+
.object({
|
|
64
|
+
design: z.boolean().default(false).description('Design tools: style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor'),
|
|
65
|
+
processing: z.boolean().default(false).description('Processing tools: remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text'),
|
|
66
|
+
frontend: z.boolean().default(false).description('Frontend tools: extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups'),
|
|
67
|
+
creative: z.boolean().default(false).description('Creative tools: spritesheet, seamless_pattern, sketch_to_image, image_pack'),
|
|
68
|
+
})
|
|
69
|
+
.default({ design: false, processing: false, frontend: false, creative: false })
|
|
70
|
+
.volatile(),
|
|
71
|
+
toolsetDesign: z
|
|
72
|
+
.boolean()
|
|
73
|
+
.description('Enable design tools: style_matrix, theme_pair, ui_asset, character_sheet, beautify_diagram, anchor')
|
|
74
|
+
.default(false)
|
|
75
|
+
.volatile(),
|
|
76
|
+
toolsetProcessing: z
|
|
77
|
+
.boolean()
|
|
78
|
+
.description('Enable processing tools: remove_background, upscale, vectorize, convert, grid, smart_crop, asset_pack, replace_image_text')
|
|
79
|
+
.default(false)
|
|
80
|
+
.volatile(),
|
|
81
|
+
toolsetFrontend: z
|
|
82
|
+
.boolean()
|
|
83
|
+
.description('Enable frontend tools: extract_design_tokens, image_to_css_gradient, check_image_contrast, responsive_mockups')
|
|
84
|
+
.default(false)
|
|
85
|
+
.volatile(),
|
|
86
|
+
toolsetCreative: z
|
|
87
|
+
.boolean()
|
|
88
|
+
.description('Enable creative tools: spritesheet, seamless_pattern, sketch_to_image, image_pack')
|
|
89
|
+
.default(false)
|
|
90
|
+
.volatile(),
|
|
57
91
|
deliverAs: z
|
|
58
92
|
.string()
|
|
59
93
|
.description('How generated images are returned: "link" keeps them on disk and embeds an inline preview link; "attachment" embeds full image bytes directly in the chat.')
|
package/lib/index.js
CHANGED
|
@@ -312,7 +312,10 @@ export function apply(ctx, rawConfig) {
|
|
|
312
312
|
}
|
|
313
313
|
try {
|
|
314
314
|
const u = new URL(req.url, 'http://localhost')
|
|
315
|
-
const
|
|
315
|
+
const qProvider = u.searchParams.get('provider')
|
|
316
|
+
const provider = (typeof qProvider === 'string' && qProvider.trim())
|
|
317
|
+
|| (typeof config?.provider === 'string' && config.provider.trim())
|
|
318
|
+
|| 'fal'
|
|
316
319
|
const deps = {
|
|
317
320
|
fetchImpl: fetch,
|
|
318
321
|
resolveKey: (ref) => resolveApiKey(ctx, ref),
|
package/lib/providers.js
CHANGED
|
@@ -378,7 +378,7 @@ export async function blendImagesFal({ fetchImpl, resolveKey, cfg }, { images, w
|
|
|
378
378
|
/** @internal — direct provider call path for integration tests */
|
|
379
379
|
export async function editImageDirect(deps, job) {
|
|
380
380
|
const providers = makeProviders(deps, job)
|
|
381
|
-
const providerKey = job.provider || deps.cfg.defaultProvider || 'fal'
|
|
381
|
+
const providerKey = (typeof job?.provider === 'string' && job.provider.trim()) || (typeof deps?.cfg?.provider === 'string' && deps.cfg.provider.trim()) || (typeof deps?.cfg?.defaultProvider === 'string' && deps.cfg.defaultProvider.trim()) || 'fal'
|
|
382
382
|
const fn = providers[providerKey] || providers.fal || providers.custom
|
|
383
383
|
if (!fn) throw new Error(`Provider "${providerKey}" does not support image editing`)
|
|
384
384
|
return fn(job.seed, job.prompt)
|
|
@@ -391,7 +391,7 @@ export async function varyImageDirect(deps, job) {
|
|
|
391
391
|
...job,
|
|
392
392
|
strength: job.variationStrength ?? job.strength ?? 0.35,
|
|
393
393
|
})
|
|
394
|
-
const providerKey = job.provider || deps.cfg.defaultProvider || 'fal'
|
|
394
|
+
const providerKey = (typeof job?.provider === 'string' && job.provider.trim()) || (typeof deps?.cfg?.provider === 'string' && deps.cfg.provider.trim()) || (typeof deps?.cfg?.defaultProvider === 'string' && deps.cfg.defaultProvider.trim()) || 'fal'
|
|
395
395
|
const fn = providers[providerKey] || providers.fal || providers.custom
|
|
396
396
|
if (!fn) throw new Error(`Provider "${providerKey}" does not support image variations`)
|
|
397
397
|
return fn(job.seed, job.prompt)
|
|
@@ -428,7 +428,11 @@ export function toLosslessJson(val) {
|
|
|
428
428
|
export async function testProviderConnection(deps, providerId) {
|
|
429
429
|
const { fetchImpl = fetch, resolveKey, cfg } = deps
|
|
430
430
|
const start = Date.now()
|
|
431
|
-
const
|
|
431
|
+
const rawProvider = (typeof providerId === 'string' && providerId.trim())
|
|
432
|
+
|| (typeof cfg?.provider === 'string' && cfg.provider.trim())
|
|
433
|
+
|| (typeof cfg?.defaultProvider === 'string' && cfg.defaultProvider.trim())
|
|
434
|
+
|| 'fal'
|
|
435
|
+
const p = rawProvider.toLowerCase()
|
|
432
436
|
|
|
433
437
|
try {
|
|
434
438
|
if (p === 'fal') {
|
package/lib/register-tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// register-tools.js — orchestrates all image-gen tool registrations (#216).
|
|
2
|
-
//
|
|
1
|
+
// register-tools.js — orchestrates all image-gen tool registrations (#216, #337).
|
|
2
|
+
// Supports 'minimal' (default: <= 3 core tools), 'all' (all 30 tools), and 'custom' (toggled toolsets).
|
|
3
3
|
|
|
4
4
|
import { registerGenerationTools } from './tools/generation.js'
|
|
5
5
|
import { registerProcessingTools } from './tools/processing-basic.js'
|
|
@@ -20,27 +20,53 @@ import { registerVisionOcrTools } from './tools/vision-ocr.js'
|
|
|
20
20
|
import { registerDiagramTools } from './tools/diagram.js'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Registers every image-gen tool on the host tools service.
|
|
23
|
+
* Registers every image-gen tool on the host tools service according to toolsetProfile (#337).
|
|
24
24
|
*
|
|
25
25
|
* @param {object} ctx cordis context
|
|
26
26
|
* @param {object} deps shared runtime from apply()
|
|
27
27
|
*/
|
|
28
28
|
export function registerAllTools(ctx, deps) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
const cfg = typeof deps.live === 'function' ? deps.live() : (deps.config || {})
|
|
30
|
+
const profile = cfg.toolsetProfile || 'minimal'
|
|
31
|
+
const isAll = profile === 'all'
|
|
32
|
+
const sets = cfg.toolsets || {}
|
|
33
|
+
const design = isAll || Boolean(sets.design || cfg.toolsetDesign)
|
|
34
|
+
const processing = isAll || Boolean(sets.processing || cfg.toolsetProcessing)
|
|
35
|
+
const frontend = isAll || Boolean(sets.frontend || cfg.toolsetFrontend)
|
|
36
|
+
const creative = isAll || Boolean(sets.creative || cfg.toolsetCreative)
|
|
37
|
+
|
|
38
|
+
// 1. Core tools: generate_image, edit_image, inspect_image_quality (<= 3 tools in minimal)
|
|
39
|
+
registerGenerationTools(ctx, deps, { coreOnly: !creative })
|
|
40
|
+
registerEditingTools(ctx, deps, { coreOnly: !processing })
|
|
41
|
+
registerInspectTools(ctx, deps, { coreOnly: !processing })
|
|
42
|
+
|
|
43
|
+
// 2. Processing toolset
|
|
44
|
+
if (processing) {
|
|
45
|
+
registerProcessingTools(ctx, deps)
|
|
46
|
+
registerProcessingAdvancedTools(ctx, deps)
|
|
47
|
+
registerVisionOcrTools(ctx, deps)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. Design toolset
|
|
51
|
+
if (design) {
|
|
52
|
+
registerAnchorTools(ctx, deps)
|
|
53
|
+
registerUiAssetTools(ctx, deps)
|
|
54
|
+
registerStyleMatrixTools(ctx, deps)
|
|
55
|
+
registerThemePairTools(ctx, deps)
|
|
56
|
+
registerCharacterSheetTools(ctx, deps)
|
|
57
|
+
registerDiagramTools(ctx, deps)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Frontend toolset
|
|
61
|
+
if (frontend) {
|
|
62
|
+
registerFrontendTools(ctx, deps)
|
|
63
|
+
registerResponsiveTools(ctx, deps)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 5. Creative toolset
|
|
67
|
+
if (creative) {
|
|
68
|
+
registerSketchTools(ctx, deps)
|
|
69
|
+
registerSpritesheetTools(ctx, deps)
|
|
70
|
+
registerPatternTools(ctx, deps)
|
|
71
|
+
}
|
|
46
72
|
}
|
package/lib/security.js
CHANGED
|
@@ -64,7 +64,8 @@ export function enforceSecurePermissions(filePath) {
|
|
|
64
64
|
try {
|
|
65
65
|
chmodSync(filePath, 0o600)
|
|
66
66
|
return true
|
|
67
|
-
} catch {
|
|
67
|
+
} catch (_err) {
|
|
68
|
+
/* non-fatal: permission change error */
|
|
68
69
|
return false
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -143,7 +144,7 @@ export function extractHostName(hostHeader) {
|
|
|
143
144
|
try {
|
|
144
145
|
const url = new URL(`http://${trimmed}`)
|
|
145
146
|
return url.hostname.replace(/^\[|\]$/g, '')
|
|
146
|
-
} catch {
|
|
147
|
+
} catch (_err) {
|
|
147
148
|
return trimmed.replace(/^\[|\]$/g, '').split(':')[0] || ''
|
|
148
149
|
}
|
|
149
150
|
}
|
|
@@ -210,7 +211,7 @@ export function isTrustedLocalRequest(req) {
|
|
|
210
211
|
if (url.host.toLowerCase() !== host.toLowerCase()) {
|
|
211
212
|
return false
|
|
212
213
|
}
|
|
213
|
-
} catch {
|
|
214
|
+
} catch (_err) {
|
|
214
215
|
return false
|
|
215
216
|
}
|
|
216
217
|
}
|
|
@@ -226,7 +227,7 @@ export function isTrustedLocalRequest(req) {
|
|
|
226
227
|
if (!isLoopbackAddress(refererHostName) && !isPrivateLanAddress(refererHostName)) {
|
|
227
228
|
return false
|
|
228
229
|
}
|
|
229
|
-
} catch {
|
|
230
|
+
} catch (_err) {
|
|
230
231
|
return false
|
|
231
232
|
}
|
|
232
233
|
}
|
package/lib/settings-route.js
CHANGED
|
@@ -79,7 +79,7 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, setLiveConfi
|
|
|
79
79
|
let payload
|
|
80
80
|
try {
|
|
81
81
|
payload = JSON.parse(buf.toString('utf8') || '{}')
|
|
82
|
-
} catch {
|
|
82
|
+
} catch (_err) {
|
|
83
83
|
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
84
84
|
res.end(JSON.stringify({ ok: false, error: 'invalid JSON' }))
|
|
85
85
|
return
|
|
@@ -89,11 +89,7 @@ export function registerCharacterSheetTools(ctx, deps) {
|
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
91
|
},
|
|
92
|
-
render: (
|
|
93
|
-
renderToolOutput(result, renderCtx, {
|
|
94
|
-
title: (res) => `Character Sheet (${res.layout || 'turnaround'}) — ${res.style || 'concept_art'}`,
|
|
95
|
-
subtitle: (res) => (res.views ? `Views: ${res.views.join(' | ')}` : ''),
|
|
96
|
-
}),
|
|
92
|
+
render: (_args, value) => renderToolOutput(value),
|
|
97
93
|
},
|
|
98
94
|
async execute(args, execCtx) {
|
|
99
95
|
const cfg = live.value
|
package/lib/tools/diagram.js
CHANGED
|
@@ -85,11 +85,7 @@ export function registerDiagramTools(ctx, deps) {
|
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
},
|
|
88
|
-
render: (
|
|
89
|
-
renderToolOutput(result, renderCtx, {
|
|
90
|
-
title: (res) => `3D Architecture Diagram — ${res.title || res.style || 'Isometric'}`,
|
|
91
|
-
subtitle: (res) => (res.detectedNodes?.length ? `Nodes: ${res.detectedNodes.slice(0, 4).join(', ')}` : ''),
|
|
92
|
-
}),
|
|
88
|
+
render: (_args, value) => renderToolOutput(value),
|
|
93
89
|
},
|
|
94
90
|
async execute(args, execCtx) {
|
|
95
91
|
const cfg = live?.value || (typeof live === 'function' ? live() : {})
|
package/lib/tools/editing.js
CHANGED
|
@@ -16,7 +16,7 @@ import { trackAndAssertLoopGuard } from '../loop-guard.js'
|
|
|
16
16
|
import { sanitizeErrorAndLogs } from '../security.js'
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
export function registerEditingTools(ctx, deps) {
|
|
19
|
+
export function registerEditingTools(ctx, deps, options = {}) {
|
|
20
20
|
const {
|
|
21
21
|
config,
|
|
22
22
|
live,
|
|
@@ -29,41 +29,36 @@ export function registerEditingTools(ctx, deps) {
|
|
|
29
29
|
ctx.tools.register(
|
|
30
30
|
defineTool({
|
|
31
31
|
name: 'edit_image',
|
|
32
|
-
description:
|
|
33
|
-
'Edit an existing image using inpainting, targeted modification, or object replacement. '
|
|
34
|
-
+ 'Describe the desired changes in prompt. '
|
|
35
|
-
+ 'If image is omitted or "latest", automatically resolves the most recent image from the conversation session (#144). '
|
|
36
|
-
+ 'Leverages @goodandready/dsh-vision-bridge for pre-edit composition analysis (#145). '
|
|
37
|
-
+ 'Optionally accepts mask (path or attachment id) and strength (0.1-1.0). Returns safe dual-output (#150).',
|
|
32
|
+
description: 'Edit an image using inpainting, targeted modification, or object replacement. If image is omitted, uses the latest image from conversation.',
|
|
38
33
|
parameters: {
|
|
39
34
|
prompt: {
|
|
40
35
|
type: 'string',
|
|
41
36
|
required: true,
|
|
42
|
-
description: '
|
|
37
|
+
description: 'Description of modification or edit to perform.',
|
|
43
38
|
},
|
|
44
39
|
image: {
|
|
45
40
|
type: 'string',
|
|
46
|
-
description: 'Path or attachment
|
|
41
|
+
description: 'Path or attachment ID of source image. Defaults to "latest".',
|
|
47
42
|
},
|
|
48
43
|
mask: {
|
|
49
44
|
type: 'string',
|
|
45
|
+
description: 'Optional inpaint mask path or attachment ID.',
|
|
50
46
|
},
|
|
51
47
|
mask_image: {
|
|
52
48
|
type: 'string',
|
|
53
|
-
description: '
|
|
54
|
-
description: 'Optional path or attachment id of the inpaint mask image.',
|
|
49
|
+
description: 'Alias for mask.',
|
|
55
50
|
},
|
|
56
51
|
strength: {
|
|
57
52
|
type: 'number',
|
|
58
|
-
description: '
|
|
53
|
+
description: 'Edit strength between 0.1 and 1.0 (default 0.75).',
|
|
59
54
|
},
|
|
60
55
|
output_name: {
|
|
61
56
|
type: 'string',
|
|
62
|
-
description: 'Optional file name stem for
|
|
57
|
+
description: 'Optional file name stem for edited image.',
|
|
63
58
|
},
|
|
64
59
|
output_dir: {
|
|
65
60
|
type: 'string',
|
|
66
|
-
description: 'Optional custom directory
|
|
61
|
+
description: 'Optional custom directory for edited image.',
|
|
67
62
|
},
|
|
68
63
|
},
|
|
69
64
|
output: {
|
|
@@ -165,6 +160,7 @@ export function registerEditingTools(ctx, deps) {
|
|
|
165
160
|
}),
|
|
166
161
|
)
|
|
167
162
|
}, 'dsh-image-gen: tool edit_image')
|
|
163
|
+
if (!options.coreOnly) {
|
|
168
164
|
ctx.effect(() => {
|
|
169
165
|
ctx.tools.register(
|
|
170
166
|
defineTool({
|
|
@@ -450,5 +446,5 @@ export function registerEditingTools(ctx, deps) {
|
|
|
450
446
|
}),
|
|
451
447
|
)
|
|
452
448
|
}, 'dsh-image-gen: tool remix_image')
|
|
453
|
-
|
|
449
|
+
}
|
|
454
450
|
}
|
package/lib/tools/generation.js
CHANGED
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
import { registerGenerationPackTool } from './generation-pack.js'
|
|
2
|
-
|
|
3
|
-
/** Run an array of async task functions with a concurrency cap */
|
|
4
|
-
async function asyncPool(tasks, concurrency = 3) {
|
|
5
|
-
const results = new Array(tasks.length)
|
|
6
|
-
let nextIdx = 0
|
|
7
|
-
async function worker() {
|
|
8
|
-
while (nextIdx < tasks.length) {
|
|
9
|
-
const idx = nextIdx++
|
|
10
|
-
results[idx] = await tasks[idx]()
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker())
|
|
14
|
-
await Promise.all(workers)
|
|
15
|
-
return results
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// generation — image-gen tools (generate_image, generate_image_pack). Extracted from apply() (#216).
|
|
19
|
-
|
|
20
2
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
21
3
|
import path from 'node:path'
|
|
22
4
|
import { mkdir, writeFile } from 'node:fs/promises'
|
|
@@ -53,8 +35,24 @@ import {
|
|
|
53
35
|
cachedResult,
|
|
54
36
|
} from '../history.js'
|
|
55
37
|
|
|
38
|
+
// generation — image-gen tools (generate_image, generate_image_pack). Extracted from apply() (#216).
|
|
56
39
|
|
|
57
|
-
|
|
40
|
+
/** Run an array of async task functions with a concurrency cap */
|
|
41
|
+
async function asyncPool(tasks, concurrency = 3) {
|
|
42
|
+
const results = new Array(tasks.length)
|
|
43
|
+
let nextIdx = 0
|
|
44
|
+
async function worker() {
|
|
45
|
+
while (nextIdx < tasks.length) {
|
|
46
|
+
const idx = nextIdx++
|
|
47
|
+
results[idx] = await tasks[idx]()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker())
|
|
51
|
+
await Promise.all(workers)
|
|
52
|
+
return results
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function registerGenerationTools(ctx, deps, options = {}) {
|
|
58
56
|
const {
|
|
59
57
|
config,
|
|
60
58
|
live,
|
|
@@ -66,17 +64,12 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
66
64
|
ctx.tools.register(
|
|
67
65
|
defineTool({
|
|
68
66
|
name: 'generate_image',
|
|
69
|
-
description:
|
|
70
|
-
'Generate an image with the configured image provider. '
|
|
71
|
-
+ 'Saves the image to the session workspace and shows it in the conversation. '
|
|
72
|
-
+ 'Depending on how the deployment is configured the result carries either the image itself or a link to it; '
|
|
73
|
-
+ 'when you only get a link, answer from the prompt and the link rather than claiming to see the picture. '
|
|
74
|
-
+ 'Use for any text-to-image request. Pass count (1-4) to generate several variations in one call; cost scales with count (default 1).',
|
|
67
|
+
description: 'Generate an image from prompt. Saves the image to workspace and conversation. Pass count (1-4) for variations.',
|
|
75
68
|
parameters: {
|
|
76
69
|
prompt: {
|
|
77
70
|
type: 'string',
|
|
78
71
|
required: true,
|
|
79
|
-
description: 'Detailed description of the image to generate
|
|
72
|
+
description: 'Detailed description of the image to generate.',
|
|
80
73
|
},
|
|
81
74
|
image_size: {
|
|
82
75
|
type: 'string',
|
|
@@ -85,7 +78,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
85
78
|
aspect_ratio: {
|
|
86
79
|
type: 'string',
|
|
87
80
|
enum: ['1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3'],
|
|
88
|
-
description: 'Optional aspect ratio
|
|
81
|
+
description: 'Optional aspect ratio (e.g. 16:9, 1:1), overrides image_size.',
|
|
89
82
|
},
|
|
90
83
|
seed: {
|
|
91
84
|
type: 'integer',
|
|
@@ -98,11 +91,11 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
98
91
|
},
|
|
99
92
|
output_name: {
|
|
100
93
|
type: 'string',
|
|
101
|
-
description: 'Optional file name stem for the saved image
|
|
94
|
+
description: 'Optional file name stem for the saved image.',
|
|
102
95
|
},
|
|
103
96
|
output_dir: {
|
|
104
97
|
type: 'string',
|
|
105
|
-
description: 'Optional custom directory to write images into
|
|
98
|
+
description: 'Optional custom directory to write images into.',
|
|
106
99
|
},
|
|
107
100
|
style_preset: {
|
|
108
101
|
type: 'string',
|
|
@@ -111,11 +104,11 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
111
104
|
palette_colors: {
|
|
112
105
|
type: 'array',
|
|
113
106
|
items: { type: 'string' },
|
|
114
|
-
description: 'Optional
|
|
107
|
+
description: 'Optional hex color codes (#1e1b4b) to harmonize palette.',
|
|
115
108
|
},
|
|
116
109
|
count: {
|
|
117
110
|
type: 'integer',
|
|
118
|
-
description: '
|
|
111
|
+
description: 'Variations count (1-4, default 1).',
|
|
119
112
|
},
|
|
120
113
|
prompts: {
|
|
121
114
|
type: 'array',
|
|
@@ -125,60 +118,60 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
125
118
|
{ type: 'object', additionalProperties: false, properties: { text: { type: 'string' } } },
|
|
126
119
|
],
|
|
127
120
|
},
|
|
128
|
-
description: 'Optional list of prompts
|
|
121
|
+
description: 'Optional list of prompts for batch generation.',
|
|
129
122
|
},
|
|
130
123
|
negative_prompt: {
|
|
131
124
|
type: 'string',
|
|
132
|
-
description: 'Optional text describing what NOT to draw
|
|
125
|
+
description: 'Optional text describing what NOT to draw.',
|
|
133
126
|
},
|
|
134
127
|
guidance_scale: {
|
|
135
128
|
type: 'number',
|
|
136
|
-
description: 'Optional prompt adherence (
|
|
129
|
+
description: 'Optional prompt adherence (1-20).',
|
|
137
130
|
},
|
|
138
131
|
quality: {
|
|
139
132
|
type: 'string',
|
|
140
133
|
enum: ['auto', 'low', 'medium', 'high'],
|
|
141
|
-
description: 'Optional quality for providers
|
|
134
|
+
description: 'Optional quality for supported providers.',
|
|
142
135
|
},
|
|
143
136
|
style: {
|
|
144
137
|
type: 'string',
|
|
145
|
-
description: 'Optional style preset for providers
|
|
138
|
+
description: 'Optional style preset for supported providers.',
|
|
146
139
|
},
|
|
147
140
|
source_image: {
|
|
148
141
|
type: 'string',
|
|
149
|
-
description: 'Optional
|
|
142
|
+
description: 'Optional source image path or attachment ID for img2img.',
|
|
150
143
|
},
|
|
151
144
|
mask: {
|
|
152
145
|
type: 'string',
|
|
153
|
-
description: 'Optional path or attachment
|
|
146
|
+
description: 'Optional inpaint mask path or attachment ID.',
|
|
154
147
|
},
|
|
155
148
|
strength: {
|
|
156
149
|
type: 'number',
|
|
157
|
-
description: 'Optional edit strength (0-1)
|
|
150
|
+
description: 'Optional edit strength (0-1).',
|
|
158
151
|
},
|
|
159
152
|
quality_gate: {
|
|
160
153
|
type: 'boolean',
|
|
161
|
-
description: 'Optional
|
|
154
|
+
description: 'Optional automated quality gate override.',
|
|
162
155
|
},
|
|
163
156
|
force: {
|
|
164
157
|
type: 'boolean',
|
|
165
|
-
description: '
|
|
158
|
+
description: 'Bypass cache and force new generation.',
|
|
166
159
|
},
|
|
167
160
|
reference_image: {
|
|
168
161
|
type: 'string',
|
|
169
|
-
description: 'Optional reference image
|
|
162
|
+
description: 'Optional reference image path or URL for consistency.',
|
|
170
163
|
},
|
|
171
164
|
reference_strength: {
|
|
172
165
|
type: 'number',
|
|
173
|
-
description: '
|
|
166
|
+
description: 'Strength of reference influence (0.1-1.0).',
|
|
174
167
|
},
|
|
175
168
|
face_reference: {
|
|
176
169
|
type: 'string',
|
|
177
|
-
description: 'Portrait reference image
|
|
170
|
+
description: 'Portrait reference image for facial identity preservation.',
|
|
178
171
|
},
|
|
179
172
|
face_strength: {
|
|
180
173
|
type: 'number',
|
|
181
|
-
description: 'Facial identity similarity weight
|
|
174
|
+
description: 'Facial identity similarity weight (0.1-1.0).',
|
|
182
175
|
},
|
|
183
176
|
},
|
|
184
177
|
output: {
|
|
@@ -344,7 +337,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
344
337
|
// Subscriptions service is optional: without it, subscription providers
|
|
345
338
|
// gracefully decline while other providers continue functioning.
|
|
346
339
|
let subscriptionImages
|
|
347
|
-
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch { subscriptionImages = undefined }
|
|
340
|
+
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch (_err) { subscriptionImages = undefined }
|
|
348
341
|
|
|
349
342
|
const source = args.source_image ? await resolveSource(ctx, exec, args.source_image) : undefined
|
|
350
343
|
const mask = args.mask ? await resolveSource(ctx, exec, args.mask) : undefined
|
|
@@ -358,7 +351,8 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
358
351
|
if (faceRefInput) {
|
|
359
352
|
try {
|
|
360
353
|
resolvedFaceRef = await resolveSource(ctx, exec, faceRefInput)
|
|
361
|
-
} catch {
|
|
354
|
+
} catch (_err) {
|
|
355
|
+
/* non-fatal: face_reference resolve fallback */
|
|
362
356
|
resolvedFaceRef = { url: faceRefInput }
|
|
363
357
|
}
|
|
364
358
|
const facePlan = buildFaceConsistencyPrompt({
|
|
@@ -651,5 +645,7 @@ export function registerGenerationTools(ctx, deps) {
|
|
|
651
645
|
}),
|
|
652
646
|
)
|
|
653
647
|
}, 'dsh-image-gen: tool generate_image')
|
|
654
|
-
|
|
648
|
+
if (!options.coreOnly) {
|
|
649
|
+
registerGenerationPackTool(ctx, deps)
|
|
650
|
+
}
|
|
655
651
|
}
|
package/lib/tools/inspect.js
CHANGED
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
estimateSharpnessAndVariance,
|
|
8
8
|
} from '../providers.js'
|
|
9
9
|
|
|
10
|
-
export function registerInspectTools(ctx, deps) {
|
|
10
|
+
export function registerInspectTools(ctx, deps, options = {}) {
|
|
11
11
|
const { resolveSource } = deps
|
|
12
|
-
|
|
13
|
-
ctx.
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
if (!options.coreOnly) {
|
|
13
|
+
ctx.effect(() => {
|
|
14
|
+
ctx.tools.register(
|
|
15
|
+
defineTool({
|
|
16
|
+
name: 'compare_images',
|
|
16
17
|
description: 'Compare two images (paths or attachment ids) and report the fraction of differing pixels. Use to check how much an edit changed the original.',
|
|
17
18
|
parameters: {
|
|
18
19
|
image_a: { type: 'string', required: true, description: 'Path or attachment id of the first image.' },
|
|
@@ -37,6 +38,7 @@ export function registerInspectTools(ctx, deps) {
|
|
|
37
38
|
}),
|
|
38
39
|
)
|
|
39
40
|
}, 'dsh-image-gen: tool compare_images')
|
|
41
|
+
}
|
|
40
42
|
ctx.effect(() => {
|
|
41
43
|
ctx.tools.register(
|
|
42
44
|
defineTool({
|
|
@@ -126,7 +126,7 @@ export function registerStyleMatrixTools(ctx, deps) {
|
|
|
126
126
|
|
|
127
127
|
const provider = PROVIDER_KEYS.includes(cfg.provider) ? cfg.provider : 'fal'
|
|
128
128
|
let subscriptionImages
|
|
129
|
-
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch { subscriptionImages = undefined }
|
|
129
|
+
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch (_err) { subscriptionImages = undefined }
|
|
130
130
|
|
|
131
131
|
const chain = resolveFallbackChain(provider, cfg.fallbackProviders, PROVIDER_KEYS)
|
|
132
132
|
const quadrantIds = ['A', 'B', 'C', 'D']
|
package/lib/tools/ui-asset.js
CHANGED
|
@@ -145,7 +145,7 @@ export function registerUiAssetTools(ctx, deps) {
|
|
|
145
145
|
|
|
146
146
|
const provider = PROVIDER_KEYS.includes(cfg.provider) ? cfg.provider : 'fal'
|
|
147
147
|
let subscriptionImages
|
|
148
|
-
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch { subscriptionImages = undefined }
|
|
148
|
+
try { subscriptionImages = ctx.get && ctx.get('subscriptionImages') } catch (_err) { subscriptionImages = undefined }
|
|
149
149
|
|
|
150
150
|
const providers = makeProviders(
|
|
151
151
|
{ fetchImpl: fetch, resolveKey: (ref) => resolveApiKey(ctx, ref), cfg, subscriptionImages },
|
package/lib/tools/vision-ocr.js
CHANGED
|
@@ -39,7 +39,7 @@ export function registerVisionOcrTools(ctx, deps) {
|
|
|
39
39
|
},
|
|
40
40
|
replacements: {
|
|
41
41
|
type: 'array',
|
|
42
|
-
description: 'Array of text replacements: [{ find: "Old Text", replace: "
|
|
42
|
+
description: 'Array of text replacements: [{ find: "Old Text", replace: "New Text", bbox: [x1,y1,x2,y2] }].',
|
|
43
43
|
items: {
|
|
44
44
|
type: 'object',
|
|
45
45
|
additionalProperties: true,
|
|
@@ -109,11 +109,7 @@ export function registerVisionOcrTools(ctx, deps) {
|
|
|
109
109
|
},
|
|
110
110
|
},
|
|
111
111
|
},
|
|
112
|
-
render: (
|
|
113
|
-
renderToolOutput(result, renderCtx, {
|
|
114
|
-
title: (res) => `Text Replaced (${res.replacedCount || 0} inscriptions)`,
|
|
115
|
-
subtitle: (res) => (res.bboxes?.length ? `Detected regions: ${res.bboxes.length}` : ''),
|
|
116
|
-
}),
|
|
112
|
+
render: (_args, value) => renderToolOutput(value),
|
|
117
113
|
},
|
|
118
114
|
async execute(args, execCtx) {
|
|
119
115
|
const cfg = live?.value || (typeof live === 'function' ? live() : {})
|
|
@@ -145,8 +141,8 @@ export function registerVisionOcrTools(ctx, deps) {
|
|
|
145
141
|
if (gRes?.bbox && gRes.bbox.length === 4) {
|
|
146
142
|
bbox = gRes.bbox
|
|
147
143
|
}
|
|
148
|
-
} catch {
|
|
149
|
-
|
|
144
|
+
} catch (_err) {
|
|
145
|
+
/* non-fatal: vision_ground fallback */
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
148
|
}
|
|
@@ -168,7 +164,7 @@ export function registerVisionOcrTools(ctx, deps) {
|
|
|
168
164
|
const defaultBox = [150, 420, 850, 520]
|
|
169
165
|
bboxes.push(defaultBox)
|
|
170
166
|
overlayItems.push({
|
|
171
|
-
text:
|
|
167
|
+
text: 'Localized Text',
|
|
172
168
|
bbox: defaultBox,
|
|
173
169
|
color: args.font_color || '#FFFFFF',
|
|
174
170
|
})
|
|
@@ -216,8 +212,8 @@ export function registerVisionOcrTools(ctx, deps) {
|
|
|
216
212
|
)
|
|
217
213
|
inpaintedBytes = inpaintResult.bytes
|
|
218
214
|
inpaintedUrl = inpaintResult.url
|
|
219
|
-
} catch {
|
|
220
|
-
|
|
215
|
+
} catch (_err) {
|
|
216
|
+
/* non-fatal: inpaint provider unavailable; use source bytes */
|
|
221
217
|
inpaintedBytes = source.bytes
|
|
222
218
|
}
|
|
223
219
|
|
package/lib/updater.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, statSync } from 'node:fs'
|
|
3
3
|
import { readFile } from 'node:fs/promises'
|
|
4
4
|
import { homedir } from 'node:os'
|
|
5
5
|
import { basename, dirname, isAbsolute, resolve } from 'node:path'
|
|
@@ -10,6 +10,60 @@ import { isLoopbackAddress, isPrivateLanAddress, extractHostName } from './secur
|
|
|
10
10
|
* Implements the unified DSH plugin updater specification.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
export function isPidAlive(pid) {
|
|
15
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0)
|
|
18
|
+
return true
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return err?.code === 'EPERM'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function checkAndCleanProfileLock(profileDir) {
|
|
25
|
+
if (!profileDir) return { locked: false }
|
|
26
|
+
const lockPath = resolve(profileDir, 'package.json.lock')
|
|
27
|
+
if (!existsSync(lockPath)) return { locked: false }
|
|
28
|
+
let pid = null
|
|
29
|
+
try {
|
|
30
|
+
const raw = readFileSync(lockPath, 'utf8').trim()
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(raw)
|
|
33
|
+
pid = parsed?.pid ?? Number(raw)
|
|
34
|
+
} catch (_e) {
|
|
35
|
+
pid = parseInt(raw, 10)
|
|
36
|
+
}
|
|
37
|
+
} catch (_err) {
|
|
38
|
+
/* lock unreadable */
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
42
|
+
if (isPidAlive(pid)) {
|
|
43
|
+
return { locked: true, pid }
|
|
44
|
+
}
|
|
45
|
+
// Stale lock: PID is not running
|
|
46
|
+
try {
|
|
47
|
+
unlinkSync(lockPath)
|
|
48
|
+
} catch (_err) {
|
|
49
|
+
/* non-fatal */
|
|
50
|
+
}
|
|
51
|
+
return { locked: false, staleCleaned: true, pid }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Lockfile without valid PID: check age (> 10 min)
|
|
55
|
+
try {
|
|
56
|
+
const stat = statSync(lockPath)
|
|
57
|
+
if (Date.now() - stat.mtimeMs > 10 * 60_000) {
|
|
58
|
+
unlinkSync(lockPath)
|
|
59
|
+
return { locked: false, staleCleaned: true }
|
|
60
|
+
}
|
|
61
|
+
return { locked: true, pid: null }
|
|
62
|
+
} catch (_err) {
|
|
63
|
+
return { locked: false }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
13
67
|
const UPDATE_HEADER = 'x-dsh-plugin-update'
|
|
14
68
|
const UPDATE_TIMEOUT_MS = 10 * 60_000
|
|
15
69
|
const VERSION_CACHE_MS = 5 * 60_000
|
|
@@ -39,7 +93,7 @@ export function isTrustedUpdateRequest(request) {
|
|
|
39
93
|
(isLoopbackAddress(hostName) || isPrivateLanAddress(hostName)) &&
|
|
40
94
|
url.host.toLowerCase() === host.toLowerCase()
|
|
41
95
|
)
|
|
42
|
-
} catch {
|
|
96
|
+
} catch (_err) {
|
|
43
97
|
return false
|
|
44
98
|
}
|
|
45
99
|
}
|
|
@@ -213,7 +267,6 @@ async function installExact(target, packageSpec, options) {
|
|
|
213
267
|
'--profile',
|
|
214
268
|
target.profileName,
|
|
215
269
|
'add',
|
|
216
|
-
'--config.minimumReleaseAge=0',
|
|
217
270
|
packageSpec,
|
|
218
271
|
`--registry=${options.registry ?? 'https://registry.npmjs.org/'}`,
|
|
219
272
|
],
|
|
@@ -232,9 +285,15 @@ async function installExact(target, packageSpec, options) {
|
|
|
232
285
|
detail = (detail + String(chunk)).slice(-4000)
|
|
233
286
|
})
|
|
234
287
|
const timer = setTimeout(() => {
|
|
235
|
-
|
|
288
|
+
try {
|
|
289
|
+
child.kill('SIGTERM')
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
try { child.kill('SIGKILL') } catch (_err) {}
|
|
292
|
+
checkAndCleanProfileLock(target.profileDir)
|
|
293
|
+
}, 1500)
|
|
294
|
+
} catch (_err) {}
|
|
236
295
|
reject(new Error('Update timed out; use the normal DSH update flow.'))
|
|
237
|
-
}, UPDATE_TIMEOUT_MS)
|
|
296
|
+
}, options.timeoutMs ?? UPDATE_TIMEOUT_MS)
|
|
238
297
|
child.once('error', (error) => {
|
|
239
298
|
clearTimeout(timer)
|
|
240
299
|
reject(error)
|
|
@@ -290,6 +349,11 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
290
349
|
json(response, 409, { error: 'This plugin is already updating.' })
|
|
291
350
|
return
|
|
292
351
|
}
|
|
352
|
+
const lockState = checkAndCleanProfileLock(target.profileDir)
|
|
353
|
+
if (lockState.locked) {
|
|
354
|
+
json(response, 409, { error: `Another package installation is currently in progress${lockState.pid ? ` (PID ${lockState.pid})` : ''}.` })
|
|
355
|
+
return
|
|
356
|
+
}
|
|
293
357
|
installing = true
|
|
294
358
|
try {
|
|
295
359
|
const before = await status(options, target)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-image-gen",
|
|
3
|
-
"version": "0.11.
|
|
4
|
-
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers
|
|
3
|
+
"version": "0.11.14",
|
|
4
|
+
"description": "Image generation for DeepSeek Harness: a generate_image tool with pluggable providers \u2014 the FAL queue, any OpenAI-compatible images API, or a ChatGPT/Grok subscription with no API key at all. The picture is shown inline in the conversation; the model receives either a link (works with any chat model) or the image itself (needs dsh-vision-bridge or a vision-capable model).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
|
-
"test": "node scripts/build-client.mjs --check && node --check lib/client.js && node --test test/*.test.mjs",
|
|
72
|
+
"test": "node scripts/build-client.mjs --check && node --check lib/client.js && node --import ./test/_setup.mjs --test test/*.test.mjs",
|
|
73
73
|
"build:client": "node scripts/build-client.mjs",
|
|
74
74
|
"publish:github": "bash scripts/publish-github.sh",
|
|
75
75
|
"build:client:check": "node scripts/build-client.mjs --check"
|