super-dsh 0.1.0 → 0.1.2
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/agent-agy/bridge/agy_bridge.py +250 -0
- package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
- package/agent-agy/cordis.patch.yml +49 -0
- package/agent-agy/dist/adapter.d.ts +15 -0
- package/agent-agy/dist/adapter.js +42 -0
- package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
- package/agent-agy/dist/agent-preset-agy.js +169 -0
- package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
- package/agent-agy/dist/agent-preset-projection.js +33 -0
- package/agent-agy/dist/agent.d.ts +137 -0
- package/agent-agy/dist/agent.js +493 -0
- package/agent-agy/dist/agy-cli-client.d.ts +137 -0
- package/agent-agy/dist/agy-cli-client.js +301 -0
- package/agent-agy/dist/agy-client.d.ts +160 -0
- package/agent-agy/dist/agy-client.js +252 -0
- package/agent-agy/dist/agy-sessions.d.ts +24 -0
- package/agent-agy/dist/agy-sessions.js +116 -0
- package/agent-agy/dist/agy-store.d.ts +39 -0
- package/agent-agy/dist/agy-store.js +83 -0
- package/agent-agy/dist/inbox.d.ts +26 -0
- package/agent-agy/dist/inbox.js +53 -0
- package/agent-agy/dist/index.d.ts +80 -0
- package/agent-agy/dist/index.js +505 -0
- package/agent-agy/dist/models.d.ts +41 -0
- package/agent-agy/dist/models.js +74 -0
- package/agent-agy/dist/world-plugin.d.ts +9 -0
- package/agent-agy/dist/world-plugin.js +97 -0
- package/agent-agy/package.json +64 -0
- package/agent-claude/cordis.patch.yml +42 -0
- package/agent-claude/dist/adapter.js +62 -0
- package/agent-claude/dist/agent-preset-claude.js +169 -0
- package/agent-claude/dist/agent-preset-projection.js +30 -0
- package/agent-claude/dist/agent.js +1271 -0
- package/agent-claude/dist/claude-client.js +357 -0
- package/agent-claude/dist/claude-events.js +223 -0
- package/agent-claude/dist/claude-home.js +29 -0
- package/agent-claude/dist/commands.js +182 -0
- package/agent-claude/dist/content.js +134 -0
- package/agent-claude/dist/inbox.js +53 -0
- package/agent-claude/dist/index.js +555 -0
- package/agent-claude/dist/input-queue.js +64 -0
- package/agent-claude/dist/interaction.js +271 -0
- package/agent-claude/dist/models.js +158 -0
- package/agent-claude/dist/permission.js +120 -0
- package/agent-claude/dist/session-id.js +20 -0
- package/agent-claude/dist/session-map.js +165 -0
- package/agent-claude/dist/world-plugin.js +14 -0
- package/agent-claude/package.json +74 -0
- package/agent-codex/README.md +74 -0
- package/agent-codex/cordis.patch.yml +77 -0
- package/agent-codex/dist/adapter.js +55 -0
- package/agent-codex/dist/agent-preset-codex.js +171 -0
- package/agent-codex/dist/agent-preset-projection.js +37 -0
- package/agent-codex/dist/agent.js +1056 -0
- package/agent-codex/dist/app-home.js +65 -0
- package/agent-codex/dist/codex-client.js +469 -0
- package/agent-codex/dist/codex-events.js +297 -0
- package/agent-codex/dist/codex-store.js +190 -0
- package/agent-codex/dist/inbox.js +53 -0
- package/agent-codex/dist/index.js +605 -0
- package/agent-codex/dist/models.js +152 -0
- package/agent-codex/dist/permission.js +71 -0
- package/agent-codex/dist/session-map.js +170 -0
- package/agent-codex/dist/spike.js +46 -0
- package/agent-codex/dist/world-plugin.js +96 -0
- package/agent-codex/package.json +78 -0
- package/agent-hermes/README.md +144 -0
- package/agent-hermes/cordis.patch.yml +83 -0
- package/agent-hermes/dist/adapter.js +83 -0
- package/agent-hermes/dist/agent-preset-hermes.js +171 -0
- package/agent-hermes/dist/agent-preset-projection.js +34 -0
- package/agent-hermes/dist/agent.js +1068 -0
- package/agent-hermes/dist/hermes-client.js +953 -0
- package/agent-hermes/dist/hermes-events.js +289 -0
- package/agent-hermes/dist/hermes-store.js +158 -0
- package/agent-hermes/dist/inbox.js +53 -0
- package/agent-hermes/dist/index.js +541 -0
- package/agent-hermes/dist/models.js +324 -0
- package/agent-hermes/dist/permission.js +60 -0
- package/agent-hermes/dist/world-plugin.js +101 -0
- package/agent-hermes/package.json +76 -0
- package/agent-hub/cordis.patch.yml +12 -0
- package/agent-hub/dist/agent-roster.d.ts +34 -0
- package/agent-hub/dist/agent-roster.js +27 -0
- package/agent-hub/dist/carrier.d.ts +43 -0
- package/agent-hub/dist/carrier.js +241 -0
- package/agent-hub/dist/client-shim.d.ts +28 -0
- package/agent-hub/dist/client-shim.js +251 -0
- package/agent-hub/dist/envelope.d.ts +54 -0
- package/agent-hub/dist/envelope.js +92 -0
- package/agent-hub/dist/gateway.d.ts +28 -0
- package/agent-hub/dist/gateway.js +18 -0
- package/agent-hub/dist/index-pass.d.ts +22 -0
- package/agent-hub/dist/index-pass.js +47 -0
- package/agent-hub/dist/index.d.ts +29 -0
- package/agent-hub/dist/index.js +29 -0
- package/agent-hub/dist/labels.d.ts +45 -0
- package/agent-hub/dist/labels.js +83 -0
- package/agent-hub/dist/ownership.d.ts +37 -0
- package/agent-hub/dist/ownership.js +54 -0
- package/agent-hub/dist/roster.d.ts +28 -0
- package/agent-hub/dist/roster.js +38 -0
- package/agent-hub/dist/rpc.d.ts +38 -0
- package/agent-hub/dist/rpc.js +52 -0
- package/agent-hub/dist/spawn-world.d.ts +46 -0
- package/agent-hub/dist/spawn-world.js +72 -0
- package/agent-hub/dist/targets.d.ts +33 -0
- package/agent-hub/dist/targets.js +30 -0
- package/agent-hub/dist/world-entry.d.ts +31 -0
- package/agent-hub/dist/world-entry.js +51 -0
- package/agent-hub/dist/world-host.d.ts +69 -0
- package/agent-hub/dist/world-host.js +44 -0
- package/agent-hub/dist/world-join.d.ts +16 -0
- package/agent-hub/dist/world-join.js +122 -0
- package/agent-hub/dist/world-mount.d.ts +52 -0
- package/agent-hub/dist/world-mount.js +52 -0
- package/agent-hub/dist/world-mux.d.ts +72 -0
- package/agent-hub/dist/world-mux.js +224 -0
- package/agent-hub/dist/world-provision.d.ts +20 -0
- package/agent-hub/dist/world-provision.js +122 -0
- package/agent-hub/dist/world-web-server.d.ts +162 -0
- package/agent-hub/dist/world-web-server.js +248 -0
- package/agent-hub/lib/client/index.js +242 -0
- package/agent-hub/package.json +65 -0
- package/agent-omp/README.md +77 -0
- package/agent-omp/cordis.patch.yml +92 -0
- package/agent-omp/dist/adapter.js +36 -0
- package/agent-omp/dist/agent-preset-omp.js +171 -0
- package/agent-omp/dist/agent-preset-projection.js +36 -0
- package/agent-omp/dist/agent.js +1145 -0
- package/agent-omp/dist/app-home.js +86 -0
- package/agent-omp/dist/discovery.js +116 -0
- package/agent-omp/dist/inbox.js +53 -0
- package/agent-omp/dist/index.js +574 -0
- package/agent-omp/dist/knobs.js +50 -0
- package/agent-omp/dist/lazy-rpc.js +161 -0
- package/agent-omp/dist/mobile/gesture.js +148 -0
- package/agent-omp/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp/dist/mobile-boot.js +38 -0
- package/agent-omp/dist/models.js +299 -0
- package/agent-omp/dist/omp-cli.js +147 -0
- package/agent-omp/dist/omp-disk-discovery.js +178 -0
- package/agent-omp/dist/omp-store.js +518 -0
- package/agent-omp/dist/pairing.js +45 -0
- package/agent-omp/dist/permission.js +96 -0
- package/agent-omp/dist/protocol.js +13 -0
- package/agent-omp/dist/rpc-types.js +1 -0
- package/agent-omp/dist/sdk-client.js +378 -0
- package/agent-omp/dist/sidecar-client.js +195 -0
- package/agent-omp/dist/store/adapter.js +47 -0
- package/agent-omp/dist/store/db.js +210 -0
- package/agent-omp/dist/store/index.js +59 -0
- package/agent-omp/dist/store/reconcile.js +51 -0
- package/agent-omp/dist/store/schema.js +40 -0
- package/agent-omp/dist/world-plugin.js +96 -0
- package/agent-omp/package.json +97 -0
- package/agent-omp/sidecar/main.ts +481 -0
- package/agent-pi/README.md +67 -0
- package/agent-pi/cordis.patch.yml +80 -0
- package/agent-pi/dist/adapter.js +53 -0
- package/agent-pi/dist/agent-preset-pi.js +169 -0
- package/agent-pi/dist/agent-preset-projection.js +29 -0
- package/agent-pi/dist/agent.js +949 -0
- package/agent-pi/dist/inbox.js +53 -0
- package/agent-pi/dist/index.js +595 -0
- package/agent-pi/dist/knobs.js +24 -0
- package/agent-pi/dist/models.js +200 -0
- package/agent-pi/dist/permission.js +66 -0
- package/agent-pi/dist/pi-client.js +224 -0
- package/agent-pi/dist/pi-events.js +155 -0
- package/agent-pi/dist/pi-home.js +55 -0
- package/agent-pi/dist/safe-json.js +25 -0
- package/agent-pi/dist/session-map.js +119 -0
- package/agent-pi/dist/world-plugin.js +97 -0
- package/agent-pi/package.json +77 -0
- package/cordis.patch.yml +22 -19
- package/package.json +88 -25
- package/client.js +0 -4
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes model catalog for the Dash model selector (AW-H Task 5).
|
|
3
|
+
*
|
|
4
|
+
* Source of truth is the gateway `model.options` RPC (247ms-3.3s live — it
|
|
5
|
+
* probes providers), field-name authority = the Task 1 LIVE capture in
|
|
6
|
+
* `test/fixtures/gateway-rpc-samples.json`:
|
|
7
|
+
*
|
|
8
|
+
* - top level `{ providers: [<row>...], model: "<current default id>",
|
|
9
|
+
* provider: "<current provider slug>" }` — the default lives in the top-level
|
|
10
|
+
* strings, NOT inside the provider rows;
|
|
11
|
+
* - provider row `{ slug, name, is_current, is_user_defined,
|
|
12
|
+
* models: [<plain id string>...], total_models, source, authenticated,
|
|
13
|
+
* capabilities: {"<model-id>": {fast, reasoning, can_disable_reasoning}},
|
|
14
|
+
* featured_models, aliases? }` — model entries are plain id strings, and the
|
|
15
|
+
* current provider's row carries `is_current: true`.
|
|
16
|
+
*
|
|
17
|
+
* `id` is the verbatim selection string — directly consumable by
|
|
18
|
+
* `session.create {model}` and `command.dispatch "/model <id>"`. Capabilities
|
|
19
|
+
* carry no context-window hints (Task 1 §3⑦), so `contextWindow` is left unset
|
|
20
|
+
* rather than invented (Task 7's route metadata treats it as optional).
|
|
21
|
+
*
|
|
22
|
+
* Fail-soft everywhere: a missing client, a failing RPC, or a garbage response
|
|
23
|
+
* degrade to the empty catalog `{ provider: "Hermes", models: [],
|
|
24
|
+
* defaultModel: undefined }` — never a throw. `defaultModel: undefined` is the
|
|
25
|
+
* explicit skip signal so Task 7 never pushes a placeholder default outward
|
|
26
|
+
* (dev-rules: 占位值禁止外流).
|
|
27
|
+
*
|
|
28
|
+
* Trim rules (2026-09-17 parity ruling, recorded per dev-rules §12), applied
|
|
29
|
+
* at mapping time so every downstream consumer (per-slug routes, route
|
|
30
|
+
* pinning, the default-model push) sees the same trimmed projection:
|
|
31
|
+
*
|
|
32
|
+
* 1. `source: "virtual"` rows are dropped (the `moa` Mixture-of-Agents
|
|
33
|
+
* synthesizer is not a selectable endpoint).
|
|
34
|
+
* 2. Mirrored endpoints whose model-id SETS are identical collapse onto one
|
|
35
|
+
* row (e.g. `copilot` vs `copilot-acp`, `zai-plan` vs `zhipu`) — the first
|
|
36
|
+
* row in gateway order wins, except a later mirrored row flagged
|
|
37
|
+
* `is_current` displaces a non-current earlier one (the r4 dedupe
|
|
38
|
+
* preference applied to whole rows). The id, not the endpoint, is the
|
|
39
|
+
* selection contract, so a mirror is just a second name for the same ids.
|
|
40
|
+
* 3. Endpoints listing more than 50 models are reordered: `featured_models`
|
|
41
|
+
* first (gateway order), then the remainder alphabetically. Small rows
|
|
42
|
+
* keep the gateway's own order untouched.
|
|
43
|
+
*
|
|
44
|
+
* The `authenticated !== true` filter predates this ruling and is unchanged.
|
|
45
|
+
*
|
|
46
|
+
* Row `name`s survive in `HermesCatalog.providerNames` so the synchronous
|
|
47
|
+
* `providerInfo(slug)` surface can serve the gateway's human label; a
|
|
48
|
+
* module-level last-known cache (written on every successful fetch) keeps
|
|
49
|
+
* those names available before and between fetches, with a capitalized-slug
|
|
50
|
+
* fallback (`hermesProviderDisplayName`).
|
|
51
|
+
*
|
|
52
|
+
* Cache: the catalog is an async/expensive source, so reads go through a
|
|
53
|
+
* module-level in-process cache with a TTL (single-world posture — keyed on
|
|
54
|
+
* nothing, deliberately not on the client instance). Task 7's probe strategy
|
|
55
|
+
* (spawn a dedicated probe client → `model.options` → close it) therefore
|
|
56
|
+
* works: the first fetch populates the cache and later client-less calls are
|
|
57
|
+
* served from it until the TTL expires. Only non-empty successful catalogs are
|
|
58
|
+
* cached — failures and garbage are retried on the next call. Concurrent
|
|
59
|
+
* callers share one in-flight fetch (single-flight).
|
|
60
|
+
*/
|
|
61
|
+
/** Default cache TTL: 5 minutes. */
|
|
62
|
+
export const HERMES_MODEL_CATALOG_DEFAULT_TTL_MS = 5 * 60_000;
|
|
63
|
+
/** The fail-soft brand shown when the real provider slug is unavailable. */
|
|
64
|
+
const FALLBACK_PROVIDER = "Hermes";
|
|
65
|
+
/** A fresh empty catalog. Never shared — callers get their own object. */
|
|
66
|
+
function emptyCatalog() {
|
|
67
|
+
return { provider: FALLBACK_PROVIDER, models: [], defaultModel: undefined, providerNames: {} };
|
|
68
|
+
}
|
|
69
|
+
/** True when the capability record says the model is fast. */
|
|
70
|
+
function capFast(cap) {
|
|
71
|
+
return cap !== null && typeof cap === "object" && !Array.isArray(cap) && cap["fast"] === true;
|
|
72
|
+
}
|
|
73
|
+
/** True when the capability record says the model reasons. */
|
|
74
|
+
function capReasoning(cap) {
|
|
75
|
+
return (cap !== null &&
|
|
76
|
+
typeof cap === "object" &&
|
|
77
|
+
!Array.isArray(cap) &&
|
|
78
|
+
cap["reasoning"] === true);
|
|
79
|
+
}
|
|
80
|
+
/** Label = the id plus capability hints when they are trivially available. */
|
|
81
|
+
function labelFor(id, cap) {
|
|
82
|
+
const hints = [];
|
|
83
|
+
if (capFast(cap))
|
|
84
|
+
hints.push("fast");
|
|
85
|
+
if (capReasoning(cap))
|
|
86
|
+
hints.push("reasoning");
|
|
87
|
+
return hints.length === 0 ? id : `${id} (${hints.join(", ")})`;
|
|
88
|
+
}
|
|
89
|
+
/** Endpoints with more models than this are reordered featured-first (trim 3). */
|
|
90
|
+
export const HERMES_FEATURED_REORDER_MIN_MODELS = 50;
|
|
91
|
+
/** The row's model ids, strings only, gateway order preserved. */
|
|
92
|
+
function sanitizeIds(models) {
|
|
93
|
+
return models.filter((id) => typeof id === "string" && id !== "");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Trim 3: for large endpoints (> {@link HERMES_FEATURED_REORDER_MIN_MODELS}
|
|
97
|
+
* models) put the gateway's `featured_models` first (their own order), then
|
|
98
|
+
* the remaining ids alphabetically. Smaller rows keep gateway order.
|
|
99
|
+
*/
|
|
100
|
+
function orderFeaturedFirst(ids, featured) {
|
|
101
|
+
if (ids.length <= HERMES_FEATURED_REORDER_MIN_MODELS || !Array.isArray(featured))
|
|
102
|
+
return ids;
|
|
103
|
+
const featuredIds = featured.filter((id) => typeof id === "string" && id !== "" && ids.includes(id));
|
|
104
|
+
const seen = new Set(featuredIds);
|
|
105
|
+
const rest = ids.filter((id) => !seen.has(id)).sort((a, b) => a.localeCompare(b));
|
|
106
|
+
return [...featuredIds, ...rest];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Validate one provider row against the servability filter and the trims.
|
|
110
|
+
* Returns `undefined` when the row is dropped: malformed, unauthenticated,
|
|
111
|
+
* `source: "virtual"` (trim 1). Mirror collapse (trim 2) happens AFTER this
|
|
112
|
+
* per-row preparation — it needs each candidate's id set, and the winner is
|
|
113
|
+
* chosen at the group level in mapHermesModelOptions.
|
|
114
|
+
*/
|
|
115
|
+
function prepareRow(row) {
|
|
116
|
+
if (row === null || typeof row !== "object" || Array.isArray(row))
|
|
117
|
+
return undefined;
|
|
118
|
+
const raw = row;
|
|
119
|
+
const slug = raw["slug"];
|
|
120
|
+
if (typeof slug !== "string" || slug === "")
|
|
121
|
+
return undefined;
|
|
122
|
+
if (raw["source"] === "virtual")
|
|
123
|
+
return undefined; // trim 1: virtual synthesizers are not selectable
|
|
124
|
+
const authenticated = raw["authenticated"];
|
|
125
|
+
const isCurrent = raw["is_current"] === true;
|
|
126
|
+
if (authenticated !== true && !(isCurrent && authenticated === undefined))
|
|
127
|
+
return;
|
|
128
|
+
const models = raw["models"];
|
|
129
|
+
if (!Array.isArray(models))
|
|
130
|
+
return undefined;
|
|
131
|
+
const ids = orderFeaturedFirst(sanitizeIds(models), raw["featured_models"]);
|
|
132
|
+
if (ids.length === 0)
|
|
133
|
+
return undefined;
|
|
134
|
+
const name = raw["name"];
|
|
135
|
+
const capabilities = raw["capabilities"];
|
|
136
|
+
return {
|
|
137
|
+
slug,
|
|
138
|
+
name: typeof name === "string" && name !== "" ? name : undefined,
|
|
139
|
+
isCurrent,
|
|
140
|
+
caps: capabilities !== null && typeof capabilities === "object" && !Array.isArray(capabilities)
|
|
141
|
+
? capabilities
|
|
142
|
+
: undefined,
|
|
143
|
+
ids,
|
|
144
|
+
mirrorKey: [...ids].sort().join("\u0000"),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Append one prepared row's entries onto `out`.
|
|
149
|
+
*
|
|
150
|
+
* Dedupe (r4, unchanged): the id is the VERBATIM gateway selection string
|
|
151
|
+
* (`session.create {model}` / `/model <id>` consume it as-is) — never
|
|
152
|
+
* provider-prefixed — so the same id listed under several slugs collapses
|
|
153
|
+
* onto ONE canonical entry, preferring the `is_current` row, else the first
|
|
154
|
+
* surviving row in the gateway's own order. The `seen` map records each id's
|
|
155
|
+
* emitted index + whether its source was current, so a current-row duplicate
|
|
156
|
+
* REPLACES an earlier non-current entry in place (order preserved).
|
|
157
|
+
*/
|
|
158
|
+
function appendPrepared(prepared, out, seen) {
|
|
159
|
+
for (const id of prepared.ids) {
|
|
160
|
+
const entry = {
|
|
161
|
+
id,
|
|
162
|
+
label: labelFor(id, prepared.caps === undefined ? undefined : prepared.caps[id]),
|
|
163
|
+
provider: prepared.slug,
|
|
164
|
+
// contextWindow intentionally never set: the gateway's capability hints
|
|
165
|
+
// carry no context windows (Task 1 §3⑦) — values would be invented.
|
|
166
|
+
};
|
|
167
|
+
const existing = seen.get(id);
|
|
168
|
+
if (existing === undefined) {
|
|
169
|
+
seen.set(id, { index: out.length, isCurrent: prepared.isCurrent });
|
|
170
|
+
out.push(entry);
|
|
171
|
+
}
|
|
172
|
+
else if (prepared.isCurrent && !existing.isCurrent) {
|
|
173
|
+
// current-provider row wins the duplicate — replace in place (order preserved)
|
|
174
|
+
out[existing.index] = entry;
|
|
175
|
+
seen.set(id, { index: existing.index, isCurrent: true });
|
|
176
|
+
}
|
|
177
|
+
// else: later duplicate from a non-current (or already-current) row → dropped.
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Pure mapping from a `model.options` response to the catalog. Total and
|
|
182
|
+
* fail-soft: any shape degrades toward the empty catalog, never throws.
|
|
183
|
+
* Exported so tests (and Task 7, if it ever holds a raw response) can map
|
|
184
|
+
* without touching the cache.
|
|
185
|
+
*/
|
|
186
|
+
export function mapHermesModelOptions(response) {
|
|
187
|
+
if (response === null || typeof response !== "object" || Array.isArray(response))
|
|
188
|
+
return emptyCatalog();
|
|
189
|
+
const raw = response;
|
|
190
|
+
const entries = [];
|
|
191
|
+
const seen = new Map();
|
|
192
|
+
const providerNames = {};
|
|
193
|
+
const providers = raw["providers"];
|
|
194
|
+
if (Array.isArray(providers)) {
|
|
195
|
+
// Trim 2 (mirror collapse): group prepared rows by their id SET; the
|
|
196
|
+
// first row of each group survives in gateway order, unless a later
|
|
197
|
+
// mirrored row is `is_current` and displaces a non-current winner.
|
|
198
|
+
const byMirrorKey = new Map();
|
|
199
|
+
const ordered = [];
|
|
200
|
+
for (const candidate of providers) {
|
|
201
|
+
const prepared = prepareRow(candidate);
|
|
202
|
+
if (prepared === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
const existing = byMirrorKey.get(prepared.mirrorKey);
|
|
205
|
+
if (existing === undefined) {
|
|
206
|
+
byMirrorKey.set(prepared.mirrorKey, prepared);
|
|
207
|
+
ordered.push(prepared);
|
|
208
|
+
}
|
|
209
|
+
else if (prepared.isCurrent && !existing.isCurrent) {
|
|
210
|
+
ordered.splice(ordered.indexOf(existing), 1, prepared);
|
|
211
|
+
byMirrorKey.set(prepared.mirrorKey, prepared);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const prepared of ordered) {
|
|
215
|
+
appendPrepared(prepared, entries, seen);
|
|
216
|
+
if (prepared.name !== undefined)
|
|
217
|
+
providerNames[prepared.slug] = prepared.name;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const model = raw["model"];
|
|
221
|
+
const provider = raw["provider"];
|
|
222
|
+
return {
|
|
223
|
+
provider: typeof provider === "string" && provider !== "" ? provider : FALLBACK_PROVIDER,
|
|
224
|
+
models: entries,
|
|
225
|
+
defaultModel: typeof model === "string" && model !== "" ? model : undefined,
|
|
226
|
+
providerNames,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
let cached = null;
|
|
230
|
+
let inflight = null;
|
|
231
|
+
/** Test seam (the codex `setCodexFactory` pattern): drop the cached catalog and any in-flight fetch. */
|
|
232
|
+
export function resetHermesModelCatalogCache() {
|
|
233
|
+
cached = null;
|
|
234
|
+
inflight = null;
|
|
235
|
+
resetHermesProviderInfoCache();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Last-known provider display names (module-level, single-world posture):
|
|
239
|
+
* every successful non-empty fetch rewrites this, so the SYNCHRONOUS
|
|
240
|
+
* `providerInfo(slug)` surface can serve the gateway's human row names
|
|
241
|
+
* before and between async fetches (the catalog itself is only reachable
|
|
242
|
+
* through an await). `hermesProviderDisplayName` falls back to the
|
|
243
|
+
* capitalized slug before the first fetch or for slugs the gateway never
|
|
244
|
+
* named.
|
|
245
|
+
*/
|
|
246
|
+
let lastKnownProviderNames = {};
|
|
247
|
+
/**
|
|
248
|
+
* Human display name for a provider slug: the last-known gateway row name
|
|
249
|
+
* when one was fetched, else the capitalized slug (`openrouter` →
|
|
250
|
+
* `Openrouter`). Synchronous by contract (`LlmAdapter.providerInfo`).
|
|
251
|
+
*/
|
|
252
|
+
export function hermesProviderDisplayName(slug) {
|
|
253
|
+
const known = lastKnownProviderNames[slug];
|
|
254
|
+
if (typeof known === "string" && known !== "")
|
|
255
|
+
return known;
|
|
256
|
+
return slug === "" ? FALLBACK_PROVIDER : slug.charAt(0).toUpperCase() + slug.slice(1);
|
|
257
|
+
}
|
|
258
|
+
/** Test seam: drop the last-known provider display names. */
|
|
259
|
+
export function resetHermesProviderInfoCache() {
|
|
260
|
+
lastKnownProviderNames = {};
|
|
261
|
+
}
|
|
262
|
+
/** Learn the fetched catalog's row names (successful non-empty fetches only). */
|
|
263
|
+
function rememberProviderNames(catalog) {
|
|
264
|
+
if (Object.keys(catalog.providerNames).length > 0)
|
|
265
|
+
lastKnownProviderNames = catalog.providerNames;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The distinct provider slugs of a catalog, gateway row order preserved —
|
|
269
|
+
* the `handle.replace` route set for the per-slug registration (2026-09-17
|
|
270
|
+
* parity ruling). Order is the gateway's own, so the selector lists groups
|
|
271
|
+
* the way the gateway ranks them.
|
|
272
|
+
*/
|
|
273
|
+
export function hermesProviderSlugs(catalog) {
|
|
274
|
+
const seen = new Set();
|
|
275
|
+
const slugs = [];
|
|
276
|
+
for (const entry of catalog.models) {
|
|
277
|
+
if (seen.has(entry.provider))
|
|
278
|
+
continue;
|
|
279
|
+
seen.add(entry.provider);
|
|
280
|
+
slugs.push(entry.provider);
|
|
281
|
+
}
|
|
282
|
+
return slugs;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Read the Hermes model catalog (async — the source is a gateway RPC).
|
|
286
|
+
*
|
|
287
|
+
* - fresh cache → served, even when `client` is omitted (the Task 7
|
|
288
|
+
* probe-client-then-close pattern);
|
|
289
|
+
* - otherwise `client.modelOptions()` is fetched (single-flight; concurrent
|
|
290
|
+
* callers share one RPC) and mapped; a non-empty success populates the
|
|
291
|
+
* cache for `ttlMs` (default 5 min);
|
|
292
|
+
* - missing client / failing RPC / garbage response → the empty catalog
|
|
293
|
+
* (`defaultModel: undefined` = the placeholder-skip signal). Never throws.
|
|
294
|
+
*/
|
|
295
|
+
export async function readHermesModelCatalog(client, options) {
|
|
296
|
+
const ttlMs = options?.ttlMs ?? HERMES_MODEL_CATALOG_DEFAULT_TTL_MS;
|
|
297
|
+
const useCache = ttlMs > 0;
|
|
298
|
+
if (useCache && cached !== null && cached.expiresAt > Date.now())
|
|
299
|
+
return cached.value;
|
|
300
|
+
if (client === undefined)
|
|
301
|
+
return emptyCatalog();
|
|
302
|
+
if (inflight !== null)
|
|
303
|
+
return inflight;
|
|
304
|
+
const fetch = (async () => {
|
|
305
|
+
try {
|
|
306
|
+
const response = await client.modelOptions();
|
|
307
|
+
const catalog = mapHermesModelOptions(response);
|
|
308
|
+
if (catalog.models.length > 0)
|
|
309
|
+
rememberProviderNames(catalog); // learn row names even when the TTL cache is bypassed
|
|
310
|
+
if (useCache && catalog.models.length > 0) {
|
|
311
|
+
cached = { value: catalog, expiresAt: Date.now() + ttlMs };
|
|
312
|
+
}
|
|
313
|
+
return catalog;
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return emptyCatalog(); // fail-soft; failures are never cached
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
inflight = null;
|
|
320
|
+
}
|
|
321
|
+
})();
|
|
322
|
+
inflight = fetch;
|
|
323
|
+
return fetch;
|
|
324
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** The Dash preset table (mirrors the base bundle's permission plugin config). */
|
|
2
|
+
const PRESETS = {
|
|
3
|
+
"danger-full-access": true,
|
|
4
|
+
"workspace-write": true,
|
|
5
|
+
"read-only": true,
|
|
6
|
+
};
|
|
7
|
+
/** Narrow an untrusted value to a known preset name. */
|
|
8
|
+
export function isPresetName(raw) {
|
|
9
|
+
return typeof raw === "string" && Object.hasOwn(PRESETS, raw);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `HERMES_APPROVAL_MODE` when it names a Dash permission preset, else
|
|
13
|
+
* undefined. Record-only (no launch flag exists on the runtime-approval line);
|
|
14
|
+
* an invalid value is IGNORED.
|
|
15
|
+
*/
|
|
16
|
+
export function envApprovalMode() {
|
|
17
|
+
const raw = process.env.HERMES_APPROVAL_MODE;
|
|
18
|
+
return raw !== undefined && isPresetName(raw) ? raw : undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The default preset for FUTURE sessions, as the permission service
|
|
22
|
+
* resolves it (settings-backed; the profile patch pins the base default).
|
|
23
|
+
* Undefined when the service is absent or its getter fails.
|
|
24
|
+
*/
|
|
25
|
+
export function defaultPermissionPreset(ctx) {
|
|
26
|
+
const service = ctx.get("permissionPresets");
|
|
27
|
+
if (service === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
const preset = service.defaultPreset;
|
|
31
|
+
return isPresetName(preset) ? preset : undefined;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The session's effective preset: the last `permission/preset` event. */
|
|
38
|
+
export function presetFromEvents(events) {
|
|
39
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
40
|
+
const event = events[index];
|
|
41
|
+
if (event.type === "permission/preset" && isPresetName(event.data?.preset))
|
|
42
|
+
return event.data.preset;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Map a Dash approval outcome onto the gateway's `approval.respond` choice.
|
|
48
|
+
* Pure: no service, no I/O. `allowed-once` grants exactly one run; an
|
|
49
|
+
* `allowed-always` grant maps to `"always"` ONLY when the pending approval
|
|
50
|
+
* offered that choice (`options` = the gateway's `choices` array); any other
|
|
51
|
+
* outcome — rejected / cancelled / unavailable / an unknown value — fails
|
|
52
|
+
* closed to `"deny"`.
|
|
53
|
+
*/
|
|
54
|
+
export function approvalChoiceFromOutcome(outcome, options) {
|
|
55
|
+
if (outcome === "allowed-once")
|
|
56
|
+
return "once";
|
|
57
|
+
if (outcome === "allowed-always" && options !== undefined && options.includes("always"))
|
|
58
|
+
return "always";
|
|
59
|
+
return "deny";
|
|
60
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aw.agent-adapter-hermes — the Hermes adapter's WORLD plugin (spec S2):
|
|
3
|
+
* activation IS roster membership.
|
|
4
|
+
*
|
|
5
|
+
* AW-B URL scheme (DL1–DL5), mirroring `@pgmi-builds/agent-adapter-omp/world`
|
|
6
|
+
* and `@pgmi-builds/agent-adapter-codex/world` so the hub sees every foreign
|
|
7
|
+
* runtime through one shape: the world is spawned as a sibling ROOT context
|
|
8
|
+
* with NO listener (`worldMountPatches`) and mounts itself under `/hermes`
|
|
9
|
+
* through the hub —
|
|
10
|
+
*
|
|
11
|
+
* 1. ctx0 publishes the host mount (real webServer instance) before spawn;
|
|
12
|
+
* 2. the world's entry row provides the virtual `webServer` in the world
|
|
13
|
+
* root, translating every route its plugins declare into `/hermes+path`;
|
|
14
|
+
* 3. the hub's carrier answers `/hermes/api` + `/hermes/api/remote.mux` in
|
|
15
|
+
* ctx0's auth domain and dispatches into this world's gateway instance.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdirSync } from 'node:fs';
|
|
18
|
+
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { spawnWorld, registerAgent, setReady, registerForeignTarget, registerHostMount, mountWorld, worldMountPatches, provisionWorldProfile, } from '../../agent-hub/dist/index.js';
|
|
21
|
+
export const name = 'aw.agent-adapter-hermes';
|
|
22
|
+
/** Runtime key — also the mount label. */
|
|
23
|
+
const KEY = 'hermes';
|
|
24
|
+
const ANCHOR = process.env.SUPERD_DSH_ANCHOR
|
|
25
|
+
?? '/home/u1/.local/lib/node_modules/@deepseek-ai/dsh/package.json';
|
|
26
|
+
export function apply(ctx) {
|
|
27
|
+
registerAgent({ key: KEY, label: 'Hermes', ready: false });
|
|
28
|
+
// Harness resolver (env → ~/.dsh): a consumer install has no DSH_HOME and
|
|
29
|
+
// no cwd contract — deriving the world root from cwd spawns worlds into a
|
|
30
|
+
// garbage tree or throws inside apply() and kills the whole host boot.
|
|
31
|
+
const root = resolveDshHome();
|
|
32
|
+
// S7 nested home: every runtime owns one root under `agents/<label>`; this
|
|
33
|
+
// directory is the WORLD's DSH home (spawnWorld home/dataHome) — DSH-side
|
|
34
|
+
// world data (sessions / settings / workspaces) and the adapter's identity
|
|
35
|
+
// map (dsh-sessions.json) live here. It is NOT hermes data: the hermes
|
|
36
|
+
// runtime keeps its native `~/.hermes`, and the gateway child env must
|
|
37
|
+
// never set HERMES_HOME (Task 3's client's concern — this world spawns no
|
|
38
|
+
// hermes processes itself).
|
|
39
|
+
const worldHome = join(root, 'agents', KEY);
|
|
40
|
+
mkdirSync(join(worldHome, 'profiles'), { recursive: true });
|
|
41
|
+
// Publish story: a foreign install has no line bootstrap, so the nested
|
|
42
|
+
// profile + @pgmi-builds links are provisioned HERE (idempotent; dev lines
|
|
43
|
+
// keep parity — smoke.mjs wrote the same layout, and AW_BARE_BASE still
|
|
44
|
+
// overrides the resolved base).
|
|
45
|
+
const bareBase = provisionWorldProfile({
|
|
46
|
+
key: KEY,
|
|
47
|
+
adapterPkg: '@pgmi-builds/agent-adapter-hermes',
|
|
48
|
+
worldHome,
|
|
49
|
+
});
|
|
50
|
+
const world = (async () => {
|
|
51
|
+
// Wait for ctx0's listening stack: the mount needs the real webServer and
|
|
52
|
+
// the browser-trust fence from the live connection service.
|
|
53
|
+
const host = await new Promise((resolve) => {
|
|
54
|
+
ctx.inject(['webServer', 'connection'], (hostCtx) => { resolve(hostCtx); });
|
|
55
|
+
});
|
|
56
|
+
const real = host['webServer'];
|
|
57
|
+
const connection = host['connection'];
|
|
58
|
+
const dropHostMount = registerHostMount({
|
|
59
|
+
key: KEY,
|
|
60
|
+
labelPath: `/${KEY}`,
|
|
61
|
+
real,
|
|
62
|
+
// ctx0's index gate, applied by the mount to this world's pages and assets:
|
|
63
|
+
// the world is reached in-ctx and owns no listener, so ctx0 decides auth.
|
|
64
|
+
authorize: (req, res) => connection.authorizeIndex(req, res),
|
|
65
|
+
});
|
|
66
|
+
const ctxW = await spawnWorld({
|
|
67
|
+
appName: `aw-${KEY}`,
|
|
68
|
+
profileName: 'web',
|
|
69
|
+
installAnchor: ANCHOR,
|
|
70
|
+
home: worldHome,
|
|
71
|
+
dataHome: worldHome,
|
|
72
|
+
// Adapter-local bundle names resolve from the profile's own
|
|
73
|
+
// node_modules (heal owns @deepseek-ai there; @pgmi-builds links are
|
|
74
|
+
// provisioned by provisionWorldProfile above, or by the line bootstrap
|
|
75
|
+
// when the adapter package was not resolvable — env wins over both).
|
|
76
|
+
bareModuleBaseUrl: process.env.AW_BARE_BASE ?? bareBase ?? undefined,
|
|
77
|
+
extraPatches: worldMountPatches(KEY),
|
|
78
|
+
});
|
|
79
|
+
const gateway = ctxW.get('typertGateway');
|
|
80
|
+
registerForeignTarget({ key: KEY, gateway });
|
|
81
|
+
const mounted = mountWorld({
|
|
82
|
+
real,
|
|
83
|
+
label: KEY,
|
|
84
|
+
gateway,
|
|
85
|
+
requestRejection: (request) => connection
|
|
86
|
+
.requestRejection(request),
|
|
87
|
+
});
|
|
88
|
+
setReady(KEY, true);
|
|
89
|
+
ctx.effect?.(() => () => {
|
|
90
|
+
mounted.dispose();
|
|
91
|
+
dropHostMount();
|
|
92
|
+
}, `aw.agent-adapter-${KEY}: mount`);
|
|
93
|
+
return ctxW;
|
|
94
|
+
})().catch((cause) => {
|
|
95
|
+
// A failed world degrades to a not-ready roster row; a rejected provided
|
|
96
|
+
// promise would fail the entire host boot (loader entry apply).
|
|
97
|
+
console.error(`[super-dsh] ${KEY} world failed:`, cause);
|
|
98
|
+
return null;
|
|
99
|
+
});
|
|
100
|
+
ctx.provide(`aw.world.${KEY}`, world);
|
|
101
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pgmi-builds/agent-adapter-hermes",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Hermes provider for DeepSeek Harness (dsh) — an AgentFactory that embeds Nous Research Hermes Agent via the TUI gateway JSON-RPC (python -m tui_gateway.entry) and bridges its session surface into Dash Agent/Session contracts (Agent Worlds AW-H).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./world": "./dist/world-plugin.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"cordis.patch.yml",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"dsh": {
|
|
16
|
+
"bundle": {
|
|
17
|
+
"patch": "./cordis.patch.yml"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"test": "node --test test/*.test.mjs"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@pgmi-builds/agent-hub": "file:../agent-hub",
|
|
26
|
+
"zod": "3.25.76"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
30
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
31
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.5-rc.2",
|
|
32
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
33
|
+
"@deepseek-ai/dsh-scope": "0.1.5-rc.2",
|
|
34
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
35
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.5-rc.2",
|
|
36
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.2"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@deepseek-ai/cordis": {
|
|
40
|
+
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"@deepseek-ai/dsh-agent": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"@deepseek-ai/dsh-agent-presets": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@deepseek-ai/dsh-llm": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@deepseek-ai/dsh-scope": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"@deepseek-ai/dsh-session": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@deepseek-ai/dsh-session-persistence": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"@deepseek-ai/dsh-typert-protocol": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^26.2.0",
|
|
66
|
+
"typescript": "^5.6.0"
|
|
67
|
+
},
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"main": "dist/index.js",
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=22.18"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Mount the agent-hub host half BESIDE the upstream gateway.
|
|
2
|
+
# The typert-gateway row STAYS ENABLED: disabling it also removed the
|
|
3
|
+
# gateway package's client half from the browser module table (learned
|
|
4
|
+
# 2026-09-10: session-controller's client requires
|
|
5
|
+
# @deepseek-ai/dsh-api-gateway/client). This plugin wraps the live
|
|
6
|
+
# typertGateway service's dispatchRpc instead — selector=native → upstream
|
|
7
|
+
# dispatch; selector=foreign → wire-forward to that context's own /api.
|
|
8
|
+
# The /api/agent-runtime selector RPC face rides along (control plane,
|
|
9
|
+
# always CTX0-local).
|
|
10
|
+
- insert:
|
|
11
|
+
- id: agent-hub
|
|
12
|
+
name: '@pgmi-builds/agent-hub'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hub's own agent roster row (AW-B, URL scheme).
|
|
3
|
+
*
|
|
4
|
+
* The selector is pure navigation: every runtime owns a mount path, so the UI
|
|
5
|
+
* needs exactly one fact — which paths exist — and nothing else. The hub bakes
|
|
6
|
+
* that fact into every page it can reach (ctx0's index and every mounted
|
|
7
|
+
* world's index), so no world implements any selector code and no server-side
|
|
8
|
+
* selection value exists anywhere.
|
|
9
|
+
*
|
|
10
|
+
* / → native DSH
|
|
11
|
+
* /omp/ → the OMP world's mount
|
|
12
|
+
* /codex/ → the Codex world's mount
|
|
13
|
+
*/
|
|
14
|
+
import type { IndexInjection } from '@deepseek-ai/dsh-host-webserver';
|
|
15
|
+
/** One selectable runtime: its key, its label, and the path that addresses it. */
|
|
16
|
+
export interface AgentLink {
|
|
17
|
+
/** `native` for ctx0, otherwise the adapter's runtime key. */
|
|
18
|
+
key: string;
|
|
19
|
+
/** Chip/menu label (`DSH`, `OMP`, `Codex`, …). */
|
|
20
|
+
label: string;
|
|
21
|
+
/** Mount root, always trailing-slashed except the native `/`. */
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
/** The global this module publishes the roster under. */
|
|
25
|
+
export declare const AGENT_ROSTER_GLOBAL = "__DSH_AGENT_ROSTER__";
|
|
26
|
+
/**
|
|
27
|
+
* Every runtime that currently owns a mount, native first.
|
|
28
|
+
*
|
|
29
|
+
* A registered agent without a host mount has no addressable path yet (its
|
|
30
|
+
* world is still spawning) and is therefore not selectable.
|
|
31
|
+
*/
|
|
32
|
+
export declare function agentLinks(): AgentLink[];
|
|
33
|
+
/** The structured index row that publishes {@link agentLinks} to the page. */
|
|
34
|
+
export declare function agentRosterRow(): IndexInjection;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { listAgents } from './roster.js';
|
|
2
|
+
import { takeHostMount } from './world-host.js';
|
|
3
|
+
/** The native runtime's synthetic entry (no adapter plugin backs it). */
|
|
4
|
+
const NATIVE = { key: 'native', label: 'DSH', path: '/' };
|
|
5
|
+
/** The global this module publishes the roster under. */
|
|
6
|
+
export const AGENT_ROSTER_GLOBAL = '__DSH_AGENT_ROSTER__';
|
|
7
|
+
/**
|
|
8
|
+
* Every runtime that currently owns a mount, native first.
|
|
9
|
+
*
|
|
10
|
+
* A registered agent without a host mount has no addressable path yet (its
|
|
11
|
+
* world is still spawning) and is therefore not selectable.
|
|
12
|
+
*/
|
|
13
|
+
export function agentLinks() {
|
|
14
|
+
const links = [NATIVE];
|
|
15
|
+
for (const agent of listAgents()) {
|
|
16
|
+
const mount = takeHostMount(agent.key);
|
|
17
|
+
if (mount === undefined)
|
|
18
|
+
continue;
|
|
19
|
+
const path = mount.labelPath.endsWith('/') ? mount.labelPath : `${mount.labelPath}/`;
|
|
20
|
+
links.push({ key: agent.key, label: agent.label, path });
|
|
21
|
+
}
|
|
22
|
+
return links;
|
|
23
|
+
}
|
|
24
|
+
/** The structured index row that publishes {@link agentLinks} to the page. */
|
|
25
|
+
export function agentRosterRow() {
|
|
26
|
+
return { kind: 'global', name: AGENT_ROSTER_GLOBAL, value: { agents: agentLinks() } };
|
|
27
|
+
}
|