@twinsearth/w2m-dsh-plugin 0.5.4 → 0.5.6
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/CHANGELOG.md +133 -0
- package/README.md +3 -1
- package/cordis.patch.yml +27 -4
- package/lib/client.js +145 -18
- package/lib/index.js +16 -11
- package/lib/tools.js +9 -3
- package/package.json +1 -1
- package/src/agent/mdns-wire.mjs +1062 -0
- package/src/agent/mdns.mjs +1311 -0
- package/src/plugin/fleet-target.mjs +84 -20
- package/src/plugin/mdns-discover.mjs +577 -0
- package/src/plugin/tools.mjs +43 -5
|
@@ -27,10 +27,12 @@
|
|
|
27
27
|
*
|
|
28
28
|
* # The wire, and why it is this small
|
|
29
29
|
*
|
|
30
|
-
* Two exact Fetch routes on the host's `/api` carrier, mounted inside its authentication fence
|
|
30
|
+
* Two exact Fetch routes on the host's `/api` carrier, mounted inside its authentication fence.
|
|
31
|
+
* The `/api` prefix is the registry's own channel — see {@link TARGET_ROUTE_PATH} — so these are the
|
|
32
|
+
* paths as the host registers them, and the page fetches the same path document-relative:
|
|
31
33
|
*
|
|
32
|
-
* GET /w2m/target?session_id=… the fleet and this session's selection, as one snapshot
|
|
33
|
-
* POST /w2m/target { session_id, machine_id|null } → the same snapshot
|
|
34
|
+
* GET /api/w2m/target?session_id=… the fleet and this session's selection, as one snapshot
|
|
35
|
+
* POST /api/w2m/target { session_id, machine_id|null } → the same snapshot
|
|
34
36
|
*
|
|
35
37
|
* Plain `Response.json`, no Typert descriptor: this package is dependency-free, and a generated
|
|
36
38
|
* strict Remote contribution is neither reachable from plain JavaScript nor worth a codec for one
|
|
@@ -48,8 +50,19 @@
|
|
|
48
50
|
* get.
|
|
49
51
|
*/
|
|
50
52
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Both routes hang off this exact path; the browser half reads the same constant.
|
|
55
|
+
*
|
|
56
|
+
* The `/api` prefix is not decoration and not ours: `@deepseek-ai/dsh-client-connection` mounts the
|
|
57
|
+
* whole fetch-route registry under its `/api` channel and validates every registration with
|
|
58
|
+
* `endpointFromPath('/api', path)`, which returns undefined for anything that does not start with
|
|
59
|
+
* `/api/`. A `/w2m/target` registration is therefore refused — `connection: invalid exact Fetch route
|
|
60
|
+
* "/w2m/target"` — inside the injected callback, where it failed the *child* fiber and left the host
|
|
61
|
+
* entry looking perfectly healthy. The shipped browser halves all follow the same rule
|
|
62
|
+
* (`dsh-client-file-upload` uses `/api/session/uploadFileBinary`), and the page fetches the same
|
|
63
|
+
* document-relative path.
|
|
64
|
+
*/
|
|
65
|
+
export const TARGET_ROUTE_PATH = '/api/w2m/target';
|
|
53
66
|
|
|
54
67
|
/**
|
|
55
68
|
* The store bucket used when a dispatch carries no session.
|
|
@@ -460,16 +473,40 @@ export function createLocalIdentity(read) {
|
|
|
460
473
|
};
|
|
461
474
|
}
|
|
462
475
|
|
|
476
|
+
/** The one diagnostic for "this host cannot carry the composer control", shared by both causes. */
|
|
477
|
+
const NO_ROUTE_REGISTRY =
|
|
478
|
+
'this host provides no `connection.fetch` route registry, so the composer control cannot reach the plugin';
|
|
479
|
+
|
|
463
480
|
/**
|
|
464
481
|
* Mount the two browser routes, if this host has a fetch-route registry to mount them on.
|
|
465
482
|
*
|
|
466
483
|
* Returning a status object instead of throwing keeps the one interesting case loud *and*
|
|
467
484
|
* survivable: a host built without `@deepseek-ai/dsh-client-connection` still gets a working plugin
|
|
468
|
-
* with
|
|
469
|
-
* load because a UI nicety has nowhere to attach.
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
485
|
+
* with all eight tools and no composer control, which is strictly better than a plugin that refuses
|
|
486
|
+
* to load because a UI nicety has nowhere to attach.
|
|
487
|
+
*
|
|
488
|
+
* # Why the service is declared with `ctx.inject`, and never read as `ctx.connection`
|
|
489
|
+
*
|
|
490
|
+
* Cordis resolves services per fiber: a property read of a name this plugin did not declare in its
|
|
491
|
+
* `inject` list **throws** — `cannot get property "connection" without inject` — and it throws
|
|
492
|
+
* whether or not some other plugin provides that service. `ctx.inject(names, callback)` is the
|
|
493
|
+
* declaration that makes the read legal, and it is the *optional* form: the plugin's own list stays
|
|
494
|
+
* `['tools']`, so a host with no Connection service still activates and still registers all eight
|
|
495
|
+
* tools — the callback below simply never runs. v0.5.4 shipped the plain property read, and the whole
|
|
496
|
+
* entry then failed on a web host and on a base-only host alike, because the throw does not depend on
|
|
497
|
+
* the service existing.
|
|
498
|
+
*
|
|
499
|
+
* `ctx.inject` also drops the ordering assumption a one-shot read would carry: the Loader creates
|
|
500
|
+
* sibling entries concurrently (`EntryGroup.update` → `Promise.all`), so a host that *does* provide
|
|
501
|
+
* Connection may still be activating it while this plugin's `apply` runs. A synchronous read would
|
|
502
|
+
* report "no registry" for a control that was about to become attachable.
|
|
503
|
+
*
|
|
504
|
+
* The status object is mutated in place when the callback runs, so `w2m_status` — called long after
|
|
505
|
+
* activation — reads what happened rather than what was knowable at `apply` time.
|
|
506
|
+
*
|
|
507
|
+
* @param {object|null|undefined} ctx Cordis context; `ctx.inject(['connection'], …)` is how the
|
|
508
|
+
* optional service is declared and read. A context without `inject` gets no route and no warning,
|
|
509
|
+
* which is the same status as a host that has no Connection at all.
|
|
473
510
|
* @param {object} deps
|
|
474
511
|
* @param {() => object} deps.cfg Current merged plugin config.
|
|
475
512
|
* @param {object} deps.store The selection store.
|
|
@@ -480,17 +517,43 @@ export function createLocalIdentity(read) {
|
|
|
480
517
|
* when the host has a Connection service that lacks the registry — the one case worth a warning.
|
|
481
518
|
*/
|
|
482
519
|
export function registerTargetRoutes(ctx, deps) {
|
|
483
|
-
const
|
|
520
|
+
const status = {
|
|
521
|
+
mounted: false,
|
|
522
|
+
unexpected: false,
|
|
523
|
+
reason: NO_ROUTE_REGISTRY,
|
|
524
|
+
};
|
|
525
|
+
if (typeof ctx?.inject === 'function') {
|
|
526
|
+
ctx.inject(['connection'], (webCtx) => {
|
|
527
|
+
mountTargetRoutes(webCtx.connection, deps, status, ctx.logger);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return status;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Attach the route pair to one host's fetch registry, recording the outcome on `status`.
|
|
535
|
+
*
|
|
536
|
+
* The registration is owned by the context it is made through — `register` is a Connection method
|
|
537
|
+
* bound to the calling fiber, so it is released with the injected child fiber — which is why there
|
|
538
|
+
* is no `ctx.effect` wrapper here.
|
|
539
|
+
*
|
|
540
|
+
* @param {object|null|undefined} connection The `connection` service, when the host has one.
|
|
541
|
+
* @param {object} deps
|
|
542
|
+
* @param {{mounted: boolean, unexpected: boolean, reason: string|null}} status Mutated in place.
|
|
543
|
+
* @param {object} [logger] The plugin's logger, for the one mismatch worth a line.
|
|
544
|
+
* @returns {void}
|
|
545
|
+
*/
|
|
546
|
+
function mountTargetRoutes(connection, deps, status, logger) {
|
|
484
547
|
const registry = connection?.fetch;
|
|
485
548
|
if (registry === undefined || typeof registry.register !== 'function') {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
549
|
+
// The distinction matters to the log: a host with **no** Connection at all is simply not a web
|
|
550
|
+
// composition, and warning about it would be noise on every headless run. A host that has
|
|
551
|
+
// Connection but no fetch registry is a real mismatch worth one line.
|
|
552
|
+
status.unexpected = connection !== null && connection !== undefined;
|
|
553
|
+
if (status.unexpected) {
|
|
554
|
+
logger?.warn?.(`w2m: the composer target control is inactive — ${NO_ROUTE_REGISTRY}`);
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
494
557
|
}
|
|
495
558
|
|
|
496
559
|
let cache = { at: 0, value: null };
|
|
@@ -599,5 +662,6 @@ export function registerTargetRoutes(ctx, deps) {
|
|
|
599
662
|
},
|
|
600
663
|
});
|
|
601
664
|
|
|
602
|
-
|
|
665
|
+
status.mounted = true;
|
|
666
|
+
status.reason = null;
|
|
603
667
|
}
|