@linxin666/dsh-pet 0.3.10 → 0.3.11
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/README.i18n.yaml +2 -2
- package/README.md +11 -1
- package/README.zh.md +11 -1
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/types/host/run-guarded.d.ts +40 -0
- package/lib/types/host/run-guarded.d.ts.map +1 -0
- package/lib/types/host/run-guarded.js +69 -0
- package/package.json +14 -14
- package/src/client/index.ts +2 -2
- package/src/host/run-guarded.ts +76 -0
- package/src/registry.test.ts +13 -4
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async-boundary guard shared by the plugin family's Host halves: every
|
|
3
|
+
* fire-and-forget promise chain and callback the host runtime does not own
|
|
4
|
+
* (route handlers, timers, event listeners, spawned work) funnels through
|
|
5
|
+
* these helpers. The dsh host installs a process-level fail-loud guard that
|
|
6
|
+
* turns ANY unhandled promise rejection into a whole-process exit — one
|
|
7
|
+
* plugin's stray rejection would otherwise take every plugin down. These
|
|
8
|
+
* helpers exist so that failure mode is structurally impossible in family
|
|
9
|
+
* code: the rejection becomes a logged error at the plugin boundary instead.
|
|
10
|
+
*
|
|
11
|
+
* Complements the aggregate's shell isolation (packages/dsh-web-all): the
|
|
12
|
+
* shell contains import/activation failures at boot; runGuarded contains
|
|
13
|
+
* run-time failures after activation.
|
|
14
|
+
* @module dsh-web-shared/host/run-guarded
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Run one async operation, logging (never propagating) a rejection. Use for
|
|
18
|
+
* promises whose lifecycle the caller does not await: spawned work, retry
|
|
19
|
+
* loops, background flushes. Returns the original promise so callers can
|
|
20
|
+
* still chain when they want to.
|
|
21
|
+
* @param promise - the work to shield; any thenable.
|
|
22
|
+
* @param label - log prefix naming the work site.
|
|
23
|
+
* @param log - error sink; defaults to console.error.
|
|
24
|
+
* @returns the input promise (rejection already consumed).
|
|
25
|
+
*/
|
|
26
|
+
export declare function runGuarded<T>(promise: PromiseLike<T>, label: string, log?: (error: unknown) => void): PromiseLike<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Wrap one callback so every invocation is individually guarded: a rejection
|
|
29
|
+
* inside one call is logged and swallowed instead of escaping into whatever
|
|
30
|
+
* infrastructure invoked the callback (HTTP server, EventEmitter, interval).
|
|
31
|
+
* Sync throws are caught identically; a returned promise is replaced by
|
|
32
|
+
* `undefined` after guarding (callers that need the original rejection should
|
|
33
|
+
* await inside their own try/catch instead).
|
|
34
|
+
* @param label - log prefix naming the callback site.
|
|
35
|
+
* @param handler - the work to guard.
|
|
36
|
+
* @param log - error sink; defaults to console.error.
|
|
37
|
+
* @returns a wrapped callback with the same parameter list.
|
|
38
|
+
*/
|
|
39
|
+
export declare function guardedHandler<A extends unknown[]>(label: string, handler: (...args: A) => unknown, log?: (error: unknown) => void): (...args: A) => unknown;
|
|
40
|
+
//# sourceMappingURL=run-guarded.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-guarded.d.ts","sourceRoot":"","sources":["../../../src/host/run-guarded.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAOH;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAoB,GAAG,WAAW,CAAC,CAAC,CAAC,CAKnI;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,EAChD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,EAChC,GAAG,GAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAoB,GAC5C,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAgBzB"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/run-guarded.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Async-boundary guard shared by the plugin family's Host halves: every
|
|
4
|
+
* fire-and-forget promise chain and callback the host runtime does not own
|
|
5
|
+
* (route handlers, timers, event listeners, spawned work) funnels through
|
|
6
|
+
* these helpers. The dsh host installs a process-level fail-loud guard that
|
|
7
|
+
* turns ANY unhandled promise rejection into a whole-process exit — one
|
|
8
|
+
* plugin's stray rejection would otherwise take every plugin down. These
|
|
9
|
+
* helpers exist so that failure mode is structurally impossible in family
|
|
10
|
+
* code: the rejection becomes a logged error at the plugin boundary instead.
|
|
11
|
+
*
|
|
12
|
+
* Complements the aggregate's shell isolation (packages/dsh-web-all): the
|
|
13
|
+
* shell contains import/activation failures at boot; runGuarded contains
|
|
14
|
+
* run-time failures after activation.
|
|
15
|
+
* @module dsh-web-shared/host/run-guarded
|
|
16
|
+
*/
|
|
17
|
+
/** Format one failure line for logging. */
|
|
18
|
+
function formatFailure(label, error) {
|
|
19
|
+
return new Error(`[${label}] unhandled async failure: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run one async operation, logging (never propagating) a rejection. Use for
|
|
23
|
+
* promises whose lifecycle the caller does not await: spawned work, retry
|
|
24
|
+
* loops, background flushes. Returns the original promise so callers can
|
|
25
|
+
* still chain when they want to.
|
|
26
|
+
* @param promise - the work to shield; any thenable.
|
|
27
|
+
* @param label - log prefix naming the work site.
|
|
28
|
+
* @param log - error sink; defaults to console.error.
|
|
29
|
+
* @returns the input promise (rejection already consumed).
|
|
30
|
+
*/
|
|
31
|
+
export function runGuarded(promise, label, log = console.error) {
|
|
32
|
+
void Promise.resolve(promise).catch(error => {
|
|
33
|
+
log(formatFailure(label, error));
|
|
34
|
+
});
|
|
35
|
+
return promise;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Wrap one callback so every invocation is individually guarded: a rejection
|
|
39
|
+
* inside one call is logged and swallowed instead of escaping into whatever
|
|
40
|
+
* infrastructure invoked the callback (HTTP server, EventEmitter, interval).
|
|
41
|
+
* Sync throws are caught identically; a returned promise is replaced by
|
|
42
|
+
* `undefined` after guarding (callers that need the original rejection should
|
|
43
|
+
* await inside their own try/catch instead).
|
|
44
|
+
* @param label - log prefix naming the callback site.
|
|
45
|
+
* @param handler - the work to guard.
|
|
46
|
+
* @param log - error sink; defaults to console.error.
|
|
47
|
+
* @returns a wrapped callback with the same parameter list.
|
|
48
|
+
*/
|
|
49
|
+
export function guardedHandler(label, handler, log = console.error) {
|
|
50
|
+
return (...args) => {
|
|
51
|
+
try {
|
|
52
|
+
const result = handler(...args);
|
|
53
|
+
if (isPromiseLike(result)) {
|
|
54
|
+
void Promise.resolve(result).catch(error => {
|
|
55
|
+
log(formatFailure(label, error));
|
|
56
|
+
});
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
log(formatFailure(label, error));
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function isPromiseLike(value) {
|
|
68
|
+
return typeof value === 'object' && value !== null && typeof value.then === 'function';
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"license": "Apache-2.0",
|
|
41
41
|
"dsh": {
|
|
42
42
|
"engines": {
|
|
43
|
-
"dsh": ">=0.1.2-alpha.
|
|
43
|
+
"dsh": ">=0.1.2-alpha.3"
|
|
44
44
|
},
|
|
45
45
|
"bundle": {
|
|
46
46
|
"patch": "./cordis.patch.yml"
|
|
@@ -65,18 +65,18 @@
|
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
68
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.
|
|
69
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.
|
|
70
|
-
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.
|
|
71
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.
|
|
72
|
-
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.
|
|
77
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.
|
|
78
|
-
"@deepseek-ai/dsh-session": "^0.1.2-alpha.
|
|
79
|
-
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.
|
|
68
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.3",
|
|
69
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.3",
|
|
70
|
+
"@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.3",
|
|
71
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.3",
|
|
72
|
+
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.3",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.3",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.3",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.3",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.3",
|
|
77
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.3",
|
|
78
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
|
|
79
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.3",
|
|
80
80
|
"@testing-library/dom": "^10.4.1",
|
|
81
81
|
"@testing-library/react": "^16.3.2",
|
|
82
82
|
"@types/node": "^22.20.0",
|
package/src/client/index.ts
CHANGED
|
@@ -296,8 +296,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
296
296
|
|
|
297
297
|
const openSession = (sessionId: string): void => {
|
|
298
298
|
const list = sessions.list.getSnapshot()
|
|
299
|
-
if (list.byId
|
|
300
|
-
sessions.open(sessionId as
|
|
299
|
+
if ((list.byId as any)[sessionId] === undefined) return
|
|
300
|
+
sessions.open(sessionId as never)
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
const injected = (): PetInjected => ({
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/run-guarded.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Async-boundary guard shared by the plugin family's Host halves: every
|
|
4
|
+
* fire-and-forget promise chain and callback the host runtime does not own
|
|
5
|
+
* (route handlers, timers, event listeners, spawned work) funnels through
|
|
6
|
+
* these helpers. The dsh host installs a process-level fail-loud guard that
|
|
7
|
+
* turns ANY unhandled promise rejection into a whole-process exit — one
|
|
8
|
+
* plugin's stray rejection would otherwise take every plugin down. These
|
|
9
|
+
* helpers exist so that failure mode is structurally impossible in family
|
|
10
|
+
* code: the rejection becomes a logged error at the plugin boundary instead.
|
|
11
|
+
*
|
|
12
|
+
* Complements the aggregate's shell isolation (packages/dsh-web-all): the
|
|
13
|
+
* shell contains import/activation failures at boot; runGuarded contains
|
|
14
|
+
* run-time failures after activation.
|
|
15
|
+
* @module dsh-web-shared/host/run-guarded
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Format one failure line for logging. */
|
|
19
|
+
function formatFailure(label: string, error: unknown): Error {
|
|
20
|
+
return new Error(`[${label}] unhandled async failure: ${error instanceof Error ? error.stack ?? error.message : String(error)}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run one async operation, logging (never propagating) a rejection. Use for
|
|
25
|
+
* promises whose lifecycle the caller does not await: spawned work, retry
|
|
26
|
+
* loops, background flushes. Returns the original promise so callers can
|
|
27
|
+
* still chain when they want to.
|
|
28
|
+
* @param promise - the work to shield; any thenable.
|
|
29
|
+
* @param label - log prefix naming the work site.
|
|
30
|
+
* @param log - error sink; defaults to console.error.
|
|
31
|
+
* @returns the input promise (rejection already consumed).
|
|
32
|
+
*/
|
|
33
|
+
export function runGuarded<T>(promise: PromiseLike<T>, label: string, log: (error: unknown) => void = console.error): PromiseLike<T> {
|
|
34
|
+
void Promise.resolve(promise).catch(error => {
|
|
35
|
+
log(formatFailure(label, error))
|
|
36
|
+
})
|
|
37
|
+
return promise
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Wrap one callback so every invocation is individually guarded: a rejection
|
|
42
|
+
* inside one call is logged and swallowed instead of escaping into whatever
|
|
43
|
+
* infrastructure invoked the callback (HTTP server, EventEmitter, interval).
|
|
44
|
+
* Sync throws are caught identically; a returned promise is replaced by
|
|
45
|
+
* `undefined` after guarding (callers that need the original rejection should
|
|
46
|
+
* await inside their own try/catch instead).
|
|
47
|
+
* @param label - log prefix naming the callback site.
|
|
48
|
+
* @param handler - the work to guard.
|
|
49
|
+
* @param log - error sink; defaults to console.error.
|
|
50
|
+
* @returns a wrapped callback with the same parameter list.
|
|
51
|
+
*/
|
|
52
|
+
export function guardedHandler<A extends unknown[]>(
|
|
53
|
+
label: string,
|
|
54
|
+
handler: (...args: A) => unknown,
|
|
55
|
+
log: (error: unknown) => void = console.error,
|
|
56
|
+
): (...args: A) => unknown {
|
|
57
|
+
return (...args: A) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = handler(...args)
|
|
60
|
+
if (isPromiseLike(result)) {
|
|
61
|
+
void Promise.resolve(result).catch(error => {
|
|
62
|
+
log(formatFailure(label, error))
|
|
63
|
+
})
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
return result
|
|
67
|
+
} catch (error) {
|
|
68
|
+
log(formatFailure(label, error))
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
75
|
+
return typeof value === 'object' && value !== null && typeof (value as PromiseLike<unknown>).then === 'function'
|
|
76
|
+
}
|
package/src/registry.test.ts
CHANGED
|
@@ -314,13 +314,15 @@ describe('loadPetRegistry', () => {
|
|
|
314
314
|
dshPetsDir: '',
|
|
315
315
|
})
|
|
316
316
|
|
|
317
|
-
// The repo checkout also resolves miku (frames2d gameplay pet)
|
|
318
|
-
// assets/; the npm files
|
|
319
|
-
//
|
|
320
|
-
//
|
|
317
|
+
// The repo checkout also resolves miku (frames2d gameplay pet) and
|
|
318
|
+
// starry-doll (community sprite2d pet) from assets/; the npm files
|
|
319
|
+
// whitelist excludes them (Workshop delivery), so npm installs see the
|
|
320
|
+
// three atlas pets until a Workshop install lands them under
|
|
321
|
+
// $DSH_HOME/pets.
|
|
321
322
|
expect(registry.entries.map(entry => entry.id)).toEqual([
|
|
322
323
|
'miku',
|
|
323
324
|
'ouo-neko',
|
|
325
|
+
'starry-doll',
|
|
324
326
|
'whale-girl',
|
|
325
327
|
'whale-girl-refined',
|
|
326
328
|
])
|
|
@@ -331,6 +333,13 @@ describe('loadPetRegistry', () => {
|
|
|
331
333
|
rows: [6, 8, 8, 4, 5, 8, 6, 6, 6],
|
|
332
334
|
})
|
|
333
335
|
expect(existsSync(petAtlasFile(registry.byId('ouo-neko')!))).toBe(true)
|
|
336
|
+
expect(registry.byId('starry-doll')).toMatchObject({
|
|
337
|
+
displayName: '星夜人偶',
|
|
338
|
+
atlasRows: 9,
|
|
339
|
+
columns: 8,
|
|
340
|
+
rows: [6, 8, 8, 4, 5, 8, 6, 6, 6],
|
|
341
|
+
})
|
|
342
|
+
expect(existsSync(petAtlasFile(registry.byId('starry-doll')!))).toBe(true)
|
|
334
343
|
expect(registry.byId('whale-girl')?.displayName).toBe('鲸鱼娘(原版)')
|
|
335
344
|
expect(registry.byId('whale-girl-refined')?.displayName).toBe('鲸鱼娘(精致版)')
|
|
336
345
|
expect(existsSync(petAtlasFile(registry.byId('whale-girl-refined')!))).toBe(true)
|