dsh-plugin-xujie-memory 0.5.0 → 0.7.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 (84) hide show
  1. package/ACCEPTANCE.md +37 -0
  2. package/BUILD-PROVENANCE.json +53 -0
  3. package/CHANGELOG.md +40 -1
  4. package/LICENSE +21 -21
  5. package/README.md +67 -17
  6. package/README_EN.md +54 -8
  7. package/cordis.patch.yml +49 -49
  8. package/lib/client.js +17219 -0
  9. package/lib/controller.js +307 -0
  10. package/lib/index.js +200 -14
  11. package/lib/typert.host.d.ts +3 -0
  12. package/lib/typert.host.js +15104 -0
  13. package/lib/typert.remote-client.d.ts +40 -0
  14. package/lib/typert.remote-client.d.ts.map +1 -0
  15. package/lib/typert.remote-client.js +15093 -0
  16. package/lib/types.d.ts +13 -0
  17. package/lib/types.js +1 -0
  18. package/package.json +49 -5
  19. package/python/specs/characters/aloof.json +14 -14
  20. package/python/specs/characters/gentle.json +14 -14
  21. package/python/specs/characters/tsundere.json +14 -14
  22. package/python/specs/debt.v1.json +31 -31
  23. package/python/specs/nickname.v1.json +16 -16
  24. package/python/specs/preference.v1.json +16 -16
  25. package/python/xujie_memory/__init__.py +123 -123
  26. package/python/xujie_memory/admission.py +211 -211
  27. package/python/xujie_memory/audit.py +377 -377
  28. package/python/xujie_memory/benchmark.py +281 -281
  29. package/python/xujie_memory/character.py +199 -199
  30. package/python/xujie_memory/console.py +41 -41
  31. package/python/xujie_memory/consolidate.py +356 -356
  32. package/python/xujie_memory/due.py +121 -121
  33. package/python/xujie_memory/emotion.py +573 -573
  34. package/python/xujie_memory/engine.py +1333 -1312
  35. package/python/xujie_memory/errors.py +98 -98
  36. package/python/xujie_memory/importance.py +104 -104
  37. package/python/xujie_memory/judge.py +350 -350
  38. package/python/xujie_memory/management.py +203 -0
  39. package/python/xujie_memory/memory.py +71 -10
  40. package/python/xujie_memory/orchestrator.py +690 -666
  41. package/python/xujie_memory/phrasing.py +292 -292
  42. package/python/xujie_memory/privacy.py +105 -0
  43. package/python/xujie_memory/schema.sql +492 -480
  44. package/python/xujie_memory/server.py +76 -0
  45. package/python/xujie_memory/specs.py +170 -170
  46. package/python/xujie_memory/summary.py +238 -238
  47. package/scripts/build.mjs +19 -0
  48. package/scripts/prepack.mjs +9 -0
  49. package/scripts/test-all.mjs +9 -0
  50. package/scripts/test-node.mjs +10 -0
  51. package/src/client/MemoryPanel.tsx +536 -0
  52. package/src/client/NeuralField.tsx +88 -0
  53. package/src/client/css.d.ts +4 -0
  54. package/src/client/icons.tsx +28 -0
  55. package/src/client/index.tsx +59 -0
  56. package/src/client/memory-adapter.ts +67 -0
  57. package/src/client/model.ts +79 -0
  58. package/src/client/neural/engine.ts +999 -0
  59. package/src/client/neural/morphology.ts +219 -0
  60. package/src/client/panel.css +337 -0
  61. package/src/client/types.ts +6 -0
  62. package/src/controller.ts +88 -0
  63. package/src/generated/typert.host.js +449 -0
  64. package/src/generated/typert.remote-client.js +438 -0
  65. package/src/index.ts +2 -0
  66. package/src/types.ts +13 -0
  67. package/test/cordis-fixture.mjs +5 -0
  68. package/test/management_regression.py +187 -0
  69. package/test/node-regression.entry.mjs +52 -0
  70. package/vendor/DSH-LICENSE.txt +21 -0
  71. package/vendor/THIRD-PARTY.md +6 -0
  72. package/vendor/dsh-protocol/index.ts +484 -0
  73. package/vendor/dsh-protocol/json-value.ts +53 -0
  74. package/vendor/dsh-protocol/owned-value.ts +39 -0
  75. package/vendor/dsh-protocol/principal.ts +17 -0
  76. package/vendor/dsh-protocol/remote-access.ts +38 -0
  77. package/vendor/dsh-protocol/remote-error.ts +49 -0
  78. package/vendor/dsh-protocol/remote-info.ts +161 -0
  79. package/vendor/dsh-protocol/types.ts +939 -0
  80. package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
  81. package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
  82. package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
  83. package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
  84. package/vendor/licenses/zod-MIT.txt +21 -0
