@moxxy/cli 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -194,6 +194,14 @@ var init_log = __esm({
194
194
  listeners = /* @__PURE__ */ new Set();
195
195
  clearListeners = /* @__PURE__ */ new Set();
196
196
  now;
197
+ /**
198
+ * Seq of the FIRST event this log holds. 0 for an authoring log; a mirror
199
+ * primed by a partial attach replay (runner protocol v6 `replay.start`)
200
+ * rebases to the first replayed seq so `ingest`'s contiguity gate lines up
201
+ * with the runner's stream instead of expecting history we never received.
202
+ * `seq === base + index` for every held event.
203
+ */
204
+ base = 0;
197
205
  constructor(seed = [], opts = {}) {
198
206
  this.now = opts.now ?? Date.now;
199
207
  for (const e3 of seed)
@@ -202,13 +210,18 @@ var init_log = __esm({
202
210
  get length() {
203
211
  return this.events.length;
204
212
  }
213
+ /** Seq of the first held event (see {@link rebase}). */
214
+ get baseSeq() {
215
+ return this.base;
216
+ }
205
217
  at(seq) {
206
- if (seq < 0 || seq >= this.events.length)
218
+ const index = seq - this.base;
219
+ if (index < 0 || index >= this.events.length)
207
220
  return void 0;
208
- return this.events[seq];
221
+ return this.events[index];
209
222
  }
210
- slice(from = 0, to = this.events.length) {
211
- return this.events.slice(from, to);
223
+ slice(from = 0, to = this.base + this.events.length) {
224
+ return this.events.slice(Math.max(0, from - this.base), Math.max(0, to - this.base));
212
225
  }
213
226
  ofType(type) {
214
227
  return this.events.filter((e3) => e3.type === type);
@@ -220,7 +233,7 @@ var init_log = __esm({
220
233
  return [...this.events];
221
234
  }
222
235
  async append(partial) {
223
- const event = materializeEvent(partial, this.events.length, this.now);
236
+ const event = materializeEvent(partial, this.base + this.events.length, this.now);
224
237
  this.events.push(event);
225
238
  const snapshot = [...this.listeners];
226
239
  for (const fn of snapshot) {
@@ -245,7 +258,7 @@ var init_log = __esm({
245
258
  * Not for normal authoring; use {@link append} for that.
246
259
  */
247
260
  ingest(event) {
248
- if (event.seq !== this.events.length)
261
+ if (event.seq !== this.base + this.events.length)
249
262
  return;
250
263
  this.events.push(event);
251
264
  const snapshot = [...this.listeners];
@@ -271,8 +284,25 @@ var init_log = __esm({
271
284
  * Safe to call only when no turn is in flight — callers should abort
272
285
  * their AbortController and await any pending runTurn() first.
273
286
  */
287
+ /**
288
+ * Start this (empty) log at `seq` instead of 0. A mirror primed by a
289
+ * PARTIAL attach replay (`replay: 'none'` / `{ tail }`) calls this with the
290
+ * runner's announced first seq so {@link ingest}'s contiguity gate accepts
291
+ * the stream. Only valid while empty — rebasing held events would detach
292
+ * their seqs from their indices.
293
+ */
294
+ rebase(seq) {
295
+ if (this.events.length > 0) {
296
+ throw new Error(`EventLog.rebase(${seq}): log already holds ${this.events.length} events`);
297
+ }
298
+ if (!Number.isInteger(seq) || seq < 0) {
299
+ throw new Error(`EventLog.rebase(${seq}): seq must be a non-negative integer`);
300
+ }
301
+ this.base = seq;
302
+ }
274
303
  clear() {
275
304
  this.events.length = 0;
305
+ this.base = 0;
276
306
  const snapshot = [...this.clearListeners];
277
307
  for (const fn of snapshot) {
278
308
  try {
@@ -466,9 +496,10 @@ async function runChildTurn(args) {
466
496
  return resolved.failure;
467
497
  const { strategy, strategyName } = resolved;
468
498
  const toolRegistry = spec.allowedTools && spec.allowedTools.length > 0 ? buildFilteredToolRegistry(parentSession.tools, new Set(spec.allowedTools)) : parentSession.tools;
499
+ const childModel = await resolveChildModel(rt3, spec, label3, childSessionId);
469
500
  const childLog = new EventLog();
470
- const spawner = createSubagentSpawner(rt3);
471
- const childCtx = buildChildContext(rt3, spec, childSessionId, childTurnId, toolRegistry, childLog, spawner);
501
+ const spawner = createSubagentSpawner({ ...rt3, parentModel: childModel });
502
+ const childCtx = buildChildContext(rt3, spec, childModel, childSessionId, childTurnId, toolRegistry, childLog, spawner);
472
503
  const capture = await executeChildLoop({
473
504
  rt: rt3,
474
505
  spec,
@@ -600,12 +631,24 @@ async function resolveStrategy(parentSession, parentTurnId, label3, childSession
600
631
  }
601
632
  };
602
633
  }
603
- function buildChildContext(rt3, spec, childSessionId, childTurnId, toolRegistry, childLog, spawner) {
604
- const { parentSession, parentSignal, parentModel } = rt3;
634
+ async function resolveChildModel(rt3, spec, label3, childSessionId) {
635
+ const { parentSession, parentTurnId, parentModel } = rt3;
636
+ const requested = spec.model;
637
+ if (requested === void 0 || requested === parentModel)
638
+ return parentModel;
639
+ const models = parentSession.providers.getActive().models;
640
+ if (models.length > 0 && !models.some((m3) => m3.id === requested)) {
641
+ await emitSubagentWarning(parentSession, parentTurnId, label3, childSessionId, `unknown model "${requested}" \u2014 falling back to parent model "${parentModel}"`);
642
+ return parentModel;
643
+ }
644
+ return requested;
645
+ }
646
+ function buildChildContext(rt3, spec, model, childSessionId, childTurnId, toolRegistry, childLog, spawner) {
647
+ const { parentSession, parentSignal } = rt3;
605
648
  return {
606
649
  sessionId: childSessionId,
607
650
  turnId: childTurnId,
608
- model: spec.model ?? parentModel,
651
+ model,
609
652
  ...spec.systemPrompt !== void 0 ? { systemPrompt: spec.systemPrompt } : {},
610
653
  provider: parentSession.providers.getActive(),
611
654
  tools: toolRegistry,
@@ -674,6 +717,7 @@ async function* runTurn(session, prompt, opts = {}) {
674
717
  const turnId = opts.turnId ?? session.startTurn().turnId;
675
718
  const provider = session.providers.getActive();
676
719
  const model = opts.model ?? provider.models[0]?.id ?? "default";
720
+ session.lastResolvedModel = model;
677
721
  const queue = [];
678
722
  const waiters = [];
679
723
  let done = false;
@@ -3023,6 +3067,11 @@ var init_session = __esm({
3023
3067
  elisionSettings = null;
3024
3068
  /** Lazy tool loading toggle, from `config.context.lazyTools`. Default off. */
3025
3069
  lazyTools = false;
3070
+ /**
3071
+ * Model id resolved by the most recent `runTurn()` (see SessionRuntime).
3072
+ * Last-writer-wins for concurrent turns; null until the first turn runs.
3073
+ */
3074
+ lastResolvedModel = null;
3026
3075
  /**
3027
3076
  * Live runtime capabilities the host installs on a local Session (see
3028
3077
  * SessionLike). A RemoteSession leaves them undefined. Declared here — rather
@@ -3638,7 +3687,7 @@ The body is the instructions for future invocations. Keep it under 30 lines. Num
3638
3687
  });
3639
3688
  async function synthesizeSkill(session, intent, scope, opts = {}) {
3640
3689
  const provider = session.providers.getActive();
3641
- const model = opts.model ?? provider.models[0]?.id ?? "claude-sonnet-4-6";
3690
+ const model = opts.model ?? session.lastResolvedModel ?? provider.models[0]?.id ?? "default";
3642
3691
  const draft = await draftSkill(provider, model, intent, session.signal);
3643
3692
  const baseDir = scope === "project" ? opts.projectDir ?? defaultProjectSkillsDir(session.cwd) : opts.userDir ?? defaultUserSkillsDir();
3644
3693
  await promises.mkdir(baseDir, { recursive: true });
@@ -134325,13 +134374,14 @@ async function createWebSocketTransportServer(opts) {
134325
134374
  const host = opts.host ?? "127.0.0.1";
134326
134375
  const maxConnections = opts.maxConnections ?? DEFAULT_MAX_CONNECTIONS;
134327
134376
  let currentToken = opts.authToken;
134377
+ let currentAllowedOrigins = opts.allowedOrigins ?? [];
134328
134378
  let connections = 0;
134329
134379
  const wss = new import_websocket_server.default({
134330
134380
  host,
134331
134381
  port: opts.port,
134332
134382
  maxPayload: opts.maxPayloadBytes ?? 64 * 1024 * 1024,
134333
134383
  verifyClient: (info) => {
134334
- if (!checkWsOrigin(info.req, opts.allowedOrigins)) {
134384
+ if (!checkWsOrigin(info.req, currentAllowedOrigins)) {
134335
134385
  console.warn(`[moxxy] ws bridge: rejected browser-origin upgrade (Origin: ${String(info.req.headers.origin)})`);
134336
134386
  return false;
134337
134387
  }
@@ -134371,6 +134421,9 @@ async function createWebSocketTransportServer(opts) {
134371
134421
  for (const client of wss.clients)
134372
134422
  client.terminate();
134373
134423
  },
134424
+ setAllowedOrigins(origins) {
134425
+ currentAllowedOrigins = [...origins];
134426
+ },
134374
134427
  clientCount() {
134375
134428
  return connections;
134376
134429
  },
@@ -134704,7 +134757,7 @@ function isRunnerUp(socketPath = runnerSocketPath()) {
134704
134757
 
134705
134758
  // ../runner/dist/server.js
134706
134759
  init_dist();
134707
- var RUNNER_PROTOCOL_VERSION = 5;
134760
+ var RUNNER_PROTOCOL_VERSION = 6;
134708
134761
  var MIN_COMPATIBLE_PROTOCOL_VERSION = 1;
134709
134762
  var RunnerMethod = {
134710
134763
  /** client->server: handshake; returns the initial info snapshot. */
@@ -134779,7 +134832,14 @@ var RunnerNotification = {
134779
134832
  * clear: post-reset events restart at seq 0, which a seq-contiguous
134780
134833
  * mirror only accepts from an empty log.
134781
134834
  */
134782
- SessionReset: "session.reset"
134835
+ SessionReset: "session.reset",
134836
+ /**
134837
+ * Sent once, immediately before the attach-time replay loop (v6): the
134838
+ * first seq this connection will replay/stream. The client rebases its
134839
+ * (empty) mirror to `fromSeq` so a partial replay (`replay: 'none'` /
134840
+ * `{ tail }`) ingests contiguously instead of dropping every event.
134841
+ */
134842
+ ReplayStart: "replay.start"
134783
134843
  };
134784
134844
  var attachmentSchema = z.object({
134785
134845
  kind: z.string(),
@@ -134790,14 +134850,22 @@ var attachmentSchema = z.object({
134790
134850
  var attachParamsSchema = z.object({
134791
134851
  protocolVersion: z.number(),
134792
134852
  role: z.string(),
134793
- sinceSeq: z.number().int().nonnegative().optional()
134853
+ sinceSeq: z.number().int().nonnegative().optional(),
134854
+ replay: z.union([
134855
+ z.literal("full"),
134856
+ z.literal("none"),
134857
+ z.object({ tail: z.number().int().positive() })
134858
+ ]).optional()
134794
134859
  });
134795
134860
  var runTurnParamsSchema = z.object({
134796
134861
  prompt: z.string(),
134797
134862
  model: z.string().optional(),
134798
134863
  systemPrompt: z.string().optional(),
134799
134864
  maxIterations: z.number().int().positive().optional(),
134800
- attachments: z.array(attachmentSchema).optional()
134865
+ attachments: z.array(attachmentSchema).optional(),
134866
+ // Bounded like the other id-bearing params so a hostile client can't stuff
134867
+ // an arbitrary blob into every event of the turn.
134868
+ turnId: z.string().min(1).max(120).optional()
134801
134869
  });
134802
134870
  var abortParamsSchema = z.object({ turnId: z.string() });
134803
134871
  var setResolverParamsSchema = z.object({
@@ -134945,7 +135013,11 @@ var RunnerServer = class {
134945
135013
  }
134946
135014
  client.role = params.role;
134947
135015
  client.attached = true;
134948
- for (const event of this.session.log.slice(0)) {
135016
+ const replay = params.replay ?? "full";
135017
+ const total = this.session.log.length;
135018
+ const start = replay === "full" ? 0 : replay === "none" ? total : Math.max(0, total - replay.tail);
135019
+ client.peer.notify(RunnerNotification.ReplayStart, { fromSeq: start });
135020
+ for (const event of this.session.log.slice(start)) {
134949
135021
  client.peer.notify(RunnerNotification.Event, { event });
134950
135022
  }
134951
135023
  return {
@@ -134956,7 +135028,10 @@ var RunnerServer = class {
134956
135028
  }
134957
135029
  handleRunTurn(client, raw) {
134958
135030
  const params = runTurnParamsSchema.parse(raw);
134959
- const turnId = newTurnId();
135031
+ const turnId = params.turnId ? asTurnId(params.turnId) : newTurnId();
135032
+ if (this.turnControllers.has(turnId)) {
135033
+ throw new Error(`turn id ${turnId} is already in flight \u2014 client-supplied turn ids must be unique`);
135034
+ }
134960
135035
  const controller = new AbortController();
134961
135036
  this.turnControllers.set(turnId, { controller, owner: client });
134962
135037
  client.turns.add(turnId);
@@ -135361,6 +135436,10 @@ var RemoteSession = class {
135361
135436
  this.peer.on(RunnerNotification.InfoChanged, (params) => {
135362
135437
  this.info = params.info;
135363
135438
  });
135439
+ this.peer.on(RunnerNotification.ReplayStart, (params) => {
135440
+ const { fromSeq } = params;
135441
+ this.mirror.rebase(fromSeq);
135442
+ });
135364
135443
  this.peer.on(RunnerNotification.SessionReset, () => {
135365
135444
  this.mirror.clear();
135366
135445
  });
@@ -135404,11 +135483,12 @@ var RemoteSession = class {
135404
135483
  return new Set(this.info?.readyProviders ?? []);
135405
135484
  }
135406
135485
  /** Handshake. Resolves once history has been replayed into the mirror. */
135407
- async attach(role, sinceSeq) {
135486
+ async attach(role, sinceSeq, replay) {
135408
135487
  const result = await this.peer.request(RunnerMethod.Attach, {
135409
135488
  protocolVersion: RUNNER_PROTOCOL_VERSION,
135410
135489
  role,
135411
- sinceSeq
135490
+ sinceSeq,
135491
+ ...replay ? { replay } : {}
135412
135492
  });
135413
135493
  this.info = result.info;
135414
135494
  this.serverProtocolVersion = typeof result.protocolVersion === "number" ? result.protocolVersion : RUNNER_PROTOCOL_VERSION;
@@ -135463,7 +135543,11 @@ var RemoteSession = class {
135463
135543
  ...opts.model ? { model: opts.model } : {},
135464
135544
  ...opts.systemPrompt ? { systemPrompt: opts.systemPrompt } : {},
135465
135545
  ...opts.maxIterations ? { maxIterations: opts.maxIterations } : {},
135466
- ...opts.attachments && opts.attachments.length > 0 ? { attachments: opts.attachments } : {}
135546
+ ...opts.attachments && opts.attachments.length > 0 ? { attachments: opts.attachments } : {},
135547
+ // Pre-minted client turn id (v6) so per-turn event filters match. The
135548
+ // reply's turnId stays authoritative: an older server ignores ours and
135549
+ // mints its own.
135550
+ ...opts.turnId ? { turnId: opts.turnId } : {}
135467
135551
  });
135468
135552
  const turnId = result.turnId;
135469
135553
  const stream = new TurnStream();
@@ -135758,7 +135842,7 @@ async function connectRemoteSession(opts = {}) {
135758
135842
  const transport = opts.transport ?? await connectWithRetry(socketPath, opts.connectRetries ?? 5);
135759
135843
  const session = new RemoteSession(transport);
135760
135844
  try {
135761
- await session.attach(opts.role ?? "client", opts.sinceSeq ?? 0);
135845
+ await session.attach(opts.role ?? "client", opts.sinceSeq ?? 0, opts.replay);
135762
135846
  return session;
135763
135847
  } catch (err) {
135764
135848
  await maybeRecoverFromMismatch(err, socketPath, opts);
@@ -135914,6 +135998,17 @@ var REMOTE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
135914
135998
  "session.setMode",
135915
135999
  "session.newSession",
135916
136000
  "session.runCommand",
136001
+ // Multi-session conversations: list/create/switch/rename are conversation-
136002
+ // scoped — the same trust class as `session.newSession` (already allowed),
136003
+ // and what a paired phone needs to mirror the desktop's session list.
136004
+ // `sessions.remove` is deliberately NOT here: it deletes on-disk state
136005
+ // (the runner's session JSONL + the chat NDJSON transcript), a destructive
136006
+ // host mutation in the same class as `desks.remove`, which is also
136007
+ // host-only.
136008
+ "sessions.list",
136009
+ "sessions.create",
136010
+ "sessions.setActive",
136011
+ "sessions.rename",
135917
136012
  // Voice input (capability-probed; transcribe fails coded without a transcriber).
135918
136013
  "session.hasTranscriber",
135919
136014
  "session.transcribe",
@@ -136049,6 +136144,20 @@ var ipcInputSchemas = {
136049
136144
  id: z.string().min(1).max(256),
136050
136145
  name: z.string().min(1).max(200)
136051
136146
  }),
136147
+ // Sessions: create/rename persist the name into the desks JSON (bound it
136148
+ // like desks.create/rename); setActive spawns a runner and remove deletes
136149
+ // the session's on-disk logs, so their ids are bounded too. These commands
136150
+ // are also served to remote (WS) clients, so the bounds are load-bearing.
136151
+ "sessions.create": z.object({
136152
+ deskId: z.string().min(1).max(256).optional(),
136153
+ name: z.string().min(1).max(200).optional()
136154
+ }).optional(),
136155
+ "sessions.setActive": z.object({ id: z.string().min(1).max(256) }),
136156
+ "sessions.remove": z.object({ id: z.string().min(1).max(256) }),
136157
+ "sessions.rename": z.object({
136158
+ id: z.string().min(1).max(256),
136159
+ name: z.string().min(1).max(200)
136160
+ }),
136052
136161
  // Whitelist the fields a renderer may write — `version` is managed by
136053
136162
  // the main process; unknown keys are rejected (.strict()).
136054
136163
  "prefs.update": z.object({
@@ -136056,7 +136165,8 @@ var ipcInputSchemas = {
136056
136165
  clerkUserId: z.string().max(256).nullable().optional(),
136057
136166
  clerkDisplayName: z.string().max(256).nullable().optional(),
136058
136167
  signedInAt: z.number().nullable().optional(),
136059
- mobileGatewayEnabled: z.boolean().optional()
136168
+ mobileGatewayEnabled: z.boolean().optional(),
136169
+ theme: z.enum(["light", "dark", "system"]).optional()
136060
136170
  }).strict(),
136061
136171
  // Mobile-gateway control. Both no-arg variants pin the payload to "nothing"
136062
136172
  // so a hostile caller can't smuggle args; setEnabled is a strict boolean.
@@ -136444,14 +136554,41 @@ function isWildcardHost(host) {
136444
136554
  const h3 = host.trim().toLowerCase();
136445
136555
  return h3 === "0.0.0.0" || h3 === "::" || h3 === "[::]";
136446
136556
  }
136557
+ var VIRTUAL_IFACE = /^(?:utun|tun|tap|ppp|ipsec|wg|zt|ts|tailscale|vmnet|vnic|bridge|docker|veth|awdl|llw|ap|anpi)\d*$/i;
136558
+ function isLinkLocalV4(ip) {
136559
+ return ip.startsWith("169.254.");
136560
+ }
136561
+ function isRfc1918(ip) {
136562
+ if (ip.startsWith("10.") || ip.startsWith("192.168."))
136563
+ return true;
136564
+ const m3 = /^172\.(\d{1,3})\./.exec(ip);
136565
+ return m3 !== null && Number(m3[1]) >= 16 && Number(m3[1]) <= 31;
136566
+ }
136567
+ function isCgnat(ip) {
136568
+ const m3 = /^100\.(\d{1,3})\./.exec(ip);
136569
+ return m3 !== null && Number(m3[1]) >= 64 && Number(m3[1]) <= 127;
136570
+ }
136447
136571
  function lanHost(fallback) {
136448
- for (const list of Object.values(os5__default.networkInterfaces())) {
136572
+ let best = null;
136573
+ for (const [name, list] of Object.entries(os5__default.networkInterfaces())) {
136449
136574
  for (const ni of list ?? []) {
136450
- if (ni.family === "IPv4" && !ni.internal)
136451
- return ni.address;
136575
+ if (ni.family !== "IPv4" || ni.internal)
136576
+ continue;
136577
+ const virtual = VIRTUAL_IFACE.test(name);
136578
+ let rank;
136579
+ if (isLinkLocalV4(ni.address))
136580
+ rank = 5;
136581
+ else if (isRfc1918(ni.address))
136582
+ rank = virtual ? 3 : 1;
136583
+ else if (isCgnat(ni.address))
136584
+ rank = 4;
136585
+ else
136586
+ rank = virtual ? 4 : 2;
136587
+ if (!best || rank < best.rank)
136588
+ best = { address: ni.address, rank };
136452
136589
  }
136453
136590
  }
136454
- return fallback;
136591
+ return best?.address ?? fallback;
136455
136592
  }
136456
136593
  function advertisedHost(bindHost) {
136457
136594
  if (isWildcardHost(bindHost))
@@ -136467,6 +136604,20 @@ function buildConnectUrl(opts) {
136467
136604
  }
136468
136605
  return `ws://${opts.localHost}:${opts.port}/?t=${t2}`;
136469
136606
  }
136607
+ function connectUrlOrigin(url2) {
136608
+ const u2 = new URL(url2);
136609
+ const secure = u2.protocol === "wss:" || u2.protocol === "https:";
136610
+ return `${secure ? "https" : "http"}://${u2.host}`;
136611
+ }
136612
+ function advertisedOrigins(bindHost, port) {
136613
+ return [
136614
+ .../* @__PURE__ */ new Set([
136615
+ connectUrlOrigin(`ws://${advertisedHost(bindHost)}:${port}`),
136616
+ `http://127.0.0.1:${port}`,
136617
+ `http://localhost:${port}`
136618
+ ])
136619
+ ];
136620
+ }
136470
136621
 
136471
136622
  // ../plugin-channel-mobile/dist/tunnel.js
136472
136623
  function normalizeTunnelChoice(raw) {
@@ -136551,10 +136702,12 @@ var MobileChannel = class {
136551
136702
  this.host = host;
136552
136703
  host.register();
136553
136704
  host.wire();
136705
+ const localOrigins = advertisedOrigins(this.bindHost, this.port);
136554
136706
  const server = await startWsBridge(bus, {
136555
136707
  port: this.port,
136556
136708
  host: this.bindHost,
136557
136709
  authToken: this.token,
136710
+ allowedOrigins: localOrigins,
136558
136711
  // Back-compat ONLY: the QR this channel prints embeds the token as `?t=`
136559
136712
  // (pairing payload); current apps strip it and authenticate via the
136560
136713
  // Sec-WebSocket-Protocol bearer entry, but older installed builds still
@@ -136569,6 +136722,7 @@ var MobileChannel = class {
136569
136722
  try {
136570
136723
  this.tunnel = await provider.open({ port: this.port, host: this.bindHost });
136571
136724
  tunnelUrl = this.tunnel.url;
136725
+ server.setAllowedOrigins([...localOrigins, connectUrlOrigin(tunnelUrl)]);
136572
136726
  this.logger?.info?.("mobile tunnel open", { provider: provider.name, url: tunnelUrl });
136573
136727
  } catch (err) {
136574
136728
  this.logger?.warn?.("mobile tunnel failed; using the local URL", {
@@ -137300,7 +137454,7 @@ var agentSpecSchema = z$1.object({
137300
137454
  agentType: z$1.string().optional().describe('Named agent kind to spawn (e.g. "researcher", "code-reviewer"). Looked up in the agent registry contributed by installed plugins. Omit, or pass "default", for a generic tool-use agent. Unknown types fall back to default \u2014 the request never fails over a missing kind. List of currently-registered kinds is visible via the /agents command.'),
137301
137455
  label: z$1.string().max(60).optional().describe('Short label shown in progress events (e.g. "research-deps", "lint-fix-A").'),
137302
137456
  systemPrompt: z$1.string().optional().describe("Override the kind's system prompt. Use to set persona, constraints, or hand off upstream artifacts the child needs as context."),
137303
- model: z$1.string().optional().describe("Model id override; defaults to the kind's model, then the parent's."),
137457
+ model: z$1.string().optional().describe("Model id override; defaults to the kind's model, then the parent's. Omit unless the user explicitly requested a specific model \u2014 do NOT invent model ids. Unknown ids fall back to the parent's model with a warning."),
137304
137458
  mode: z$1.string().optional().describe(`Loop strategy override. Valid values: "default", "goal", "research". OMIT for the kind's default \u2014 do NOT invent names.`),
137305
137459
  allowedTools: z$1.array(z$1.string()).optional().describe("Restrict the child to these tool names. Overrides the kind's allowlist if set.")
137306
137460
  });
@@ -143956,7 +144110,7 @@ function createTool(deps) {
143956
144110
  if (!provider) {
143957
144111
  throw new MoxxyError({ code: "PROVIDER_NOT_CONFIGURED", message: "workflow_create: no active provider to draft with." });
143958
144112
  }
143959
- const model = deps.draftModel ?? provider.models[0]?.id ?? "claude-sonnet-4-6";
144113
+ const model = deps.draftModel ?? provider.models[0]?.id ?? "default";
143960
144114
  const drafted = await draftWorkflow(provider, model, intent, ctx.signal, {
143961
144115
  ...deps.listSkills ? { availableSkills: deps.listSkills() } : {},
143962
144116
  ...deps.listTools ? { availableTools: deps.listTools() } : {},
@@ -144847,7 +145001,7 @@ async function fireAfterWorkflowDependents(args) {
144847
145001
  }
144848
145002
  }
144849
145003
  function activeModel(session) {
144850
- return safeActiveProvider(session)?.models[0]?.id ?? "claude-sonnet-4-6";
145004
+ return session.lastResolvedModel ?? safeActiveProvider(session)?.models[0]?.id ?? "default";
144851
145005
  }
144852
145006
  function safeActiveProvider(session) {
144853
145007
  try {
@@ -145356,6 +145510,15 @@ function scopedSessionView(session, allowedTools, triggerName) {
145356
145510
  get pluginHost() {
145357
145511
  return session.pluginHost;
145358
145512
  },
145513
+ // Read-through AND write-through: runTurn records the resolved model on
145514
+ // the session it was handed, and that must land on the real session, not
145515
+ // this per-fire view.
145516
+ get lastResolvedModel() {
145517
+ return session.lastResolvedModel;
145518
+ },
145519
+ set lastResolvedModel(model) {
145520
+ session.lastResolvedModel = model;
145521
+ },
145359
145522
  startTurn: () => session.startTurn(),
145360
145523
  appContext: () => session.appContext()
145361
145524
  };