@zfdx123/dsh-hooks-ordering 1.0.5 → 1.0.7
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/client.js +198 -18
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/settings.ts +10 -3
package/client.js
CHANGED
|
@@ -181,15 +181,34 @@
|
|
|
181
181
|
var primitivesError = ''
|
|
182
182
|
var UI = loadPrimitives()
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Whether a value can be rendered as a React component.
|
|
186
|
+
*
|
|
187
|
+
* `typeof x === 'function'` is NOT enough: the shell's `Button` is built with
|
|
188
|
+
* `React.forwardRef(...)`, so its `typeof` is always `'object'` (`$$typeof =
|
|
189
|
+
* Symbol(react.forward_ref)`, `render` holds the function). A guard that asks
|
|
190
|
+
* for `typeof primitives.Button === 'function'` is therefore permanently
|
|
191
|
+
* false, which throws away the whole kit even though Input/Tag/StateDot
|
|
192
|
+
* really are plain functions — so the failure looks like "only the buttons
|
|
193
|
+
* are off" and the page silently ships its own fallback elements.
|
|
194
|
+
*
|
|
195
|
+
* Every other plugin in this repo guards the same way; this one was missed.
|
|
196
|
+
*/
|
|
197
|
+
function isRenderable(value) {
|
|
198
|
+
if (typeof value === 'function') return true
|
|
199
|
+
if (typeof value !== 'object' || value === null) return false
|
|
200
|
+
return value.$$typeof === Symbol.for('react.forward_ref') || value.$$typeof === Symbol.for('react.memo')
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
function loadPrimitives() {
|
|
185
204
|
try {
|
|
186
205
|
var primitives = require(PRIMITIVES)
|
|
187
206
|
if (
|
|
188
207
|
primitives &&
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
208
|
+
isRenderable(primitives.Button) &&
|
|
209
|
+
isRenderable(primitives.Input) &&
|
|
210
|
+
isRenderable(primitives.Tag) &&
|
|
211
|
+
isRenderable(primitives.StateDot)
|
|
193
212
|
) {
|
|
194
213
|
return primitives
|
|
195
214
|
}
|
|
@@ -346,6 +365,105 @@
|
|
|
346
365
|
return style
|
|
347
366
|
}
|
|
348
367
|
|
|
368
|
+
/**
|
|
369
|
+
* The name this plugin's settings entry actually has, resolved at runtime
|
|
370
|
+
* from the descriptors the host returns.
|
|
371
|
+
*
|
|
372
|
+
* It CANNOT be a constant. The settings namespace is the **loader entry
|
|
373
|
+
* id**, which belongs to whoever wrote the row that mounted this plugin,
|
|
374
|
+
* not to the plugin: the aggregator's `cordis.patch.yml` uses
|
|
375
|
+
* `dsh-plugin-hooks-ordering`, while this package's own patch uses
|
|
376
|
+
* `hooks-ordering`. Hardcoding the latter against the former produced
|
|
377
|
+
*
|
|
378
|
+
* no settings entry is named "hooks-ordering" (describe() returned: …, dsh-plugin-hooks-ordering, …)
|
|
379
|
+
*
|
|
380
|
+
* and the write path was refused outright:
|
|
381
|
+
*
|
|
382
|
+
* settings/rejected: No configurable plugin entry "hooks-ordering"
|
|
383
|
+
*
|
|
384
|
+
* Matching on the descriptor's own `ns` removes the guesswork: whatever id
|
|
385
|
+
* the mounting row has, this is the entry it produced.
|
|
386
|
+
*/
|
|
387
|
+
var resolvedNs = null
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Normalise a Remote answer to `{ ok, value }` / `{ ok: false, reason }`.
|
|
391
|
+
*
|
|
392
|
+
* The 0.1.7 Remote protocol answers every call with a `RemoteResult`
|
|
393
|
+
* envelope and the client proxy hands that envelope through as-is, so a
|
|
394
|
+
* caller that reads fields off the answer directly sees `undefined` while
|
|
395
|
+
* the wire traffic is healthy. A plain value is accepted too, so this
|
|
396
|
+
* keeps working if a future runtime unwraps for us.
|
|
397
|
+
*
|
|
398
|
+
* @param answer - whatever the Remote call resolved to.
|
|
399
|
+
* @returns the unwrapped outcome.
|
|
400
|
+
*/
|
|
401
|
+
function unwrapRemote(answer) {
|
|
402
|
+
if (answer !== null && typeof answer === 'object' && typeof answer.ok === 'boolean') {
|
|
403
|
+
if (answer.ok === false) {
|
|
404
|
+
var failure = answer.error
|
|
405
|
+
var reason =
|
|
406
|
+
failure && typeof failure === 'object'
|
|
407
|
+
? String(failure.code || 'remote error') + ': ' + String(failure.message || '')
|
|
408
|
+
: String(failure || 'remote error')
|
|
409
|
+
return { ok: false, reason: reason }
|
|
410
|
+
}
|
|
411
|
+
return { ok: true, value: answer.value }
|
|
412
|
+
}
|
|
413
|
+
return { ok: true, value: answer }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The fields this plugin's settings entry carries. The entry is claimed by
|
|
418
|
+
* VALUE SHAPE against this set, never by guessing at a name.
|
|
419
|
+
*
|
|
420
|
+
* Exactly the fields `hooksOrderingConfig` declares. A host projects only the
|
|
421
|
+
* entries that have a volatile node, and `entryId` is not volatile, so it can
|
|
422
|
+
* never appear in a descriptor value — do not add it back.
|
|
423
|
+
*/
|
|
424
|
+
var OWN_FIELDS = ['hooks', 'serialHooks', 'log']
|
|
425
|
+
|
|
426
|
+
/** The fields a real entry must carry at least one of, or it is not ours. */
|
|
427
|
+
var OWN_REQUIRED_FIELDS = ['hooks', 'serialHooks', 'log']
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* @param list - descriptor list from `describe()`.
|
|
431
|
+
* @returns the descriptor for this plugin, or undefined.
|
|
432
|
+
*
|
|
433
|
+
* Matching on the namespace name is what produced the production failure:
|
|
434
|
+
* the settings ns is the LOADER ENTRY ID, which belongs to whoever wrote
|
|
435
|
+
* the row that mounted this plugin. The same package mounted at the top
|
|
436
|
+
* level has `dsh-plugin-hooks-ordering`, mounted under an `include` group
|
|
437
|
+
* it becomes `include:dsh-plugin-hooks-ordering` — and a hardcoded name or
|
|
438
|
+
* a suffix guess then reads one entry and writes another, which the host
|
|
439
|
+
* refuses outright:
|
|
440
|
+
*
|
|
441
|
+
* No configurable plugin entry "include:dsh-mcp-manager"
|
|
442
|
+
*
|
|
443
|
+
* Claiming by value shape removes the guesswork: every key of the entry's
|
|
444
|
+
* value must be a field this plugin declares, and it must carry at least
|
|
445
|
+
* one of the real fields (an empty object would match any schema). Two
|
|
446
|
+
* candidates means ambiguity, and ambiguity returns undefined rather than
|
|
447
|
+
* betting on one.
|
|
448
|
+
*/
|
|
449
|
+
function pickEntry(list) {
|
|
450
|
+
if (!Array.isArray(list)) return undefined
|
|
451
|
+
var candidates = list.filter(function (entry) {
|
|
452
|
+
if (!entry || typeof entry !== 'object') return false
|
|
453
|
+
var value = entry.value
|
|
454
|
+
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) return false
|
|
455
|
+
var keys = Object.keys(value)
|
|
456
|
+
if (keys.length === 0) return false
|
|
457
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
458
|
+
if (OWN_FIELDS.indexOf(keys[i]) < 0) return false
|
|
459
|
+
}
|
|
460
|
+
return OWN_REQUIRED_FIELDS.some(function (field) {
|
|
461
|
+
return keys.indexOf(field) >= 0
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
return candidates.length === 1 ? candidates[0] : undefined
|
|
465
|
+
}
|
|
466
|
+
|
|
349
467
|
// ── the settings page ──────────────────────────────────────────────────
|
|
350
468
|
|
|
351
469
|
/**
|
|
@@ -396,6 +514,12 @@
|
|
|
396
514
|
*/
|
|
397
515
|
function createSettingsSource(ctx) {
|
|
398
516
|
var remote = ctx && ctx.remote && ctx.remote.settings
|
|
517
|
+
/**
|
|
518
|
+
* The name this plugin's settings entry actually has is resolved at
|
|
519
|
+
* runtime by {@link pickEntry} into the module-level `resolvedNs`; see
|
|
520
|
+
* the note there for why it cannot be a constant.
|
|
521
|
+
*/
|
|
522
|
+
|
|
399
523
|
/**
|
|
400
524
|
* Why the form has no data. Kept as a concrete, user-visible string:
|
|
401
525
|
* "not available" on its own made a wiring fault indistinguishable from
|
|
@@ -431,17 +555,27 @@
|
|
|
431
555
|
return remote.describe()
|
|
432
556
|
})
|
|
433
557
|
.then(function (answer) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
558
|
+
// A Remote call answers with a `RemoteResult` envelope
|
|
559
|
+
// (`{ok:true, value}` / `{ok:false, error}`) and the client proxy
|
|
560
|
+
// does NOT unwrap it: reading `answer.namespaces` directly got
|
|
561
|
+
// `undefined`, which surfaced as the misleading
|
|
562
|
+
// "describe() returned no namespaces array" while the network
|
|
563
|
+
// exchange was perfectly healthy (ok:true, 20 namespaces).
|
|
564
|
+
var result = unwrapRemote(answer)
|
|
565
|
+
if (result.ok === false) {
|
|
566
|
+
return unavailableBecause('describe() failed: ' + result.reason)
|
|
567
|
+
}
|
|
568
|
+
var value = result.value
|
|
569
|
+
if (value === null || value === undefined) {
|
|
570
|
+
return unavailableBecause('describe() returned no value')
|
|
437
571
|
}
|
|
438
|
-
var
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
572
|
+
var list = Array.isArray(value.namespaces) ? value.namespaces : null
|
|
573
|
+
if (list === null) {
|
|
574
|
+
return unavailableBecause(
|
|
575
|
+
'describe() value has no namespaces array (keys: ' + Object.keys(value).join(', ') + ')',
|
|
576
|
+
)
|
|
444
577
|
}
|
|
578
|
+
var found = pickEntry(list)
|
|
445
579
|
if (found === undefined) {
|
|
446
580
|
var seen = list
|
|
447
581
|
.map(function (entry) {
|
|
@@ -450,9 +584,14 @@
|
|
|
450
584
|
.filter(Boolean)
|
|
451
585
|
.join(', ')
|
|
452
586
|
return unavailableBecause(
|
|
453
|
-
'no settings entry
|
|
587
|
+
'no settings entry matches this plugin\'s fields (' +
|
|
588
|
+
OWN_REQUIRED_FIELDS.join('/') +
|
|
589
|
+
'; describe() returned: ' +
|
|
590
|
+
(seen || 'nothing') +
|
|
591
|
+
')',
|
|
454
592
|
)
|
|
455
593
|
}
|
|
594
|
+
resolvedNs = found.ns
|
|
456
595
|
return {
|
|
457
596
|
status: 'ready',
|
|
458
597
|
available: true,
|
|
@@ -482,12 +621,22 @@
|
|
|
482
621
|
}
|
|
483
622
|
// The revision is read fresh immediately before the write so a page
|
|
484
623
|
// left open across another edit reports a conflict instead of
|
|
485
|
-
// silently overwriting it.
|
|
624
|
+
// silently overwriting it. `read()` also refreshes `resolvedNs`.
|
|
486
625
|
return read().then(function (snap) {
|
|
487
626
|
if (!snap.available) {
|
|
488
627
|
throw new Error(snap.reason || 'this plugin has no settings entry in the active profile')
|
|
489
628
|
}
|
|
490
|
-
|
|
629
|
+
if (resolvedNs === null) {
|
|
630
|
+
throw new Error('this plugin did not resolve its settings entry id')
|
|
631
|
+
}
|
|
632
|
+
// The write answers with a RemoteResult envelope as well; unwrap
|
|
633
|
+
// it so a refusal surfaces as a real message instead of looking
|
|
634
|
+
// like a successful save.
|
|
635
|
+
return remote.mutate(resolvedNs, ops, snap.revision).then(function (answer) {
|
|
636
|
+
var result = unwrapRemote(answer)
|
|
637
|
+
if (result.ok === false) throw new Error(result.reason)
|
|
638
|
+
return result.value
|
|
639
|
+
})
|
|
491
640
|
})
|
|
492
641
|
},
|
|
493
642
|
/**
|
|
@@ -503,7 +652,15 @@
|
|
|
503
652
|
if (!events || typeof events.$on !== 'function') return function () {}
|
|
504
653
|
try {
|
|
505
654
|
return events.$on('settings/document-updated', function (ns) {
|
|
506
|
-
|
|
655
|
+
// Match the CLAIMED entry id — never a constant.
|
|
656
|
+
//
|
|
657
|
+
// Writing a refresh() call is harmless, but this comparison is the
|
|
658
|
+
// shape someone copies into a write guard, and a constant there
|
|
659
|
+
// would silently address the wrong entry: the settings ns is the
|
|
660
|
+
// loader entry id, which changes with how the row is mounted. Until
|
|
661
|
+
// the first claim, `resolvedNs` is null and a refresh is accepted
|
|
662
|
+
// unconditionally — a re-describe is cheap and always safe.
|
|
663
|
+
if (ns === undefined || resolvedNs === null || ns === resolvedNs) listener()
|
|
507
664
|
})
|
|
508
665
|
} catch (error) {
|
|
509
666
|
// A missing gateway listener must not take the page down.
|
|
@@ -534,6 +691,12 @@
|
|
|
534
691
|
var setDraft = draftState[1]
|
|
535
692
|
|
|
536
693
|
var dirtyRef = useRef(false)
|
|
694
|
+
// Bumped after every accepted mutation so the re-seed effect runs again
|
|
695
|
+
// even when neither the revision nor the snapshot identity changed (a
|
|
696
|
+
// reset back to the composition default is exactly that case).
|
|
697
|
+
var seedTokenState = useState(0)
|
|
698
|
+
var seedToken = seedTokenState[0]
|
|
699
|
+
var setSeedToken = seedTokenState[1]
|
|
537
700
|
var busyState = useState(false)
|
|
538
701
|
var busy = busyState[0]
|
|
539
702
|
var setBusy = busyState[1]
|
|
@@ -568,6 +731,8 @@
|
|
|
568
731
|
|
|
569
732
|
// Re-seed the editor from every accepted snapshot, but never over an
|
|
570
733
|
// unsaved edit: the user's typing outranks a background refresh.
|
|
734
|
+
// `seedToken` is in the key so an accepted mutation always re-seeds once,
|
|
735
|
+
// even if the revision did not move (see settle()).
|
|
571
736
|
var revision = snap ? snap.revision : undefined
|
|
572
737
|
useEffect(
|
|
573
738
|
function () {
|
|
@@ -575,7 +740,7 @@
|
|
|
575
740
|
var value = snap.value || {}
|
|
576
741
|
setDraft({ hooks: toLines(value.hooks), serialHooks: toLines(value.serialHooks), log: value.log || '' })
|
|
577
742
|
},
|
|
578
|
-
[revision, snap],
|
|
743
|
+
[revision, snap, seedToken],
|
|
579
744
|
)
|
|
580
745
|
|
|
581
746
|
/** @param fieldName - settings field to edit; @param next - its new textarea content. */
|
|
@@ -593,7 +758,20 @@
|
|
|
593
758
|
setError('')
|
|
594
759
|
pending.then(
|
|
595
760
|
function () {
|
|
761
|
+
// Two things have to happen on success, and the FIRST one alone is
|
|
762
|
+
// not enough:
|
|
763
|
+
// 1. the unsaved-edit flag drops, so a re-seed is allowed again;
|
|
764
|
+
// 2. a re-seed is actually TRIGGERED.
|
|
765
|
+
// `dirtyRef` is a ref: writing it does not re-render, and the
|
|
766
|
+
// re-seed effect keys on `[revision, snap]`. After a reset the
|
|
767
|
+
// revision stays 0 and the value goes back to `{}`, so the effect
|
|
768
|
+
// had no reason to run again: the form kept showing the value the
|
|
769
|
+
// user just reset, which read as "恢复组合默认无效" even though the
|
|
770
|
+
// write had succeeded. Bumping a counter makes it run once more.
|
|
596
771
|
dirtyRef.current = false
|
|
772
|
+
setSeedToken(function (token) {
|
|
773
|
+
return token + 1
|
|
774
|
+
})
|
|
597
775
|
setBusy(false)
|
|
598
776
|
},
|
|
599
777
|
function (failure) {
|
|
@@ -796,6 +974,8 @@
|
|
|
796
974
|
exports.apply = apply
|
|
797
975
|
exports.inject = inject
|
|
798
976
|
exports.NS = NS
|
|
977
|
+
exports.pickEntry = pickEntry
|
|
978
|
+
exports.unwrapRemote = unwrapRemote
|
|
799
979
|
exports.CLASS = CLASS
|
|
800
980
|
exports.CSS = CSS
|
|
801
981
|
exports.ZH = zh
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DEFAULT_WATERFALL_HOOKS: readonly string[]","DEFAULT_SYNC_RETURN_HOOKS: readonly string[]","DEFAULT_SERIAL_HOOKS: readonly string[]","deps: string[]","HookOrdering"],"sources":["../src/settings.ts","../src/dsh.ts"],"sourcesContent":["/**\n * This plugin's Cordis Config — and, since DSH 0.1.7, its Settings page.\n *\n * 0.1.7 removed `ctx.settings.register(ns, schema)`. Settings are now *projected\n * from each entry's own Config*: the loader validates the row's `config` against\n * this schema, resolves the defaults, and the settings service renders exactly\n * the nodes marked `.volatile()` as an editable form. Three consequences are\n * load-bearing here:\n *\n * - The defaults below are the *real* defaults, not \"off\" placeholders. Nothing\n * supplies a second `base` layer any more, so an entry whose `config:` key is\n * empty must resolve to the hook set this plugin controls by default.\n * - `.volatile()` belongs on the individual fields, never on the root object:\n * a volatile root collapses the whole Config into a single reference and\n * discards every nested default (verified against schemastery 3.18.4), and\n * schemastery rejects a volatile node nested inside another volatile node.\n * - The schema is built lazily by {@link hooksOrderingConfig}. `dsh.ts` imports\n * this module and this module needs `dsh.ts`'s default hook lists, so building\n * the schema eagerly would read those bindings before they are initialized.\n *\n * `syncReturnHooks` is deliberately absent: it describes the HOST's dispatch\n * (which hooks are consumed without awaiting), not a user preference, so it\n * stays composition-only in `apply()`.\n *\n * @module dsh-hooks-ordering/settings\n */\n\nimport Schema from '@deepseek-ai/schemastery'\n\n/** The fields this plugin exposes as editable settings. */\nexport interface HooksOrderingSettings {\n /** Waterfall hooks to control; `[]` disables the waterfall service entirely. */\n readonly hooks: readonly string[]\n /** Serial hooks to control; `[]` disables the serial service entirely. */\n readonly serialHooks: readonly string[]\n /** Constraint-DAG log file; the empty string means \"do not log\". */\n readonly log: string\n}\n\n/**\n * Entry id used by `cordis.patch.yml` and, in 0.1.7, by the settings form.\n *\n * The form is keyed by the **loader entry id**; this constant is the shipped\n * row's id, and it is also the client half's settings-section id.\n */\nexport const SETTINGS_NS = 'hooks-ordering'\n\n/**\n * Build the Config schema from the caller's default hook lists.\n *\n * Every field is `.volatile()`, so every field is user-editable from the\n * Settings page and an edit is committed into the live reference instead of\n * remounting the plugin. `log` is volatile too, because a non-volatile field\n * would not appear in the form at all.\n *\n * @param defaultWaterfallHooks - waterfall hooks controlled when unconfigured.\n * @param defaultSerialHooks - serial hooks controlled when unconfigured.\n * @returns a callable schemastery schema, which is what Cordis resolves through.\n */\nexport function hooksOrderingConfig(\n defaultWaterfallHooks: readonly string[],\n defaultSerialHooks: readonly string[],\n): Schema {\n return Schema.object({\n hooks: Schema.array(Schema.string()).default([...defaultWaterfallHooks]).volatile(),\n serialHooks: Schema.array(Schema.string()).default([...defaultSerialHooks]).volatile(),\n log: Schema.string().default('').volatile(),\n })\n}\n\n/**\n * Read one resolved field, unwrapping a volatile reference when the loader\n * supplied one and falling back to a default when the field is absent.\n *\n * A loader-provided Config carries volatile fields as references (`.get()`);\n * a plain Cordis mount, or an older harness, passes plain values. Both must\n * work, and an absent field must not be confused with an explicit empty one.\n *\n * @param config - the resolved plugin config (possibly `null`).\n * @param field - the field to read.\n * @param fallback - value to use when the field is absent.\n * @returns the field's current value, or `fallback`.\n */\nexport function readSetting<T>(\n config: object | null | undefined,\n field: string,\n fallback: T,\n): T {\n const value = (config as Record<string, unknown> | null | undefined)?.[field]\n if (value === undefined || value === null) return fallback\n if (typeof (value as { get?: unknown }).get === 'function') {\n const current = (value as { get(): unknown }).get()\n return (current ?? fallback) as T\n }\n return value as T\n}\n","/**\n * The DeepSeek-Harness layer: a dsh plugin that mounts the ordering services\n * and takes control of the real dsh hooks that multiple independent packages\n * contribute to, so a profile can opt into deterministic ordering with one row.\n *\n * dsh (deepseek-harness) ships waterfall hooks such as `agent/pre-step`\n * (subscribed by a dozen+ independent packages), `tools/post-execute`,\n * `llm/stream`, and `system-prompt/assemble`, plus the serial hook\n * `agent/turn-stopping`. Their relative listener order is load-bearing yet\n * today decided only by binary `prepend` and registration timing. This plugin\n * controls those hooks up front; controlling an empty hook is a transparent\n * pass-through, so nothing changes until participants register with\n * `before`/`after`.\n *\n * @module dsh-hooks-ordering/dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport HookOrdering from './waterfall.ts'\nimport { SerialHookOrdering } from './serial.ts'\nimport { type HooksOrderingSettings, hooksOrderingConfig, readSetting } from './settings.ts'\n\n/** Cordis plugin name used by loader diagnostics. */\nexport const name = 'hooks-ordering'\n\n/**\n * `settings` is a **hard** dependency, and it has to be declared rather than\n * probed.\n *\n * cordis activates a plugin as soon as the services it declares exist, and\n * `ctx.get('settings')` reads the service store *without* creating that\n * requirement. Without this line the dsh layer loads in the first wave — before\n * the host has provided `settings` — the probe returns `undefined`, and the\n * settings namespace is never registered. Silently: no form, no error.\n *\n * Declaring it makes cordis wait, which also lands this plugin's prepended\n * brackets later in the boot, exactly where the ordering guarantee wants them.\n *\n * This entry targets dsh only — the hook names below are dsh's, and dsh always\n * provides `settings`, so the dependency costs nothing here. `/waterfall` and\n * `/serial` are the entries that carry no host-service requirement.\n */\nexport const inject = ['settings']\n\n/**\n * The dsh waterfall hooks this plugin controls by default — the ones multiple\n * independent packages contribute to, where relative order matters. Controlling\n * a hook with no registered participants is a no-op pass-through.\n *\n * Every name here was checked against the events dsh actually declares: each is\n * a `@mode waterfall` event in the installed build, and each is dispatched with\n * `await`, so the caller already handles a Promise and the bracket's async\n * phases cannot change the hook's return type. `tools/code-dispatch-log` used\n * to sit in this list and no dsh package declares it at all — controlling a\n * name nothing dispatches \"succeeds\" and then does nothing forever, which is\n * exactly the dead configuration this list must not carry.\n *\n * The other admission rule is {@link DEFAULT_SYNC_RETURN_HOOKS}: a hook whose\n * value the caller consumes without awaiting cannot carry ordered participants,\n * so controlling it by default would install a bracket nothing may ever\n * register into — the same dead configuration by a different route.\n */\nexport const DEFAULT_WATERFALL_HOOKS: readonly string[] = [\n 'agent/pre-step',\n 'agent/request',\n 'agent/request-error',\n 'system-prompt/assemble',\n 'tools/pre-execute',\n 'tools/execute',\n 'tools/post-execute',\n 'fs/write-intent',\n 'fs/edit-intent',\n 'approval/request',\n]\n\n/**\n * dsh hooks whose dispatch return value is consumed by the caller without\n * awaiting, and which therefore cannot carry ordered participants. Verified\n * against the installed build:\n *\n * - `llm/stream` — `dsh-llm` dispatches it as\n * `return this.ctx.waterfall(this, \"llm/stream\", options, …)` (no await), and\n * `dsh-session-title-llm` iterates the result with\n * `for await (const chunk of ctx.llm.stream(options))`. A Promise there throws\n * \"not async iterable\". It is a genuine multi-contributor hook (dsh-agent-loop,\n * dsh-llm's own invariant, dsh-session-checkpoint-policy, dsh-session-title),\n * so it is a real loss — but ordering it requires dsh to await the dispatch,\n * not a plugin-side workaround.\n * - `session-telemetry/record` — `dsh-session-telemetry` returns the record\n * straight out of the waterfall and hands it to the backend, so a Promise\n * would be emitted as a record: silent corruption, no error anywhere.\n * - `compaction/summary-error` — `dsh-compaction-basic` dispatches it as\n * `recover: (…) => this.ctx.waterfall(this, \"compaction/summary-error\", …, () => false)`\n * (no await) and consumes the boolean synchronously in\n * `if (!dependencies.recover(error, agent, prepared.shadowedSeqs, signal)) throw error`.\n * A Promise is always truthy, so `!recover(…)` is always false and every\n * summarizer failure is swallowed instead of rethrown — the compaction then\n * proceeds as if recovery had succeeded.\n *\n * Override per profile with `syncReturnHooks` once the host awaits one of them.\n */\nexport const DEFAULT_SYNC_RETURN_HOOKS: readonly string[] = [\n 'llm/stream',\n 'session-telemetry/record',\n 'compaction/summary-error',\n]\n\n/** The dsh serial hook controlled by default. */\nexport const DEFAULT_SERIAL_HOOKS: readonly string[] = ['agent/turn-stopping']\n\n/**\n * This plugin's Cordis Config schema.\n *\n * Since DSH 0.1.7 this *is* the Settings page: the fields are `.volatile()`, so\n * the settings service projects exactly them into an editable form and commits\n * an edit into the live reference rather than remounting the plugin. The\n * defaults are the real hook sets below, so an entry whose `config:` key is\n * empty resolves to them.\n */\nexport const Config = hooksOrderingConfig(DEFAULT_WATERFALL_HOOKS, DEFAULT_SERIAL_HOOKS)\n\n/** The same schema under its pre-0.1.7 name. */\nexport const HooksOrderingConfig = Config\n\n/**\n * Plugin config as a plain value.\n *\n * A loader-resolved config carries the volatile fields as references instead of\n * plain arrays, so this describes the *plain* shape callers and tests construct.\n */\nexport interface ConfigShape {\n /**\n * Waterfall hooks to control. Defaults to {@link DEFAULT_WATERFALL_HOOKS}.\n * Pass `[]` to disable the waterfall service entirely.\n */\n hooks?: readonly string[]\n /**\n * Serial hooks to control. Defaults to {@link DEFAULT_SERIAL_HOOKS}.\n * Pass `[]` to disable the serial service entirely.\n */\n serialHooks?: readonly string[]\n /**\n * Hooks whose return value the host consumes without awaiting, and which must\n * therefore refuse participants. Defaults to {@link DEFAULT_SYNC_RETURN_HOOKS}.\n * Pass `[]` — or a set without a given hook — to opt in to ordering it, once\n * the host awaits that dispatch.\n *\n * Composition-only: this describes the HOST's dispatch, not a user\n * preference, so it is intentionally not a field of {@link Config} and never\n * appears on the Settings page.\n */\n syncReturnHooks?: readonly string[]\n /** When set, the constraint DAG (JSON) is logged to this file on every change. */\n log?: string\n /** Overrides the loader entry id; only non-loader harnesses need it. */\n entryId?: string\n}\n\n/**\n * Mount {@link HookOrdering} and/or {@link SerialHookOrdering} and control the\n * configured hooks once the services are active.\n *\n * `config` may be `null`, and that is the ordinary case rather than an edge\n * case: a loader row whose `config:` key is followed only by comments parses as\n * YAML null, and a default parameter covers `undefined` but not `null`. This\n * package's own `cordis.patch.yml` is written exactly that way — every option\n * commented out — so a null config must mean \"all defaults\", not a crash.\n *\n * Since DSH 0.1.7 there is no second \"user layer\": the row's `config` IS the\n * editable settings, because Cordis validates it against {@link Config} and the\n * settings service projects that schema. A volatile field arrives here as a\n * reference rather than a plain array, which is why every read goes through\n * {@link readSetting} — that also keeps a plain Cordis mount (and an older\n * harness) working, where the fields are plain values.\n *\n * `hooks`/`serialHooks` are read once, at apply time. An edit from the Settings\n * page therefore needs a plugin reload to take effect — the same restart\n * semantics this namespace declared before 0.1.7 (`applies: 'restart'`), since\n * changing the controlled set means adding or removing live hook brackets and\n * the coordinator has no public \"release one hook\" operation.\n *\n * @param ctx - the Cordis context.\n * @param config - which hooks to control and an optional DAG `log` file; null means all defaults.\n */\nexport function apply(ctx: Context, config: ConfigShape | null = {}): void {\n const row = config ?? {}\n const settings: HooksOrderingSettings = {\n hooks: readSetting(row, 'hooks', DEFAULT_WATERFALL_HOOKS),\n serialHooks: readSetting(row, 'serialHooks', DEFAULT_SERIAL_HOOKS),\n log: readSetting(row, 'log', ''),\n }\n const { hooks, serialHooks, log } = settings\n const serviceConfig = log === '' ? {} : { log }\n // Not part of the settings form: the sync-return list describes the HOST's\n // dispatch, not a user preference, so it is composition (row) only.\n const syncReturnHooks = row.syncReturnHooks ?? DEFAULT_SYNC_RETURN_HOOKS\n\n const deps: string[] = []\n if (hooks.length > 0) {\n ctx.plugin(HookOrdering, { ...serviceConfig, syncReturnHooks })\n deps.push('hooksOrdering')\n }\n if (serialHooks.length > 0) {\n ctx.plugin(SerialHookOrdering, serviceConfig)\n deps.push('serialHooksOrdering')\n }\n if (deps.length === 0) return\n\n // The services activate asynchronously; control the hooks once they exist.\n ctx.inject(deps, (ready) => {\n for (const hook of hooks) ready.hooksOrdering.control(hook)\n for (const hook of serialHooks) ready.serialHooksOrdering.control(hook)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2DA,SAAgB,oBACd,uBACA,oBACQ;AACR,QAAO,OAAO,OAAO;EACnB,OAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,sBAAsB,CAAC,CAAC,UAAU;EACnF,aAAa,OAAO,MAAM,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,mBAAmB,CAAC,CAAC,UAAU;EACtF,KAAK,OAAO,QAAQ,CAAC,QAAQ,GAAG,CAAC,UAAU;EAC5C,CAAC;;;;;;;;;;;;;;;AAgBJ,SAAgB,YACd,QACA,OACA,UACG;CACH,MAAM,QAAS,SAAwD;AACvE,KAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,KAAI,OAAQ,MAA4B,QAAQ,WAE9C,QADiB,MAA6B,KAAK,IAChC;AAErB,QAAO;;;;;;ACvET,MAAa,OAAO;;;;;;;;;;;;;;;;;;AAmBpB,MAAa,SAAS,CAAC,WAAW;;;;;;;;;;;;;;;;;;;AAoBlC,MAAaA,0BAA6C;CACxD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BD,MAAaC,4BAA+C;CAC1D;CACA;CACA;CACD;;AAGD,MAAaC,uBAA0C,CAAC,sBAAsB;;;;;;;;;;AAW9E,MAAa,SAAS,oBAAoB,yBAAyB,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiExF,SAAgB,MAAM,KAAc,SAA6B,EAAE,EAAQ;CACzE,MAAM,MAAM,UAAU,EAAE;CAMxB,MAAM,EAAE,OAAO,aAAa,QALY;EACtC,OAAO,YAAY,KAAK,SAAS,wBAAwB;EACzD,aAAa,YAAY,KAAK,eAAe,qBAAqB;EAClE,KAAK,YAAY,KAAK,OAAO,GAAG;EACjC;CAED,MAAM,gBAAgB,QAAQ,KAAK,EAAE,GAAG,EAAE,KAAK;CAG/C,MAAM,kBAAkB,IAAI,mBAAmB;CAE/C,MAAMC,OAAiB,EAAE;AACzB,KAAI,MAAM,SAAS,GAAG;AACpB,MAAI,OAAOC,mBAAc;GAAE,GAAG;GAAe;GAAiB,CAAC;AAC/D,OAAK,KAAK,gBAAgB;;AAE5B,KAAI,YAAY,SAAS,GAAG;AAC1B,MAAI,OAAO,oBAAoB,cAAc;AAC7C,OAAK,KAAK,sBAAsB;;AAElC,KAAI,KAAK,WAAW,EAAG;AAGvB,KAAI,OAAO,OAAO,UAAU;AAC1B,OAAK,MAAM,QAAQ,MAAO,OAAM,cAAc,QAAQ,KAAK;AAC3D,OAAK,MAAM,QAAQ,YAAa,OAAM,oBAAoB,QAAQ,KAAK;GACvE"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["DEFAULT_WATERFALL_HOOKS: readonly string[]","DEFAULT_SYNC_RETURN_HOOKS: readonly string[]","DEFAULT_SERIAL_HOOKS: readonly string[]","deps: string[]","HookOrdering"],"sources":["../src/settings.ts","../src/dsh.ts"],"sourcesContent":["/**\n * This plugin's Cordis Config — and, since DSH 0.1.7, its Settings page.\n *\n * 0.1.7 removed `ctx.settings.register(ns, schema)`. Settings are now *projected\n * from each entry's own Config*: the loader validates the row's `config` against\n * this schema, resolves the defaults, and the settings service renders exactly\n * the nodes marked `.volatile()` as an editable form. Three consequences are\n * load-bearing here:\n *\n * - The defaults below are the *real* defaults, not \"off\" placeholders. Nothing\n * supplies a second `base` layer any more, so an entry whose `config:` key is\n * empty must resolve to the hook set this plugin controls by default.\n * - `.volatile()` belongs on the individual fields, never on the root object:\n * a volatile root collapses the whole Config into a single reference and\n * discards every nested default (verified against schemastery 3.18.4), and\n * schemastery rejects a volatile node nested inside another volatile node.\n * - The schema is built lazily by {@link hooksOrderingConfig}. `dsh.ts` imports\n * this module and this module needs `dsh.ts`'s default hook lists, so building\n * the schema eagerly would read those bindings before they are initialized.\n *\n * `syncReturnHooks` is deliberately absent: it describes the HOST's dispatch\n * (which hooks are consumed without awaiting), not a user preference, so it\n * stays composition-only in `apply()`.\n *\n * @module dsh-hooks-ordering/settings\n */\n\nimport Schema from '@deepseek-ai/schemastery'\n\n/** The fields this plugin exposes as editable settings. */\nexport interface HooksOrderingSettings {\n /** Waterfall hooks to control; `[]` disables the waterfall service entirely. */\n readonly hooks: readonly string[]\n /** Serial hooks to control; `[]` disables the serial service entirely. */\n readonly serialHooks: readonly string[]\n /** Constraint-DAG log file; the empty string means \"do not log\". */\n readonly log: string\n}\n\n/**\n * The id this package's own `cordis.patch.yml` uses, and the client half's\n * settings-section id. **Test-only for the server half** — do not read it as \"the\n * settings namespace\".\n *\n * In 0.1.7 a settings entry is named by the **loader entry id** of the row that\n * mounted the plugin, which is not something the plugin knows: the aggregator\n * mounts this package as `dsh-plugin-hooks-ordering`, this package's own patch as\n * `hooks-ordering`, and a row inside an `include` group gets a group prefix\n * (`include:...`). The server half therefore reads its config through the Config\n * it was handed, and the client half claims its entry by **value shape** (see\n * `pickEntry` in client.js) rather than by comparing names.\n */\nexport const SETTINGS_NS = 'hooks-ordering'\n\n/**\n * Build the Config schema from the caller's default hook lists.\n *\n * Every field is `.volatile()`, so every field is user-editable from the\n * Settings page and an edit is committed into the live reference instead of\n * remounting the plugin. `log` is volatile too, because a non-volatile field\n * would not appear in the form at all.\n *\n * @param defaultWaterfallHooks - waterfall hooks controlled when unconfigured.\n * @param defaultSerialHooks - serial hooks controlled when unconfigured.\n * @returns a callable schemastery schema, which is what Cordis resolves through.\n */\nexport function hooksOrderingConfig(\n defaultWaterfallHooks: readonly string[],\n defaultSerialHooks: readonly string[],\n): Schema {\n return Schema.object({\n hooks: Schema.array(Schema.string()).default([...defaultWaterfallHooks]).volatile(),\n serialHooks: Schema.array(Schema.string()).default([...defaultSerialHooks]).volatile(),\n log: Schema.string().default('').volatile(),\n })\n}\n\n/**\n * Read one resolved field, unwrapping a volatile reference when the loader\n * supplied one and falling back to a default when the field is absent.\n *\n * A loader-provided Config carries volatile fields as references (`.get()`);\n * a plain Cordis mount, or an older harness, passes plain values. Both must\n * work, and an absent field must not be confused with an explicit empty one.\n *\n * @param config - the resolved plugin config (possibly `null`).\n * @param field - the field to read.\n * @param fallback - value to use when the field is absent.\n * @returns the field's current value, or `fallback`.\n */\nexport function readSetting<T>(\n config: object | null | undefined,\n field: string,\n fallback: T,\n): T {\n const value = (config as Record<string, unknown> | null | undefined)?.[field]\n if (value === undefined || value === null) return fallback\n if (typeof (value as { get?: unknown }).get === 'function') {\n const current = (value as { get(): unknown }).get()\n return (current ?? fallback) as T\n }\n return value as T\n}\n","/**\n * The DeepSeek-Harness layer: a dsh plugin that mounts the ordering services\n * and takes control of the real dsh hooks that multiple independent packages\n * contribute to, so a profile can opt into deterministic ordering with one row.\n *\n * dsh (deepseek-harness) ships waterfall hooks such as `agent/pre-step`\n * (subscribed by a dozen+ independent packages), `tools/post-execute`,\n * `llm/stream`, and `system-prompt/assemble`, plus the serial hook\n * `agent/turn-stopping`. Their relative listener order is load-bearing yet\n * today decided only by binary `prepend` and registration timing. This plugin\n * controls those hooks up front; controlling an empty hook is a transparent\n * pass-through, so nothing changes until participants register with\n * `before`/`after`.\n *\n * @module dsh-hooks-ordering/dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport HookOrdering from './waterfall.ts'\nimport { SerialHookOrdering } from './serial.ts'\nimport { type HooksOrderingSettings, hooksOrderingConfig, readSetting } from './settings.ts'\n\n/** Cordis plugin name used by loader diagnostics. */\nexport const name = 'hooks-ordering'\n\n/**\n * `settings` is a **hard** dependency, and it has to be declared rather than\n * probed.\n *\n * cordis activates a plugin as soon as the services it declares exist, and\n * `ctx.get('settings')` reads the service store *without* creating that\n * requirement. Without this line the dsh layer loads in the first wave — before\n * the host has provided `settings` — the probe returns `undefined`, and the\n * settings namespace is never registered. Silently: no form, no error.\n *\n * Declaring it makes cordis wait, which also lands this plugin's prepended\n * brackets later in the boot, exactly where the ordering guarantee wants them.\n *\n * This entry targets dsh only — the hook names below are dsh's, and dsh always\n * provides `settings`, so the dependency costs nothing here. `/waterfall` and\n * `/serial` are the entries that carry no host-service requirement.\n */\nexport const inject = ['settings']\n\n/**\n * The dsh waterfall hooks this plugin controls by default — the ones multiple\n * independent packages contribute to, where relative order matters. Controlling\n * a hook with no registered participants is a no-op pass-through.\n *\n * Every name here was checked against the events dsh actually declares: each is\n * a `@mode waterfall` event in the installed build, and each is dispatched with\n * `await`, so the caller already handles a Promise and the bracket's async\n * phases cannot change the hook's return type. `tools/code-dispatch-log` used\n * to sit in this list and no dsh package declares it at all — controlling a\n * name nothing dispatches \"succeeds\" and then does nothing forever, which is\n * exactly the dead configuration this list must not carry.\n *\n * The other admission rule is {@link DEFAULT_SYNC_RETURN_HOOKS}: a hook whose\n * value the caller consumes without awaiting cannot carry ordered participants,\n * so controlling it by default would install a bracket nothing may ever\n * register into — the same dead configuration by a different route.\n */\nexport const DEFAULT_WATERFALL_HOOKS: readonly string[] = [\n 'agent/pre-step',\n 'agent/request',\n 'agent/request-error',\n 'system-prompt/assemble',\n 'tools/pre-execute',\n 'tools/execute',\n 'tools/post-execute',\n 'fs/write-intent',\n 'fs/edit-intent',\n 'approval/request',\n]\n\n/**\n * dsh hooks whose dispatch return value is consumed by the caller without\n * awaiting, and which therefore cannot carry ordered participants. Verified\n * against the installed build:\n *\n * - `llm/stream` — `dsh-llm` dispatches it as\n * `return this.ctx.waterfall(this, \"llm/stream\", options, …)` (no await), and\n * `dsh-session-title-llm` iterates the result with\n * `for await (const chunk of ctx.llm.stream(options))`. A Promise there throws\n * \"not async iterable\". It is a genuine multi-contributor hook (dsh-agent-loop,\n * dsh-llm's own invariant, dsh-session-checkpoint-policy, dsh-session-title),\n * so it is a real loss — but ordering it requires dsh to await the dispatch,\n * not a plugin-side workaround.\n * - `session-telemetry/record` — `dsh-session-telemetry` returns the record\n * straight out of the waterfall and hands it to the backend, so a Promise\n * would be emitted as a record: silent corruption, no error anywhere.\n * - `compaction/summary-error` — `dsh-compaction-basic` dispatches it as\n * `recover: (…) => this.ctx.waterfall(this, \"compaction/summary-error\", …, () => false)`\n * (no await) and consumes the boolean synchronously in\n * `if (!dependencies.recover(error, agent, prepared.shadowedSeqs, signal)) throw error`.\n * A Promise is always truthy, so `!recover(…)` is always false and every\n * summarizer failure is swallowed instead of rethrown — the compaction then\n * proceeds as if recovery had succeeded.\n *\n * Override per profile with `syncReturnHooks` once the host awaits one of them.\n */\nexport const DEFAULT_SYNC_RETURN_HOOKS: readonly string[] = [\n 'llm/stream',\n 'session-telemetry/record',\n 'compaction/summary-error',\n]\n\n/** The dsh serial hook controlled by default. */\nexport const DEFAULT_SERIAL_HOOKS: readonly string[] = ['agent/turn-stopping']\n\n/**\n * This plugin's Cordis Config schema.\n *\n * Since DSH 0.1.7 this *is* the Settings page: the fields are `.volatile()`, so\n * the settings service projects exactly them into an editable form and commits\n * an edit into the live reference rather than remounting the plugin. The\n * defaults are the real hook sets below, so an entry whose `config:` key is\n * empty resolves to them.\n */\nexport const Config = hooksOrderingConfig(DEFAULT_WATERFALL_HOOKS, DEFAULT_SERIAL_HOOKS)\n\n/** The same schema under its pre-0.1.7 name. */\nexport const HooksOrderingConfig = Config\n\n/**\n * Plugin config as a plain value.\n *\n * A loader-resolved config carries the volatile fields as references instead of\n * plain arrays, so this describes the *plain* shape callers and tests construct.\n */\nexport interface ConfigShape {\n /**\n * Waterfall hooks to control. Defaults to {@link DEFAULT_WATERFALL_HOOKS}.\n * Pass `[]` to disable the waterfall service entirely.\n */\n hooks?: readonly string[]\n /**\n * Serial hooks to control. Defaults to {@link DEFAULT_SERIAL_HOOKS}.\n * Pass `[]` to disable the serial service entirely.\n */\n serialHooks?: readonly string[]\n /**\n * Hooks whose return value the host consumes without awaiting, and which must\n * therefore refuse participants. Defaults to {@link DEFAULT_SYNC_RETURN_HOOKS}.\n * Pass `[]` — or a set without a given hook — to opt in to ordering it, once\n * the host awaits that dispatch.\n *\n * Composition-only: this describes the HOST's dispatch, not a user\n * preference, so it is intentionally not a field of {@link Config} and never\n * appears on the Settings page.\n */\n syncReturnHooks?: readonly string[]\n /** When set, the constraint DAG (JSON) is logged to this file on every change. */\n log?: string\n /** Overrides the loader entry id; only non-loader harnesses need it. */\n entryId?: string\n}\n\n/**\n * Mount {@link HookOrdering} and/or {@link SerialHookOrdering} and control the\n * configured hooks once the services are active.\n *\n * `config` may be `null`, and that is the ordinary case rather than an edge\n * case: a loader row whose `config:` key is followed only by comments parses as\n * YAML null, and a default parameter covers `undefined` but not `null`. This\n * package's own `cordis.patch.yml` is written exactly that way — every option\n * commented out — so a null config must mean \"all defaults\", not a crash.\n *\n * Since DSH 0.1.7 there is no second \"user layer\": the row's `config` IS the\n * editable settings, because Cordis validates it against {@link Config} and the\n * settings service projects that schema. A volatile field arrives here as a\n * reference rather than a plain array, which is why every read goes through\n * {@link readSetting} — that also keeps a plain Cordis mount (and an older\n * harness) working, where the fields are plain values.\n *\n * `hooks`/`serialHooks` are read once, at apply time. An edit from the Settings\n * page therefore needs a plugin reload to take effect — the same restart\n * semantics this namespace declared before 0.1.7 (`applies: 'restart'`), since\n * changing the controlled set means adding or removing live hook brackets and\n * the coordinator has no public \"release one hook\" operation.\n *\n * @param ctx - the Cordis context.\n * @param config - which hooks to control and an optional DAG `log` file; null means all defaults.\n */\nexport function apply(ctx: Context, config: ConfigShape | null = {}): void {\n const row = config ?? {}\n const settings: HooksOrderingSettings = {\n hooks: readSetting(row, 'hooks', DEFAULT_WATERFALL_HOOKS),\n serialHooks: readSetting(row, 'serialHooks', DEFAULT_SERIAL_HOOKS),\n log: readSetting(row, 'log', ''),\n }\n const { hooks, serialHooks, log } = settings\n const serviceConfig = log === '' ? {} : { log }\n // Not part of the settings form: the sync-return list describes the HOST's\n // dispatch, not a user preference, so it is composition (row) only.\n const syncReturnHooks = row.syncReturnHooks ?? DEFAULT_SYNC_RETURN_HOOKS\n\n const deps: string[] = []\n if (hooks.length > 0) {\n ctx.plugin(HookOrdering, { ...serviceConfig, syncReturnHooks })\n deps.push('hooksOrdering')\n }\n if (serialHooks.length > 0) {\n ctx.plugin(SerialHookOrdering, serviceConfig)\n deps.push('serialHooksOrdering')\n }\n if (deps.length === 0) return\n\n // The services activate asynchronously; control the hooks once they exist.\n ctx.inject(deps, (ready) => {\n for (const hook of hooks) ready.hooksOrdering.control(hook)\n for (const hook of serialHooks) ready.serialHooksOrdering.control(hook)\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkEA,SAAgB,oBACd,uBACA,oBACQ;AACR,QAAO,OAAO,OAAO;EACnB,OAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,sBAAsB,CAAC,CAAC,UAAU;EACnF,aAAa,OAAO,MAAM,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,mBAAmB,CAAC,CAAC,UAAU;EACtF,KAAK,OAAO,QAAQ,CAAC,QAAQ,GAAG,CAAC,UAAU;EAC5C,CAAC;;;;;;;;;;;;;;;AAgBJ,SAAgB,YACd,QACA,OACA,UACG;CACH,MAAM,QAAS,SAAwD;AACvE,KAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,KAAI,OAAQ,MAA4B,QAAQ,WAE9C,QADiB,MAA6B,KAAK,IAChC;AAErB,QAAO;;;;;;AC9ET,MAAa,OAAO;;;;;;;;;;;;;;;;;;AAmBpB,MAAa,SAAS,CAAC,WAAW;;;;;;;;;;;;;;;;;;;AAoBlC,MAAaA,0BAA6C;CACxD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BD,MAAaC,4BAA+C;CAC1D;CACA;CACA;CACD;;AAGD,MAAaC,uBAA0C,CAAC,sBAAsB;;;;;;;;;;AAW9E,MAAa,SAAS,oBAAoB,yBAAyB,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiExF,SAAgB,MAAM,KAAc,SAA6B,EAAE,EAAQ;CACzE,MAAM,MAAM,UAAU,EAAE;CAMxB,MAAM,EAAE,OAAO,aAAa,QALY;EACtC,OAAO,YAAY,KAAK,SAAS,wBAAwB;EACzD,aAAa,YAAY,KAAK,eAAe,qBAAqB;EAClE,KAAK,YAAY,KAAK,OAAO,GAAG;EACjC;CAED,MAAM,gBAAgB,QAAQ,KAAK,EAAE,GAAG,EAAE,KAAK;CAG/C,MAAM,kBAAkB,IAAI,mBAAmB;CAE/C,MAAMC,OAAiB,EAAE;AACzB,KAAI,MAAM,SAAS,GAAG;AACpB,MAAI,OAAOC,mBAAc;GAAE,GAAG;GAAe;GAAiB,CAAC;AAC/D,OAAK,KAAK,gBAAgB;;AAE5B,KAAI,YAAY,SAAS,GAAG;AAC1B,MAAI,OAAO,oBAAoB,cAAc;AAC7C,OAAK,KAAK,sBAAsB;;AAElC,KAAI,KAAK,WAAW,EAAG;AAGvB,KAAI,OAAO,OAAO,UAAU;AAC1B,OAAK,MAAM,QAAQ,MAAO,OAAM,cAAc,QAAQ,KAAK;AAC3D,OAAK,MAAM,QAAQ,YAAa,OAAM,oBAAoB,QAAQ,KAAK;GACvE"}
|
package/package.json
CHANGED
package/src/settings.ts
CHANGED
|
@@ -38,10 +38,17 @@ export interface HooksOrderingSettings {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* The id this package's own `cordis.patch.yml` uses, and the client half's
|
|
42
|
+
* settings-section id. **Test-only for the server half** — do not read it as "the
|
|
43
|
+
* settings namespace".
|
|
42
44
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
+
* In 0.1.7 a settings entry is named by the **loader entry id** of the row that
|
|
46
|
+
* mounted the plugin, which is not something the plugin knows: the aggregator
|
|
47
|
+
* mounts this package as `dsh-plugin-hooks-ordering`, this package's own patch as
|
|
48
|
+
* `hooks-ordering`, and a row inside an `include` group gets a group prefix
|
|
49
|
+
* (`include:...`). The server half therefore reads its config through the Config
|
|
50
|
+
* it was handed, and the client half claims its entry by **value shape** (see
|
|
51
|
+
* `pickEntry` in client.js) rather than by comparing names.
|
|
45
52
|
*/
|
|
46
53
|
export const SETTINGS_NS = 'hooks-ordering'
|
|
47
54
|
|