@@ -0,0 +1,484 @@
1
+ /**
2
+ * Remote decorators and explicit Gateway bindings backed by versioned
3
+ * descriptors carried on decorated class prototypes. Strict reflection
4
+ * remains a Typert compiler responsibility.
5
+ * @module @deepseek-ai/dsh-typert-protocol
6
+ */
7
+
8
+ import { Context, Service } from '@deepseek-ai/cordis'
9
+ import type {
10
+ RemoteWireArgs,
11
+ TypertContextMap,
12
+ TypertResourceReader,
13
+ TypertResourceReaders,
14
+ } from './types.ts'
15
+
16
+ export { RemoteError, remoteErrorOf } from './remote-error.ts'
17
+ export { TYPERT_OWNED_VALUE, isTypertOwnedValue, typertOwnedValue } from './owned-value.ts'
18
+ export type { TypertOwnedValue } from './owned-value.ts'
19
+ export { isRemoteJsonValue, isRemoteUplinkItem } from './json-value.ts'
20
+ export { createTypertRemoteInfo, isTypertRemoteMethod } from './remote-info.ts'
21
+ export { principalOf } from './principal.ts'
22
+ export type { RemoteAccessResource, RemoteAccessResourceKind, RemoteAccessResourceOf } from './remote-access.ts'
23
+
24
+ const TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/
25
+
26
+ /**
27
+ * Test one generated Remote name against the Connection endpoint grammar.
28
+ * @param value - namespace, method, lookup, or Context segment.
29
+ * @returns whether the value can cross the shared RPC carrier unchanged.
30
+ */
31
+ export function isTypertRemoteSegment(value: string): boolean {
32
+ return value !== '.' && value !== '..' && TYPERT_REMOTE_SEGMENT_PATTERN.test(value)
33
+ }
34
+
35
+ export type {
36
+ InvocationDescriptor,
37
+ InvocationParameterDescriptor,
38
+ InvocationSourceLocation,
39
+ PeerId,
40
+ PeerScope,
41
+ Principal,
42
+ PrincipalMap,
43
+ RemoteDecoded,
44
+ RemoteErrorCode,
45
+ RemoteErrorDetailsMap,
46
+ RemoteFailure,
47
+ RemoteInvocation,
48
+ RemoteResult,
49
+ RemoteStream,
50
+ RemoteStreamHandle,
51
+ RemoteUplinkWindow,
52
+ RemoteWireArgs,
53
+ RemoteWireValue,
54
+ TypertClientEventListener,
55
+ TypertClientRemote,
56
+ TypertClientContextAdapter,
57
+ TypertCodec,
58
+ TypertContext,
59
+ TypertContextMap,
60
+ TypertContextRegistry,
61
+ TypertContextWire,
62
+ TypertDisposer,
63
+ TypertForwardableEvent,
64
+ TypertForwardableEventEntry,
65
+ TypertHostContextAdapter,
66
+ TypertHostContextResolver,
67
+ TypertResource,
68
+ TypertResourceDeclaration,
69
+ TypertResourceEntry,
70
+ TypertResourceId,
71
+ TypertResourceKind,
72
+ TypertResourceMap,
73
+ TypertResourceReader,
74
+ TypertResourceReaders,
75
+ TypertResourceRegistry,
76
+ TypertLocalRegistry,
77
+ TypertLookup,
78
+ TypertLookupDefinition,
79
+ TypertLookupHost,
80
+ TypertLookupMap,
81
+ TypertLookupProvider,
82
+ TypertLookupResolver,
83
+ TypertLookupRegistry,
84
+ TypertLookupWire,
85
+ TypertRemoteScopeApi,
86
+ TypertRemoteScopeMap,
87
+ TypertRemoteScopeNamespace,
88
+ TypertRemoteContribution,
89
+ TypertRemoteInfo,
90
+ TypertRemoteInputOptions,
91
+ TypertRemoteMethod,
92
+ TypertRemoteValue,
93
+ TypertRemoteCodecSpec,
94
+ TypertRemoteParameterSpec,
95
+ TypertRemoteMethodSpec,
96
+ TypertRemoteSpec,
97
+ TypertRemoteEvent,
98
+ TypertRemoteEventSelection,
99
+ TypertRemoteMap,
100
+ TypertRemoteNamespace,
101
+ TypertRemoteNamespaceMap,
102
+ TypertRemoteRegistry,
103
+ TypertRegistryChange,
104
+ TypertRegistryListener,
105
+ TypertSchema,
106
+ TypertRegistryContract,
107
+ } from './types.ts'
108
+
109
+ /** Options for an explicit Service-to-Gateway binding. */
110
+ export interface TypertGatewayBindingOptions {
111
+ /** Wire namespace; defaults to the Cordis service key. */
112
+ readonly namespace?: string
113
+ }
114
+
115
+ /** Visible declaration that one Service participates in Typert Gateway export. */
116
+ export interface TypertGatewayBinding<Service extends object = object> {
117
+ readonly service: Service
118
+ readonly serviceKey: string
119
+ readonly namespace: string
120
+ }
121
+
122
+ /** Invocation mode recorded by a Remote method decorator. */
123
+ export type RemoteInvocationMarker =
124
+ | { readonly kind: 'direct' }
125
+ | { readonly kind: 'context'; readonly context: string }
126
+
127
+ /** One decorator marker discovered for a live Service instance. */
128
+ export interface RemoteMethodMarker {
129
+ /** Public instance method carrying the implementation. */
130
+ readonly method: string
131
+ /** Endpoint method when it differs from the implementation member. */
132
+ readonly exportName?: string
133
+ /** Stream methods yield many independently validated result items. */
134
+ readonly mode?: 'stream'
135
+ readonly invocation: RemoteInvocationMarker
136
+ /**
137
+ * Declared `resources` readers keyed by resource kind. Callers spread the
138
+ * method's wire arguments into each one and validate its result.
139
+ */
140
+ readonly resources?: Readonly<Record<string, TypertResourceReader>>
141
+ }
142
+
143
+ /**
144
+ * Explicit endpoint and resource choices for one Remote Scope method.
145
+ * @template WireArgs - the decorated method's positional parameters as the
146
+ * wire carries them; the default `never` admits readers of any tuple.
147
+ */
148
+ export interface RemoteScopeOptions<WireArgs extends readonly unknown[] = never> {
149
+ /** Exported endpoint method; omission retains the implementation member name. */
150
+ readonly name?: string
151
+ /**
152
+ * Per-kind readers of the resources one call touches. Each receives the
153
+ * method's arguments as its own parameters, JSON arguments as their Typert
154
+ * codecs validated them, a lookup parameter as its wire identity and a
155
+ * cancellation parameter as the call's signal, so it performs plain reads
156
+ * only, and returns one identity, several, or `undefined` for none. A
157
+ * throw, an empty list, or an identity other than a non-empty string
158
+ * refuses the call. For a kind without a reader, the Gateway reads the
159
+ * identity of a scoped Context whose Host adapter declares that `resource`
160
+ * and of lookup parameters declaring it, else the field registered in
161
+ * `typert.resources`: a top-level argument or, failing that, the first
162
+ * object argument. A call that carries no identity touches `global`. The
163
+ * readers and these defaults together must name every resource the method
164
+ * accesses: access control never checks a resource they omit.
165
+ */
166
+ readonly resources?: TypertResourceReaders<WireArgs>
167
+ }
168
+
169
+ /**
170
+ * Explicit endpoint, stream, and resource choices for one direct Remote method.
171
+ * @template WireArgs - the decorated method's positional parameters as the
172
+ * wire carries them; the default `never` admits readers of any tuple.
173
+ */
174
+ export interface RemoteMethodOptions<WireArgs extends readonly unknown[] = never> extends RemoteScopeOptions<WireArgs> {
175
+ /** `stream`: deliver each Iterable item over the shared logical-stream carrier. */
176
+ readonly mode?: 'stream'
177
+ }
178
+
179
+ /** A decorator whose `resources` readers were checked against the decorated method's parameters. */
180
+ type RemoteTypedMethodDecorator<This extends object, Args extends unknown[], Result> = (
181
+ method: (this: This, ...args: Args) => Result,
182
+ context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
183
+ ) => void
184
+
185
+ type RemoteMethodDecorator = <This extends object, Args extends unknown[], Result>(
186
+ method: (this: This, ...args: Args) => Result,
187
+ context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
188
+ ) => void
189
+
190
+ interface RemoteInitializerContext<This extends object> {
191
+ readonly private: boolean
192
+ readonly static: boolean
193
+ readonly name: string | symbol
194
+ addInitializer(initializer: (this: This) => void): void
195
+ }
196
+
197
+ interface StoredRemoteMethodMarker {
198
+ readonly exportName?: string
199
+ readonly mode?: 'stream'
200
+ readonly invocation: RemoteInvocationMarker
201
+ readonly resources?: Readonly<Record<string, TypertResourceReader>>
202
+ }
203
+
204
+ /** Marker fields selected by decorator arguments. */
205
+ interface RemoteMarkerOptions {
206
+ readonly mode?: 'stream'
207
+ readonly exportName?: string
208
+ readonly resources?: Readonly<Record<string, TypertResourceReader>>
209
+ }
210
+
211
+ interface StoredRemoteMethod extends StoredRemoteMethodMarker {
212
+ readonly method: string
213
+ }
214
+
215
+ interface RemoteMethodDescriptorV1 {
216
+ readonly version: 1
217
+ readonly methods: readonly StoredRemoteMethod[]
218
+ }
219
+
220
+ const REMOTE_METHOD_DESCRIPTOR = '@deepseek-ai/dsh-typert-protocol/remote-methods'
221
+
222
+ /**
223
+ * Bind one visible Service field to a Cordis key and Remote namespace. A
224
+ * service that owns a Cordis Context also gives its tree `ctx.invocation`,
225
+ * `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
226
+ * a Host composition to read it.
227
+ * @param service - owning Service instance, normally `this`.
228
+ * @param serviceKey - exact Cordis service key.
229
+ * @param options - optional wire namespace.
230
+ * @returns a frozen, inspectable binding with no compiler-injected metadata.
231
+ */
232
+ export function bindTypertRemote<Service extends object>(
233
+ service: Service,
234
+ serviceKey: string,
235
+ options: TypertGatewayBindingOptions = {},
236
+ ): TypertGatewayBinding<Service> {
237
+ validateName('service key', serviceKey)
238
+ const namespace = options.namespace ?? serviceKey
239
+ validateName('namespace', namespace)
240
+ const ctx: unknown = Reflect.get(service, 'ctx')
241
+ if (Context.is(ctx)) provideInvocationAccessor(ctx)
242
+ return Object.freeze({ service, serviceKey, namespace })
243
+ }
244
+
245
+ /** Cordis Service base that exposes its registered name through Typert Gateway. */
246
+ export abstract class TypertRemoteService<out T = never> extends Service<T> {
247
+ /** Visible binding consumed by the Gateway's source-mode discovery. */
248
+ readonly typertRemote: TypertGatewayBinding<this>
249
+
250
+ /**
251
+ * Register the Service and bind the same key to Typert Gateway.
252
+ * @param ctx - owning Cordis Context.
253
+ * @param serviceKey - exact Cordis service key and default wire namespace.
254
+ * @param options - optional wire namespace.
255
+ */
256
+ protected constructor(ctx: Context, serviceKey: string, options: TypertGatewayBindingOptions = {}) {
257
+ super(ctx, serviceKey)
258
+ this.typertRemote = bindTypertRemote(this, this.name, options)
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
264
+ * reflect service's "cannot get property" error; a call-derived Context shadows
265
+ * the accessor with its own property. The first Remote Service constructed in a
266
+ * tree registers it on the root, where it outlives any one Service.
267
+ */
268
+ function provideInvocationAccessor(ctx: Context): void {
269
+ if (Object.hasOwn(ctx.root.reflect.props, 'invocation')) return
270
+ ctx.root.accessor('invocation', { get: () => undefined })
271
+ }
272
+
273
+ /**
274
+ * Mark one public instance method as a direct Remote invocation.
275
+ * @param _method - decorated method; retained only by the class itself.
276
+ * @param context - standard decorator context used to schedule private marking.
277
+ */
278
+ export function Remote<This extends object, Args extends unknown[], Result>(
279
+ _method: (this: This, ...args: Args) => Result,
280
+ context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
281
+ ): void
282
+ /**
283
+ * Mark one public instance method with an endpoint name.
284
+ * @param name - exported endpoint method.
285
+ * @returns a standard method decorator.
286
+ */
287
+ export function Remote(name: string): RemoteMethodDecorator
288
+ /**
289
+ * Mark one public instance method with endpoint, stream, or resource options.
290
+ * @param options - Remote options; each reader's parameters are inferred from the decorated method.
291
+ * @returns a standard method decorator.
292
+ */
293
+ export function Remote<This extends object, Args extends unknown[], Result>(
294
+ options: NoInfer<RemoteMethodOptions<RemoteWireArgs<Args>>>,
295
+ ): RemoteTypedMethodDecorator<This, Args, Result>
296
+ export function Remote<This extends object, Args extends unknown[], Result>(
297
+ methodExportOrOptions: string | RemoteMethodOptions | ((this: This, ...args: Args) => Result),
298
+ context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
299
+ ): void | RemoteMethodDecorator {
300
+ if (typeof methodExportOrOptions === 'string') {
301
+ validateName('Remote export name', methodExportOrOptions)
302
+ return remoteDecorator({ kind: 'direct' }, { exportName: methodExportOrOptions })
303
+ }
304
+ if (typeof methodExportOrOptions === 'object') {
305
+ return remoteDecorator({ kind: 'direct' }, markerOptions(
306
+ methodExportOrOptions,
307
+ REMOTE_OPTION_KEYS,
308
+ 'Remote options require name, mode: "stream", or resources',
309
+ ))
310
+ }
311
+ if (context === undefined) throw new TypeError('typert-protocol: Remote decorator context is missing')
312
+ addMarkerInitializer(context, { kind: 'direct' })
313
+ }
314
+
315
+ const REMOTE_OPTION_KEYS: ReadonlySet<string> = new Set(['name', 'mode', 'resources'])
316
+ const REMOTE_SCOPE_OPTION_KEYS: ReadonlySet<string> = new Set(['name', 'resources'])
317
+
318
+ /** Validate one decorator options object against its accepted keys and select the marker fields. */
319
+ function markerOptions(options: RemoteMethodOptions, accepted: ReadonlySet<string>, requirement: string): RemoteMarkerOptions {
320
+ const keys = Reflect.ownKeys(options)
321
+ if (keys.length === 0 || keys.some(key => typeof key !== 'string' || !accepted.has(key))
322
+ || (Object.hasOwn(options, 'mode') && options.mode !== 'stream')) {
323
+ throw new TypeError(`typert-protocol: ${requirement}`)
324
+ }
325
+ const { mode, name, resources } = options
326
+ if (name !== undefined) validateName('Remote export name', name)
327
+ return {
328
+ ...(mode === undefined ? {} : { mode }),
329
+ ...(name === undefined ? {} : { exportName: name }),
330
+ ...(resources === undefined ? {} : { resources: resourceReaders(resources) }),
331
+ }
332
+ }
333
+
334
+ /** Freeze declared readers into the marker record; a misdeclared reader fails at decoration. */
335
+ function resourceReaders(resources: object): Readonly<Record<string, TypertResourceReader>> {
336
+ const readers: Record<string, TypertResourceReader> = {}
337
+ for (const [kind, reader] of Object.entries(resources as Readonly<Record<string, unknown>>)) {
338
+ if (typeof reader !== 'function') {
339
+ throw new TypeError(`typert-protocol: Remote resource reader "${kind}" must be a function`)
340
+ }
341
+ readers[kind] = reader as TypertResourceReader
342
+ }
343
+ if (Object.keys(readers).length === 0) throw new TypeError('typert-protocol: Remote resources require at least one reader')
344
+ return Object.freeze(readers)
345
+ }
346
+
347
+ function remoteDecorator(
348
+ invocation: RemoteInvocationMarker,
349
+ options: RemoteMarkerOptions = {},
350
+ ): RemoteMethodDecorator {
351
+ return function <This extends object, Args extends unknown[], Result>(
352
+ _method: (this: This, ...args: Args) => Result,
353
+ context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
354
+ ): void {
355
+ addMarkerInitializer(context, invocation, options)
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Create a decorator for a method resolved from one Remote Scope.
361
+ * @param key - scope key declared through the Context map.
362
+ * @param name - optional Remote export name; defaults to the method name.
363
+ * @returns a standard method decorator that records a versioned prototype descriptor.
364
+ */
365
+ export function RemoteScope(key: Extract<keyof TypertContextMap, string>, name?: string): RemoteMethodDecorator
366
+ /**
367
+ * Create a decorator for a Remote Scope method with endpoint or resource options.
368
+ * @param key - scope key declared through the Context map.
369
+ * @param options - Remote Scope options; each reader's parameters are inferred from the decorated method.
370
+ * @returns a standard method decorator that records a versioned prototype descriptor.
371
+ */
372
+ export function RemoteScope<This extends object, Args extends unknown[], Result>(
373
+ key: Extract<keyof TypertContextMap, string>,
374
+ options: NoInfer<RemoteScopeOptions<RemoteWireArgs<Args>>>,
375
+ ): RemoteTypedMethodDecorator<This, Args, Result>
376
+ export function RemoteScope(
377
+ key: Extract<keyof TypertContextMap, string>,
378
+ option?: string | RemoteScopeOptions,
379
+ ): RemoteMethodDecorator {
380
+ validateName('Scope key', key)
381
+ const invocation: RemoteInvocationMarker = { kind: 'context', context: key }
382
+ if (option === undefined) return remoteDecorator(invocation)
383
+ if (typeof option === 'string') {
384
+ validateName('Remote export name', option)
385
+ return remoteDecorator(invocation, { exportName: option })
386
+ }
387
+ return remoteDecorator(invocation, markerOptions(
388
+ option,
389
+ REMOTE_SCOPE_OPTION_KEYS,
390
+ 'RemoteScope options require name or resources',
391
+ ))
392
+ }
393
+
394
+ /**
395
+ * Read Remote markers attached to a live Service's class prototype.
396
+ * The returned snapshot cannot mutate the stored descriptor.
397
+ * @param service - live Service instance.
398
+ * @returns markers in class declaration order.
399
+ */
400
+ export function remoteMethods(service: object): readonly RemoteMethodMarker[] {
401
+ const prototype = Object.getPrototypeOf(service) as object | null
402
+ if (prototype === null) return []
403
+ return (readRemoteMethodDescriptor(prototype)?.methods ?? []).map(marker => ({ ...marker }))
404
+ }
405
+
406
+ function readRemoteMethodDescriptor(prototype: object): RemoteMethodDescriptorV1 | undefined {
407
+ const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR)
408
+ if (property === undefined) return undefined
409
+ const descriptor: unknown = property.value
410
+ if (descriptor === null || typeof descriptor !== 'object') {
411
+ throw new TypeError('typert-protocol: Remote method descriptor must be an object')
412
+ }
413
+ const version: unknown = Reflect.get(descriptor, 'version')
414
+ if (version !== 1) {
415
+ throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`)
416
+ }
417
+ const methods: unknown = Reflect.get(descriptor, 'methods')
418
+ if (!Array.isArray(methods)) {
419
+ throw new TypeError('typert-protocol: Remote method descriptor methods must be an array')
420
+ }
421
+ return descriptor as RemoteMethodDescriptorV1
422
+ }
423
+
424
+ function addMarkerInitializer<This extends object>(
425
+ context: RemoteInitializerContext<This>,
426
+ invocation: RemoteInvocationMarker,
427
+ options: RemoteMarkerOptions = {},
428
+ ): void {
429
+ if (context.private || context.static || typeof context.name !== 'string') {
430
+ throw new TypeError('typert-protocol: Remote decorators require a public instance method with a string name')
431
+ }
432
+ const method = context.name
433
+ context.addInitializer(function (this: This) {
434
+ const prototype = Object.getPrototypeOf(this) as object | null
435
+ if (prototype === null) {
436
+ throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`)
437
+ }
438
+ mark(prototype, method, invocation, options)
439
+ })
440
+ }
441
+
442
+ function mark(
443
+ prototype: object,
444
+ method: string,
445
+ invocation: RemoteInvocationMarker,
446
+ { mode, exportName, resources }: RemoteMarkerOptions,
447
+ ): void {
448
+ const descriptor = readRemoteMethodDescriptor(prototype)
449
+ const marker: StoredRemoteMethod = Object.freeze({
450
+ method,
451
+ ...(exportName === undefined || exportName === method ? {} : { exportName }),
452
+ ...(mode === undefined ? {} : { mode }),
453
+ invocation: Object.freeze(invocation),
454
+ ...(resources === undefined ? {} : { resources }),
455
+ })
456
+ const current = descriptor?.methods.find(candidate => candidate.method === method)
457
+ if (current !== undefined) {
458
+ // One decorator application owns one frozen reader record, so identity marks the same declaration.
459
+ if (current.exportName === marker.exportName
460
+ && current.mode === marker.mode
461
+ && current.resources === marker.resources
462
+ && sameInvocation(current.invocation, invocation)) return
463
+ throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`)
464
+ }
465
+ Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
466
+ configurable: true,
467
+ value: Object.freeze({
468
+ version: 1,
469
+ methods: Object.freeze([...(descriptor?.methods ?? []), marker]),
470
+ } satisfies RemoteMethodDescriptorV1),
471
+ })
472
+ }
473
+
474
+ function sameInvocation(left: RemoteInvocationMarker, right: RemoteInvocationMarker): boolean {
475
+ if (left.kind === 'direct') return right.kind === 'direct'
476
+ if (right.kind === 'direct') return false
477
+ return left.context === right.context
478
+ }
479
+
480
+ function validateName(subject: string, value: string): void {
481
+ if (!isTypertRemoteSegment(value)) {
482
+ throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`)
483
+ }
484
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Lossless JSON checks every Remote carrier shares: the Client handle before it
3
+ * queues an uplink item, the Gateway at its wire and codec-less uplink
4
+ * boundaries, and the in-process mock.
5
+ */
6
+
7
+ /**
8
+ * Test whether a value crosses JSON transport without coercion or omission.
9
+ * @param value - candidate boundary value.
10
+ * @returns whether the value is losslessly JSON-compatible.
11
+ */
12
+ export function isRemoteJsonValue(value: unknown): boolean {
13
+ return visitJsonValue(value, new Set<object>())
14
+ }
15
+
16
+ /**
17
+ * Test whether a value may travel as one uplink item: a lossless JSON value, or
18
+ * a top-level `undefined`, which the wire carries as an `item` frame without
19
+ * `value`. Nested `undefined`, `NaN`, and infinities stay rejected.
20
+ * @param value - candidate uplink item.
21
+ * @returns whether the item crosses every carrier unchanged.
22
+ */
23
+ export function isRemoteUplinkItem(value: unknown): boolean {
24
+ return value === undefined || isRemoteJsonValue(value)
25
+ }
26
+
27
+ function visitJsonValue(value: unknown, ancestors: Set<object>): boolean {
28
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
29
+ if (typeof value === 'number') return Number.isFinite(value) && !Object.is(value, -0)
30
+ if (typeof value !== 'object') return false
31
+ if (ancestors.has(value)) return false
32
+ ancestors.add(value)
33
+ try {
34
+ if (Array.isArray(value)) {
35
+ if (Object.getPrototypeOf(value) !== Array.prototype
36
+ || Reflect.ownKeys(value).length !== value.length + 1) return false
37
+ for (let index = 0; index < value.length; index++) {
38
+ if (!Object.hasOwn(value, index) || !visitJsonValue(value[index], ancestors)) return false
39
+ }
40
+ return true
41
+ }
42
+ const prototype: unknown = Object.getPrototypeOf(value)
43
+ if (prototype !== Object.prototype && prototype !== null) return false
44
+ for (const key of Reflect.ownKeys(value)) {
45
+ if (typeof key !== 'string') return false
46
+ const descriptor = Object.getOwnPropertyDescriptor(value, key)
47
+ if (descriptor?.enumerable !== true || !visitJsonValue(Reflect.get(value, key), ancestors)) return false
48
+ }
49
+ return true
50
+ } finally {
51
+ ancestors.delete(value)
52
+ }
53
+ }
@@ -0,0 +1,39 @@
1
+ /** Generic invocation-owned values returned by synchronous Client Context resolvers. */
2
+
3
+ /** Shared identity across independently bundled Context providers and Gateway. */
4
+ export const TYPERT_OWNED_VALUE = Symbol.for('dsh.typert.owned-value')
5
+
6
+ /** A borrowed payload paired with the invocation owner's idempotent cleanup. */
7
+ export interface TypertOwnedValue<Value> extends Disposable {
8
+ readonly [TYPERT_OWNED_VALUE]: true
9
+ readonly value: Value
10
+ }
11
+
12
+ /**
13
+ * Transfer cleanup ownership without adding another resource reference count.
14
+ * @param value - resolved payload passed to the invocation.
15
+ * @param release - non-throwing synchronous release, called at most once.
16
+ * @returns an owned payload disposed after invocation and reply settlement.
17
+ */
18
+ export function typertOwnedValue<Value>(value: Value, release: () => void): TypertOwnedValue<Value> {
19
+ let active = true
20
+ return {
21
+ [TYPERT_OWNED_VALUE]: true,
22
+ value,
23
+ [Symbol.dispose]() {
24
+ if (!active) return
25
+ active = false
26
+ release()
27
+ },
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Identify invocation-owned values using the shared marker.
33
+ * @param value - borrowed or owned resolver result.
34
+ * @returns whether the result carries invocation cleanup.
35
+ */
36
+ export function isTypertOwnedValue(value: unknown): value is TypertOwnedValue<unknown> {
37
+ return typeof value === 'object' && value !== null
38
+ && TYPERT_OWNED_VALUE in value && value[TYPERT_OWNED_VALUE] === true
39
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Reads the non-owner caller of the current Remote call.
3
+ * @module @deepseek-ai/dsh-typert-protocol/principal
4
+ */
5
+
6
+ import type { Principal, RemoteInvocation } from './types.ts'
7
+
8
+ /**
9
+ * The identity a Remote-call producer records on durable output.
10
+ * @param invocation - `ctx.invocation`; `undefined` outside a Remote call.
11
+ * @returns the caller's principal when it is not the owner; `undefined` for the
12
+ * owner and for trusted in-process code outside a Remote call.
13
+ */
14
+ export function principalOf(invocation: RemoteInvocation | undefined): Principal | undefined {
15
+ const principal = invocation?.peer.principal
16
+ return principal?.kind === 'owner' ? undefined : principal
17
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Resources of Remote access control: the built-in identity-less `global`
3
+ * resource and one resource per identity of a kind declared in
4
+ * `TypertResourceMap`. Resources are independent: access to one resource
5
+ * implies no access to any other.
6
+ * @module @deepseek-ai/dsh-typert-protocol/remote-access
7
+ */
8
+
9
+ /* oxlint-disable typescript/no-redundant-type-constituents --
10
+ * `TypertResourceKind` is the declare-merge key pattern: the resource map is
11
+ * empty in this compilation unit, so the rule sees `never`, while every
12
+ * domain that declares a kind merges its key in. */
13
+ import type {
14
+ TypertResourceId,
15
+ TypertResourceKind,
16
+ TypertResourceMap,
17
+ } from './types.ts'
18
+
19
+ /** One resource kind: the built-in `global` kind or a declared kind. */
20
+ export type RemoteAccessResourceKind = 'global' | TypertResourceKind
21
+
22
+ /**
23
+ * One resource a call or forwarded event touches: `global`, or one identity
24
+ * of a declared kind. Identities are wire values, read before any lookup
25
+ * resolves or validates them.
26
+ */
27
+ export type RemoteAccessResource =
28
+ | { readonly kind: 'global' }
29
+ | {
30
+ [Kind in TypertResourceKind]: { readonly kind: Kind; readonly id: TypertResourceId<TypertResourceMap[Kind]> }
31
+ }[TypertResourceKind]
32
+
33
+ /**
34
+ * The resource variant of one kind: `{ kind: 'global' }` for `global`, else
35
+ * that kind with its identity.
36
+ * @template K - the resource kind to select.
37
+ */
38
+ export type RemoteAccessResourceOf<K extends RemoteAccessResourceKind> = Extract<RemoteAccessResource, { readonly kind: K }>
@@ -0,0 +1,49 @@
1
+ /** The one Remote failure class shared by owners, the Gateway, and consumers. */
2
+
3
+ import type { RemoteErrorCode, RemoteErrorDetailsMap, RemoteFailure } from './types.ts'
4
+
5
+ /**
6
+ * One Remote call failure: a real Error carrying its stable code and typed
7
+ * details. Owners throw it at the failure point; the Host Gateway encodes it
8
+ * onto the wire unchanged; the Client face rebuilds an instance for the
9
+ * `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
10
+ * Discrimination is always by `code`, never by instanceof.
11
+ */
12
+ export class RemoteError<Code extends RemoteErrorCode = RemoteErrorCode> extends Error {
13
+ /** Structural marker: cross-realm/bundle identification never uses instanceof. */
14
+ readonly isDSHRemoteError: true = true
15
+
16
+ /**
17
+ * @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
18
+ * @param message - human diagnostic carried across the wire.
19
+ * @param details - structured payload typed by the code.
20
+ * @param options - standard Error options (`cause` survives in-process only).
21
+ */
22
+ constructor(
23
+ readonly code: Code,
24
+ message: string,
25
+ readonly details: RemoteErrorDetailsMap[Code],
26
+ options?: ErrorOptions,
27
+ ) {
28
+ super(message, options)
29
+ this.name = 'RemoteError'
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Structurally identify a RemoteError thrown across module or realm copies of
35
+ * this class. Mechanism-internal: the Gateway and test assertions use it;
36
+ * business code receives typed failures and never needs it.
37
+ * @param value - a caught value.
38
+ * @returns the failure when the marker matches, otherwise undefined.
39
+ */
40
+ export function remoteErrorOf(value: unknown): RemoteFailure | undefined {
41
+ // Structural, not instanceof: an Error thrown in another realm (iframe, VM)
42
+ // fails instanceof Error here, so the marker plus the code field is the test.
43
+ if (typeof value === 'object' && value !== null
44
+ && (value as { isDSHRemoteError?: unknown }).isDSHRemoteError === true
45
+ && typeof (value as { code?: unknown }).code === 'string') {
46
+ return value as RemoteFailure
47
+ }
48
+ return undefined
49
+ }