super-dsh 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -2
- package/agent-agy/dist/agent.js +19 -1
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +177 -22
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +18 -6
- package/agent-codex/dist/adapter.js +3 -2
- package/agent-codex/dist/agent.js +41 -7
- package/agent-codex/dist/app-server-client.js +7 -1
- package/agent-codex/dist/app-server-wire.js +10 -22
- package/agent-codex/dist/codex-launch.js +10 -32
- package/agent-codex/dist/codex-native-config.js +9 -3
- package/agent-codex/dist/codex-oauth.js +153 -0
- package/agent-codex/dist/codex-provider-gateway.js +36 -38
- package/agent-codex/dist/codex-providers.js +20 -7
- package/agent-codex/dist/codex-runtime.js +120 -0
- package/agent-codex/dist/index.js +18 -11
- package/agent-codex/package.json +1 -1
- package/agent-codex-sdk/dist/adapter.js +3 -2
- package/agent-codex-sdk/dist/agent.js +35 -1
- package/agent-codex-sdk/dist/codex-native-config.js +9 -3
- package/agent-codex-sdk/dist/codex-oauth.js +153 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +36 -38
- package/agent-codex-sdk/dist/codex-providers.js +20 -7
- package/agent-codex-sdk/dist/codex-sdk-launch.js +5 -38
- package/agent-codex-sdk/dist/index.js +18 -11
- package/agent-codex-sdk/dist/sdk-runtime.js +2 -2
- package/agent-hermes/dist/agent.js +46 -2
- package/agent-hermes/dist/hermes-client.js +49 -1
- package/agent-omp/dist/agent.js +17 -4
- package/agent-omp-sdk/dist/agent.d.ts +11 -0
- package/agent-omp-sdk/dist/agent.js +17 -4
- package/agent-pi/dist/agent.js +33 -1
- package/client/index.js +150 -58
- package/package.json +2 -3
- package/superd-hub/dist/client/agent-settings.d.ts +66 -12
- package/superd-hub/dist/client/agent-settings.js +85 -14
- package/superd-hub/dist/client/runtime-settings-api.js +5 -5
- package/superd-hub/dist/world-mount.d.ts +6 -5
- package/superd-hub/dist/world-mount.js +6 -6
- package/superd-hub/lib/client/index.js +150 -58
|
@@ -25,15 +25,21 @@ import { Service } from "@deepseek-ai/cordis";
|
|
|
25
25
|
import { SessionLogOffset, SessionPreparation, interruptedTurnClosers, } from "@deepseek-ai/dsh-session";
|
|
26
26
|
import { ClaudeSdkClient, probeClaudeModels } from "./claude-client.js";
|
|
27
27
|
import { ClaudeAgent } from "./agent.js";
|
|
28
|
-
import { ClaudeLlmAdapter, CLAUDE_PROVIDER_ID } from "./adapter.js";
|
|
28
|
+
import { ClaudeLlmAdapter, CLAUDE_PROVIDER_ID, routeIds } from "./adapter.js";
|
|
29
29
|
import { resolveClaudeHome } from "./claude-home.js";
|
|
30
30
|
import { resetProjectionState } from "./claude-events.js";
|
|
31
31
|
import { claudeSessionIdFromDsh } from "./session-id.js";
|
|
32
32
|
import { claudePermissionMode, defaultPermissionPreset, modeChangePatch, presetFromEvents, } from "./permission.js";
|
|
33
33
|
import { sessionRecord, upsertSession } from "./session-map.js";
|
|
34
34
|
import { makeCanUseTool, answerAskUserQuestion, askPlanReview, } from "./interaction.js";
|
|
35
|
-
import { modelEntryFromSdk,
|
|
35
|
+
import { modelEntryFromSdk, setModelCatalog, CLAUDE_DEFAULT_MODEL } from "./models.js";
|
|
36
36
|
import { registerClaudePreset } from "./agent-preset-claude.js";
|
|
37
|
+
import { claudeLaunchFor } from "./claude-launch.js";
|
|
38
|
+
import { routeSeed } from "./claude-route.js";
|
|
39
|
+
import { endpointKey, readProviderStore } from "./claude-providers.js";
|
|
40
|
+
import { ClaudeProvidersGateway, syncFromNative } from "./claude-provider-gateway.js";
|
|
41
|
+
import { readNativeModel, readNativeProviders } from "./claude-native-config.js";
|
|
42
|
+
import { resolveClaudeBinary } from "./sdk-runtime.js";
|
|
37
43
|
const CLAUDE_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "auto", "dontAsk"];
|
|
38
44
|
/** Narrow an untrusted value to a persisted Claude permission mode. */
|
|
39
45
|
function isClaudeMode(raw) {
|
|
@@ -56,6 +62,14 @@ const trace = (...parts) => {
|
|
|
56
62
|
if (process.env.CLAUDE_TRACE === "1")
|
|
57
63
|
process.stderr.write(`[claude-provider ${Date.now() % 1_000_000}] ${parts.join(" ")}\n`);
|
|
58
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* How often the canonical store is reconciled with the user's `settings.json`
|
|
67
|
+
* env (2026-10-04 user ruling: startup + every ten minutes). The file is the
|
|
68
|
+
* user's own and edited with their own tools, so the sync is a poll rather
|
|
69
|
+
* than an event subscription — ten minutes is the granularity the ruling
|
|
70
|
+
* asked for.
|
|
71
|
+
*/
|
|
72
|
+
const SYNC_INTERVAL_MS = 600_000;
|
|
59
73
|
/**
|
|
60
74
|
* Resume identity guard (exported for unit testing): fail closed when no Claude
|
|
61
75
|
* session is recorded for the DSH session id. Route B (a mapping file) is V1+
|
|
@@ -118,6 +132,11 @@ export class ClaudeProvider extends Service {
|
|
|
118
132
|
home;
|
|
119
133
|
/** Echo/staleness guard for the app-wide default-model push. */
|
|
120
134
|
defaultModelKey = "";
|
|
135
|
+
/** Epoch ms of the last reconcile with the user's `settings.json` env. */
|
|
136
|
+
syncedAt;
|
|
137
|
+
/** The live `ctx.llm` registration for the claude routes, when one exists.
|
|
138
|
+
* Its `replace()` is how the route SET follows the store (codex twin). */
|
|
139
|
+
routes;
|
|
121
140
|
constructor(ctx) {
|
|
122
141
|
super(ctx, "claudeProvider");
|
|
123
142
|
this.runtime = { ctx };
|
|
@@ -127,6 +146,17 @@ export class ClaudeProvider extends Service {
|
|
|
127
146
|
trace(`home: worldHome=${this.home} claudeHome=${resolveClaudeHome()}`);
|
|
128
147
|
ctx.effect(() => ctx.agents.setFactory(this), "claudeProvider.setFactory()");
|
|
129
148
|
this.#registerModelCatalog();
|
|
149
|
+
// The store is canonical and the native settings.json env is a source
|
|
150
|
+
// (2026-10-04). Reconcile once at startup, then keep reconciling on a
|
|
151
|
+
// timer; the sync is idempotent and write-only-when-moved, so a world
|
|
152
|
+
// with no providers configured pays nothing but a no-op read.
|
|
153
|
+
this.syncNow();
|
|
154
|
+
const timer = setInterval(() => this.syncNow(), SYNC_INTERVAL_MS);
|
|
155
|
+
timer.unref?.();
|
|
156
|
+
// Fiber-scoped cleanup (super-dsh/AGENTS unload contract): the timer must
|
|
157
|
+
// never outlive this row — HMR row unload disposes the fiber, which runs
|
|
158
|
+
// this effect's disposer.
|
|
159
|
+
ctx.effect(() => () => clearInterval(timer), "claudeProvider.syncTimer()");
|
|
130
160
|
// The roster must be visible to the API gateway's root-level remote
|
|
131
161
|
// enumeration; nesting it under a child fiber via ctx.plugin hides its
|
|
132
162
|
// @Remote routes from the gateway and every agentPresets/* call 404s (the
|
|
@@ -140,6 +170,50 @@ export class ClaudeProvider extends Service {
|
|
|
140
170
|
registerClaudePreset(ctx);
|
|
141
171
|
this.#registerDefaultModel();
|
|
142
172
|
this.#probeBootModels();
|
|
173
|
+
// The Hub settings panel's data plane (2026-10-04 multi-provider ruling):
|
|
174
|
+
// an adapter-owned Typert Remote (`claudeProviders/get|save|remove`) on
|
|
175
|
+
// this world's context. The hub's carrier forwards `envelope.method`
|
|
176
|
+
// verbatim, so the panel reaches it through the existing
|
|
177
|
+
// `/hub/api?agent=claude` family — no new hub route, and no provider
|
|
178
|
+
// knowledge leaks out of the adapter (dev-rules §12). Cordis keeps the
|
|
179
|
+
// instance alive as this context's `claudeProviders` service. There is no
|
|
180
|
+
// `select`: which provider a session runs on is DSH's per-session
|
|
181
|
+
// `model/selection`.
|
|
182
|
+
//
|
|
183
|
+
// `resolveClaudeHome` is passed as a THUNK on purpose (the codex twin's
|
|
184
|
+
// rule): the native home is re-resolved per call, so a test's
|
|
185
|
+
// `CLAUDE_CONFIG_DIR` and an operator's override are both honored.
|
|
186
|
+
//
|
|
187
|
+
// A panel save/remove re-points the route SET (one route per provider, plus
|
|
188
|
+
// the fixed `claude` route) through routes.replace — the codex twin's
|
|
189
|
+
// mechanism, wired exactly.
|
|
190
|
+
new ClaudeProvidersGateway(ctx, this.home, () => resolveClaudeHome(), () => this.refreshRoutes());
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Reconcile the canonical provider store with the user's `settings.json`
|
|
194
|
+
* env (2026-10-04 user ruling) and stamp the last-reconcile epoch.
|
|
195
|
+
*
|
|
196
|
+
* Called once at startup and every {@link SYNC_INTERVAL_MS}. Mirrors the
|
|
197
|
+
* codex twin's semantics exactly — idempotent, silent, and
|
|
198
|
+
* write-only-when-moved (claude-providers.ts guarantees the store rewrite
|
|
199
|
+
* happens only when something actually changed) — minus codex's
|
|
200
|
+
* `#applyRoutes` step: claude serves every provider under the ONE fixed
|
|
201
|
+
* `claude` route registered at boot, so there is no route set to re-point.
|
|
202
|
+
* Never throws: a provider list is not worth a boot.
|
|
203
|
+
*/
|
|
204
|
+
syncNow() {
|
|
205
|
+
try {
|
|
206
|
+
const { store, added, updated } = syncFromNative(this.home, resolveClaudeHome());
|
|
207
|
+
trace(`provider sync: added=${added.join(",") || "none"} updated=${updated.join(",") || "none"}`);
|
|
208
|
+
if (added.length > 0 || updated.length > 0) {
|
|
209
|
+
this.runtime.ctx.logger.info(`claude-provider: settings.json sync — added ${added.join(", ") || "none"}, refreshed ${updated.join(", ") || "none"}`);
|
|
210
|
+
}
|
|
211
|
+
this.syncedAt = Date.now();
|
|
212
|
+
this.#applyRoutes(store);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
this.runtime.ctx.logger.warn(`claude-provider: provider sync failed: ${String(error)}`);
|
|
216
|
+
}
|
|
143
217
|
}
|
|
144
218
|
/**
|
|
145
219
|
* RC-3 boot model probe: ONE ephemeral SDK query asks the CLI's
|
|
@@ -150,7 +224,7 @@ export class ClaudeProvider extends Service {
|
|
|
150
224
|
* probe-failure fallback, and a live session re-observes anyway.
|
|
151
225
|
*/
|
|
152
226
|
#probeBootModels() {
|
|
153
|
-
void probeClaudeModels()
|
|
227
|
+
void probeClaudeModels({ worldHome: this.home })
|
|
154
228
|
.then((models) => {
|
|
155
229
|
const entries = models.flatMap((model) => modelEntryFromSdk(model));
|
|
156
230
|
if (entries.length === 0) {
|
|
@@ -222,21 +296,22 @@ export class ClaudeProvider extends Service {
|
|
|
222
296
|
// The host-side permission gate is injected now, closing over the injected
|
|
223
297
|
// approval/questions seams plus a lazy agent holder (set in setupAndPublish).
|
|
224
298
|
const agentRef = { current: undefined };
|
|
225
|
-
const
|
|
299
|
+
const canUseTool = makeClaudeCanUseTool({
|
|
300
|
+
approval: loopCtx.get("approval"),
|
|
301
|
+
questions: loopCtx.get("userQuestions"),
|
|
302
|
+
getAgent: () => agentRef.current,
|
|
303
|
+
});
|
|
304
|
+
// One factory per session: the shared wiring (canUseTool, worldHome, cwd,
|
|
305
|
+
// permission posture) plus per-instance overrides. The agent swaps the whole
|
|
306
|
+
// client through it when a provider move needs a different child.
|
|
307
|
+
const clientFactory = (overrides = {}) => new ClaudeSdkClient({
|
|
226
308
|
cwd: spawnCwd,
|
|
227
|
-
|
|
228
|
-
canUseTool
|
|
229
|
-
approval: loopCtx.get("approval"),
|
|
230
|
-
questions: loopCtx.get("userQuestions"),
|
|
231
|
-
getAgent: () => agentRef.current,
|
|
232
|
-
}),
|
|
309
|
+
worldHome: this.home,
|
|
310
|
+
canUseTool,
|
|
233
311
|
allowDangerouslySkipPermissions: claudeMode === "bypassPermissions",
|
|
312
|
+
...overrides,
|
|
234
313
|
});
|
|
235
|
-
|
|
236
|
-
// create-time `setModel`); an explicit caller selection is honored by
|
|
237
|
-
// `sessionAgentOptions` at publication.
|
|
238
|
-
const createCatalog = readModelCatalog();
|
|
239
|
-
void client.setModel(createCatalog.defaultModel ?? CLAUDE_DEFAULT_MODEL).catch(() => { });
|
|
314
|
+
const client = clientFactory({ claudeSessionId });
|
|
240
315
|
const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
|
|
241
316
|
...(options.seed === undefined ? {} : { seed: options.seed }),
|
|
242
317
|
...(meta === undefined ? {} : { meta }),
|
|
@@ -271,7 +346,9 @@ export class ClaudeProvider extends Service {
|
|
|
271
346
|
upsertSession(this.home, String(id), { resumable: false });
|
|
272
347
|
trace(`marked ${id} not resumable (conversation never materialized)`);
|
|
273
348
|
},
|
|
274
|
-
|
|
349
|
+
launchFor: (providerId, model) => this.agentLaunchFor(providerId, model),
|
|
350
|
+
contextWindowFor: (providerId, model) => this.agentContextWindowFor(providerId, model),
|
|
351
|
+
}, agentRef, clientFactory);
|
|
275
352
|
}
|
|
276
353
|
catch (error) {
|
|
277
354
|
trace(`create ${id} THREW: ${String(error)}`);
|
|
@@ -333,16 +410,19 @@ export class ClaudeProvider extends Service {
|
|
|
333
410
|
// first prompt). Re-apply the persisted permission mode up front (buffered
|
|
334
411
|
// until the query exists), and re-inject the host-side permission gate.
|
|
335
412
|
const agentRef = { current: undefined };
|
|
336
|
-
const
|
|
413
|
+
const canUseTool = makeClaudeCanUseTool({
|
|
414
|
+
approval: loopCtx.get("approval"),
|
|
415
|
+
questions: loopCtx.get("userQuestions"),
|
|
416
|
+
getAgent: () => agentRef.current,
|
|
417
|
+
});
|
|
418
|
+
const clientFactory = (overrides = {}) => new ClaudeSdkClient({
|
|
337
419
|
cwd: spawnCwd,
|
|
338
|
-
|
|
339
|
-
canUseTool
|
|
340
|
-
approval: loopCtx.get("approval"),
|
|
341
|
-
questions: loopCtx.get("userQuestions"),
|
|
342
|
-
getAgent: () => agentRef.current,
|
|
343
|
-
}),
|
|
420
|
+
worldHome: this.home,
|
|
421
|
+
canUseTool,
|
|
344
422
|
allowDangerouslySkipPermissions: claudeMode === "bypassPermissions",
|
|
423
|
+
...overrides,
|
|
345
424
|
});
|
|
425
|
+
const client = clientFactory({ resumeSessionId: claudeSessionId });
|
|
346
426
|
void client.setPermissionMode(claudeMode).catch(() => { });
|
|
347
427
|
let handle;
|
|
348
428
|
let stored;
|
|
@@ -397,7 +477,9 @@ export class ClaudeProvider extends Service {
|
|
|
397
477
|
upsertSession(this.home, String(id), { resumable: false });
|
|
398
478
|
trace(`marked ${id} not resumable (conversation never materialized)`);
|
|
399
479
|
},
|
|
400
|
-
|
|
480
|
+
launchFor: (providerId, model) => this.agentLaunchFor(providerId, model),
|
|
481
|
+
contextWindowFor: (providerId, model) => this.agentContextWindowFor(providerId, model),
|
|
482
|
+
}, agentRef, clientFactory);
|
|
401
483
|
}
|
|
402
484
|
catch (error) {
|
|
403
485
|
trace(`resume ${id} THREW: ${String(error)} | stack=${error instanceof Error ? error.stack?.slice(0, 400) : "n/a"}`);
|
|
@@ -412,25 +494,104 @@ export class ClaudeProvider extends Service {
|
|
|
412
494
|
}
|
|
413
495
|
}
|
|
414
496
|
#registerModelCatalog() {
|
|
497
|
+
this.#applyRoutes(readProviderStore(this.home));
|
|
498
|
+
}
|
|
499
|
+
/** Re-register the `ctx.llm` routes from the current store (panel mutation path). */
|
|
500
|
+
refreshRoutes() {
|
|
501
|
+
try {
|
|
502
|
+
this.#applyRoutes(readProviderStore(this.home));
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
this.runtime.ctx.logger.warn(`claude-provider: route refresh failed: ${String(error)}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/** Register or atomically re-point the claude routes at the given store
|
|
509
|
+
* (the fixed `claude` route + one route per provider with models). */
|
|
510
|
+
#applyRoutes(store) {
|
|
415
511
|
const llm = this.runtime.ctx.get("llm");
|
|
416
512
|
if (llm === undefined)
|
|
417
513
|
return;
|
|
418
|
-
|
|
514
|
+
const ids = routeIds(store);
|
|
515
|
+
if (this.routes === undefined) {
|
|
516
|
+
this.routes = llm.registerAdapter([...ids], new ClaudeLlmAdapter(this.home));
|
|
517
|
+
trace(`routes registered: ${ids.join(", ")}`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
// `replace` validates the whole candidate set before swapping; a throw here
|
|
521
|
+
// must not take the caller (a timer, or a panel save) down with it.
|
|
522
|
+
this.routes.replace([...ids]);
|
|
523
|
+
trace(`routes replaced: ${ids.join(", ") || "none"}`);
|
|
419
524
|
}
|
|
420
525
|
/**
|
|
421
526
|
* The factory owns its sessions' route (user directive 2026-09-15): the
|
|
422
527
|
* ambient `agentDefaultModel` selection is NOT authoritative here — a session
|
|
423
|
-
* created by this factory
|
|
424
|
-
*
|
|
425
|
-
*
|
|
528
|
+
* created by this factory starts from the reconciled pair below unless the
|
|
529
|
+
* caller asked for a route this world serves (the native `claude` route or a
|
|
530
|
+
* curated provider route).
|
|
426
531
|
*/
|
|
427
532
|
sessionAgentOptions(requested) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
533
|
+
// 1. A caller-requested route wins when it names a pair this world serves:
|
|
534
|
+
// the fixed native `claude` route (any raw id — launch native, inject
|
|
535
|
+
// nothing) or a curated provider route whose models include the id.
|
|
536
|
+
const wanted = requested?.provider !== undefined && typeof requested.model === "string" && requested.model !== ""
|
|
537
|
+
? requested
|
|
431
538
|
: undefined;
|
|
432
|
-
const
|
|
433
|
-
|
|
539
|
+
const served = wanted !== undefined && (wanted.provider === CLAUDE_PROVIDER_ID
|
|
540
|
+
|| this.providerEntry(wanted.provider)?.models.some((model) => model.id === wanted.model) === true);
|
|
541
|
+
if (wanted !== undefined && served)
|
|
542
|
+
return { ...wanted, provider: wanted.provider, model: wanted.model };
|
|
543
|
+
// 2. Native fold model (the user's own settings.json env).
|
|
544
|
+
const nativeProvider = this.nativeProviderId();
|
|
545
|
+
const nativeModel = readNativeModel(resolveClaudeHome());
|
|
546
|
+
if (nativeProvider !== undefined && nativeModel !== undefined && nativeModel !== "") {
|
|
547
|
+
return { ...(requested ?? {}), provider: nativeProvider, model: nativeModel };
|
|
548
|
+
}
|
|
549
|
+
// 3. First provider's first model.
|
|
550
|
+
for (const entry of readProviderStore(this.home).providers) {
|
|
551
|
+
const model = entry.models[0]?.id;
|
|
552
|
+
if (model !== undefined)
|
|
553
|
+
return { ...(requested ?? {}), provider: entry.id, model };
|
|
554
|
+
}
|
|
555
|
+
// 4. Legacy boot default (claude/sonnet → NATIVE_LAUNCH).
|
|
556
|
+
return { ...(requested ?? {}), provider: CLAUDE_PROVIDER_ID, model: CLAUDE_DEFAULT_MODEL };
|
|
557
|
+
}
|
|
558
|
+
/** The store entry a provider id names, or `undefined`. */
|
|
559
|
+
providerEntry(providerId) {
|
|
560
|
+
return readProviderStore(this.home).providers.find((entry) => entry.id === providerId);
|
|
561
|
+
}
|
|
562
|
+
/** The provider id the user's own settings.json env folds to, or `undefined`. */
|
|
563
|
+
nativeProviderId() {
|
|
564
|
+
const table = readNativeProviders(resolveClaudeHome())[0];
|
|
565
|
+
if (table?.baseUrl === undefined)
|
|
566
|
+
return undefined;
|
|
567
|
+
const key = endpointKey(table.baseUrl);
|
|
568
|
+
return readProviderStore(this.home).providers.find((entry) => endpointKey(entry.baseUrl) === key)?.id;
|
|
569
|
+
}
|
|
570
|
+
/** The launch that pins one provider for a freshly spawned client. */
|
|
571
|
+
agentLaunchFor(providerId, model) {
|
|
572
|
+
const entry = this.providerEntry(providerId);
|
|
573
|
+
if (entry === undefined)
|
|
574
|
+
return undefined;
|
|
575
|
+
return claudeLaunchFor({ provider: providerId, model }, entry, resolveClaudeBinary());
|
|
576
|
+
}
|
|
577
|
+
/** The provider's own context window for one of its models, when known. */
|
|
578
|
+
agentContextWindowFor(providerId, model) {
|
|
579
|
+
const entry = this.providerEntry(providerId);
|
|
580
|
+
return entry?.models.find((candidate) => candidate.id === model)?.contextWindow;
|
|
581
|
+
}
|
|
582
|
+
/** The app-wide default selection (DSH-side `claude/<composite-or-boot>`). */
|
|
583
|
+
defaultModelSelection() {
|
|
584
|
+
const nativeProvider = this.nativeProviderId();
|
|
585
|
+
const nativeModel = readNativeModel(resolveClaudeHome());
|
|
586
|
+
if (nativeProvider !== undefined && nativeModel !== undefined && nativeModel !== "") {
|
|
587
|
+
return { provider: nativeProvider, model: nativeModel };
|
|
588
|
+
}
|
|
589
|
+
for (const entry of readProviderStore(this.home).providers) {
|
|
590
|
+
const model = entry.models[0]?.id;
|
|
591
|
+
if (model !== undefined)
|
|
592
|
+
return { provider: entry.id, model };
|
|
593
|
+
}
|
|
594
|
+
return { provider: CLAUDE_PROVIDER_ID, model: CLAUDE_DEFAULT_MODEL };
|
|
434
595
|
}
|
|
435
596
|
/**
|
|
436
597
|
* App-wide default-model push (mirrors `agent-codex` `#registerDefaultModel`
|
|
@@ -447,8 +608,7 @@ export class ClaudeProvider extends Service {
|
|
|
447
608
|
const service = mCtx.get("agentDefaultModel");
|
|
448
609
|
if (service === undefined)
|
|
449
610
|
return;
|
|
450
|
-
const
|
|
451
|
-
const target = { provider: CLAUDE_PROVIDER_ID, model: catalog.defaultModel ?? CLAUDE_DEFAULT_MODEL };
|
|
611
|
+
const target = this.defaultModelSelection();
|
|
452
612
|
const targetKey = `${target.provider}/${target.model}`;
|
|
453
613
|
const current = service.currentSelection?.();
|
|
454
614
|
const currentKey = current === undefined ? undefined : `${current.provider}/${current.model}`;
|
|
@@ -487,7 +647,7 @@ async function appendUnstoredSuffix(stored, session) {
|
|
|
487
647
|
* a module-level function (not a private method) because Cordis exposes the
|
|
488
648
|
* provider through a tracing proxy, which breaks hard-private receivers.
|
|
489
649
|
*/
|
|
490
|
-
async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, session, client, parentAgent, source, opts, stored, runtimeInfo, agentRef) {
|
|
650
|
+
async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, session, client, parentAgent, source, opts, stored, runtimeInfo, agentRef, clientFactory) {
|
|
491
651
|
let detachSession;
|
|
492
652
|
let detachAgent;
|
|
493
653
|
let agent;
|
|
@@ -497,8 +657,25 @@ async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, sessi
|
|
|
497
657
|
// module-level reset is belt-and-suspenders for a re-entry before the
|
|
498
658
|
// agent exists (e.g. a throwing setup that re-publishes).
|
|
499
659
|
resetProjectionState();
|
|
500
|
-
agent = new ClaudeAgent(loopCtx, id, agentOptions, session, client, () => idleExit?.(), runtimeInfo);
|
|
660
|
+
agent = new ClaudeAgent(loopCtx, id, agentOptions, session, client, () => idleExit?.(), runtimeInfo, clientFactory);
|
|
501
661
|
agentRef.current = agent;
|
|
662
|
+
// Seed THIS session's route only when its log has none (mirrors codex): the
|
|
663
|
+
// harness's `model/selection` is the durable per-session route, so an
|
|
664
|
+
// existing one is the authority and only a session that never named a route
|
|
665
|
+
// gets the boot default. Log-only — the agent resolves it each turn.
|
|
666
|
+
try {
|
|
667
|
+
const events = session.snapshotEvents();
|
|
668
|
+
const alreadyRouted = events.some((event) => event.type === "model/selection");
|
|
669
|
+
const seed = routeSeed(agentOptions);
|
|
670
|
+
if (!alreadyRouted && seed !== undefined) {
|
|
671
|
+
const selection = { provider: seed.provider, model: seed.model };
|
|
672
|
+
session.append("model/selection", selection);
|
|
673
|
+
trace(`session route seeded: ${selection.provider}/${selection.model}`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
trace(`session route seeding failed: ${String(error)}`);
|
|
678
|
+
}
|
|
502
679
|
// Composition-only setup on the unpublished agent scope.
|
|
503
680
|
const commit = await setup?.(agent.ctx, agent);
|
|
504
681
|
// Publish: flush the unstored suffix, enter + announce in order.
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* field: exactly one Claude app per world observes it, and the settings-UI
|
|
31
31
|
* round trip constructs its own adapter instance.
|
|
32
32
|
*/
|
|
33
|
+
import { compositeModelId } from "./claude-route.js";
|
|
33
34
|
/** The empty catalog after a legitimate empty observation (`setModelCatalog([])`). */
|
|
34
35
|
const EMPTY_CATALOG = Object.freeze({ models: Object.freeze([]) });
|
|
35
36
|
/**
|
|
@@ -97,6 +98,19 @@ export function readModelCatalog() {
|
|
|
97
98
|
...(catalog.defaultModel === undefined ? {} : { defaultModel: catalog.defaultModel }),
|
|
98
99
|
});
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Mint one observed entry's catalog id as `${provider}/${id}` (the composite id
|
|
103
|
+
* the selector picks). Only the LIVE session's authoritative `supportedModels()`
|
|
104
|
+
* roster goes through this — the degraded single-entry catalog and the boot
|
|
105
|
+
* probe stay raw (observed-default stays raw-only-in-memory).
|
|
106
|
+
*
|
|
107
|
+
* @param provider - the CURRENT turn's provider (`#turnRoute?.provider ?? "native"`).
|
|
108
|
+
* @param entries - observed entries whose ids are RAW wire ids.
|
|
109
|
+
* @returns the entries with composite ids.
|
|
110
|
+
*/
|
|
111
|
+
export function mintCatalogEntries(provider, entries) {
|
|
112
|
+
return entries.map((entry) => ({ ...entry, id: compositeModelId(provider, entry.id) }));
|
|
113
|
+
}
|
|
100
114
|
/**
|
|
101
115
|
* Derive the **degraded** single-entry catalog from a projected `session_init`
|
|
102
116
|
* (Task 4) while `supportedModels()` has not answered yet.
|