@femtomc/mu-agent 26.2.101 → 26.2.103

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/operator.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { HudDocSchema, normalizeHudDocs } from "@femtomc/mu-core";
2
- import { appendJsonl, getStorePaths, readJsonl } from "@femtomc/mu-core/node";
2
+ import { appendJsonl, getStorePaths } from "@femtomc/mu-core/node";
3
3
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
5
5
  import { z } from "zod";
@@ -292,93 +292,6 @@ function collectHudDocsFromToolExecutionEvent(event) {
292
292
  }
293
293
  return extractHudDocsFromToolResult(rec.result);
294
294
  }
295
- function finiteInt(value) {
296
- if (typeof value !== "number" || !Number.isFinite(value)) {
297
- return null;
298
- }
299
- return Math.trunc(value);
300
- }
301
- function stringList(value) {
302
- if (!Array.isArray(value)) {
303
- return [];
304
- }
305
- const out = [];
306
- for (const item of value) {
307
- const parsed = nonEmptyString(item);
308
- if (parsed) {
309
- out.push(parsed);
310
- }
311
- }
312
- return out;
313
- }
314
- function sessionFlashPath(repoRoot) {
315
- return join(getStorePaths(repoRoot).storeDir, "control-plane", "session_flash.jsonl");
316
- }
317
- async function loadPendingSessionFlashes(opts) {
318
- const rows = await readJsonl(sessionFlashPath(opts.repoRoot));
319
- const created = new Map();
320
- const delivered = new Set();
321
- for (const row of rows) {
322
- const rec = asRecord(row);
323
- if (!rec) {
324
- continue;
325
- }
326
- const kind = nonEmptyString(rec.kind);
327
- if (kind === "session_flash.create") {
328
- const tsMs = finiteInt(rec.ts_ms) ?? Date.now();
329
- const flashId = nonEmptyString(rec.flash_id);
330
- const sessionId = nonEmptyString(rec.session_id);
331
- const body = nonEmptyString(rec.body);
332
- if (!flashId || !sessionId || !body || sessionId !== opts.sessionId) {
333
- continue;
334
- }
335
- created.set(flashId, {
336
- flash_id: flashId,
337
- created_at_ms: tsMs,
338
- session_id: sessionId,
339
- session_kind: nonEmptyString(rec.session_kind),
340
- body,
341
- context_ids: stringList(rec.context_ids),
342
- source: nonEmptyString(rec.source),
343
- metadata: asRecord(rec.metadata) ?? {},
344
- });
345
- continue;
346
- }
347
- if (kind === "session_flash.delivery") {
348
- const flashId = nonEmptyString(rec.flash_id);
349
- const sessionId = nonEmptyString(rec.session_id);
350
- if (!flashId || !sessionId || sessionId !== opts.sessionId) {
351
- continue;
352
- }
353
- delivered.add(flashId);
354
- }
355
- }
356
- const pending = [...created.values()]
357
- .filter((row) => !delivered.has(row.flash_id))
358
- .sort((a, b) => a.created_at_ms - b.created_at_ms);
359
- const limit = Math.max(1, Math.trunc(opts.limit ?? 16));
360
- if (pending.length <= limit) {
361
- return pending;
362
- }
363
- return pending.slice(pending.length - limit);
364
- }
365
- async function markSessionFlashesDelivered(opts) {
366
- if (opts.flashIds.length === 0) {
367
- return;
368
- }
369
- const deduped = [...new Set(opts.flashIds.filter((id) => id.trim().length > 0))];
370
- for (const flashId of deduped) {
371
- const row = {
372
- kind: "session_flash.delivery",
373
- ts_ms: opts.nowMs,
374
- flash_id: flashId,
375
- session_id: opts.sessionId,
376
- delivered_by: "messaging_operator_runtime",
377
- note: null,
378
- };
379
- await appendJsonl(sessionFlashPath(opts.repoRoot), row);
380
- }
381
- }
382
295
  export class JsonFileConversationSessionStore {
383
296
  #path;
384
297
  #loaded = false;
@@ -551,46 +464,14 @@ export class MessagingOperatorRuntime {
551
464
  operatorTurnId: turnId,
552
465
  };
553
466
  }
554
- let pendingFlashes = [];
555
- try {
556
- pendingFlashes = await loadPendingSessionFlashes({
557
- repoRoot: opts.inbound.repo_root,
558
- sessionId,
559
- });
560
- }
561
- catch {
562
- pendingFlashes = [];
563
- }
564
- const inboundForBackend = pendingFlashes.length > 0
565
- ? {
566
- ...opts.inbound,
567
- metadata: {
568
- ...opts.inbound.metadata,
569
- session_flash_messages: pendingFlashes,
570
- },
571
- }
572
- : opts.inbound;
573
467
  let backendResult;
574
468
  try {
575
469
  backendResult = OperatorBackendTurnResultSchema.parse(await this.#backend.runTurn({
576
470
  sessionId,
577
471
  turnId,
578
- inbound: inboundForBackend,
472
+ inbound: opts.inbound,
579
473
  binding: opts.binding,
580
474
  }));
581
- if (pendingFlashes.length > 0) {
582
- try {
583
- await markSessionFlashesDelivered({
584
- repoRoot: opts.inbound.repo_root,
585
- sessionId,
586
- flashIds: pendingFlashes.map((row) => row.flash_id),
587
- nowMs: Date.now(),
588
- });
589
- }
590
- catch {
591
- // Best-effort delivery bookkeeping; do not fail the operator turn.
592
- }
593
- }
594
475
  }
595
476
  catch (err) {
596
477
  const failureCode = classifyBackendFailureCode(err);
@@ -707,53 +588,6 @@ function buildOperatorPromptContextBlock(metadata) {
707
588
  }
708
589
  return ["", "Client context (structured preview):", preview];
709
590
  }
710
- function extractSessionFlashPromptMessages(metadata) {
711
- const raw = metadata.session_flash_messages;
712
- if (!Array.isArray(raw)) {
713
- return [];
714
- }
715
- const out = [];
716
- for (const value of raw) {
717
- const rec = asRecord(value);
718
- if (!rec) {
719
- continue;
720
- }
721
- const flashId = nonEmptyString(rec.flash_id);
722
- const body = nonEmptyString(rec.body);
723
- const sessionId = nonEmptyString(rec.session_id);
724
- if (!flashId || !body || !sessionId) {
725
- continue;
726
- }
727
- out.push({
728
- flash_id: flashId,
729
- created_at_ms: finiteInt(rec.created_at_ms) ?? 0,
730
- session_id: sessionId,
731
- session_kind: nonEmptyString(rec.session_kind),
732
- body,
733
- context_ids: stringList(rec.context_ids),
734
- source: nonEmptyString(rec.source),
735
- metadata: asRecord(rec.metadata) ?? {},
736
- });
737
- }
738
- out.sort((a, b) => a.created_at_ms - b.created_at_ms);
739
- return out;
740
- }
741
- function buildOperatorPromptFlashBlock(metadata) {
742
- const flashes = extractSessionFlashPromptMessages(metadata);
743
- if (flashes.length === 0) {
744
- return [];
745
- }
746
- const lines = ["", `Session flash messages (${flashes.length}):`];
747
- for (const flash of flashes) {
748
- const source = flash.source ?? "unknown";
749
- const contextIds = flash.context_ids.length > 0 ? ` | context_ids=${flash.context_ids.join(",")}` : "";
750
- const bodyPreview = compactJsonPreview(flash.body, 400) ?? flash.body;
751
- lines.push(`- [${flash.flash_id}] source=${source}${contextIds}`);
752
- lines.push(` ${bodyPreview}`);
753
- }
754
- lines.push("Treat these as high-priority user-provided context for this session.");
755
- return lines;
756
- }
757
591
  function buildOperatorPrompt(input) {
758
592
  const lines = [
759
593
  `[Messaging context]`,
@@ -763,7 +597,6 @@ function buildOperatorPrompt(input) {
763
597
  ``,
764
598
  `User message: ${input.inbound.command_text}`,
765
599
  ...buildOperatorPromptContextBlock(input.inbound.metadata),
766
- ...buildOperatorPromptFlashBlock(input.inbound.metadata),
767
600
  ];
768
601
  return lines.join("\n");
769
602
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session_factory.d.ts","sourceRoot":"","sources":["../src/session_factory.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,iBAAiB,GAAG,KAAK,GAAG,MAAM,CAAC;AAExF,MAAM,MAAM,wBAAwB,GAAG;IACtC,IAAI,CAAC,EAAE,wBAAwB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC1D,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE;QAChB,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;CACF,CAAC;AAkCF,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CA2CnF"}
1
+ {"version":3,"file":"session_factory.d.ts","sourceRoot":"","sources":["../src/session_factory.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,iBAAiB,GAAG,KAAK,GAAG,MAAM,CAAC;AAExF,MAAM,MAAM,wBAAwB,GAAG;IACtC,IAAI,CAAC,EAAE,wBAAwB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC1D,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE;QAChB,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;CACF,CAAC;AAsCF,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAuDnF"}
@@ -1,8 +1,11 @@
1
1
  import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@mariozechner/pi-coding-agent";
2
2
  import { createMuResourceLoader, resolveModel } from "./backend.js";
3
3
  import { MU_DEFAULT_THEME_NAME, MU_DEFAULT_THEME_PATH } from "./ui_defaults.js";
4
+ function resolveSessionPersistenceMode(sessionOpts) {
5
+ return sessionOpts?.mode ?? (sessionOpts?.sessionFile ? "open" : "continue-recent");
6
+ }
4
7
  function createSessionManager(SessionManager, cwd, sessionOpts) {
5
- const mode = sessionOpts?.mode ?? (sessionOpts?.sessionFile ? "open" : "continue-recent");
8
+ const mode = resolveSessionPersistenceMode(sessionOpts);
6
9
  const sessionDir = sessionOpts?.sessionDir;
7
10
  switch (mode) {
8
11
  case "continue-recent":
@@ -23,10 +26,13 @@ function createSessionManager(SessionManager, cwd, sessionOpts) {
23
26
  export async function createMuSession(opts) {
24
27
  const { AuthStorage, createAgentSession, SessionManager, SettingsManager } = await import("@mariozechner/pi-coding-agent");
25
28
  const authStorage = AuthStorage.create();
29
+ const sessionMode = resolveSessionPersistenceMode(opts.session);
30
+ const shouldRestoreSessionConfig = sessionMode === "open" && !opts.provider && !opts.model;
26
31
  const defaultModel = "gpt-5.3-codex";
27
- const modelId = opts.model ?? defaultModel;
28
- const model = resolveModel(modelId, authStorage, opts.provider);
29
- if (!model) {
32
+ const requestedModelId = opts.model?.trim();
33
+ const modelId = requestedModelId && requestedModelId.length > 0 ? requestedModelId : shouldRestoreSessionConfig ? null : defaultModel;
34
+ const model = modelId ? resolveModel(modelId, authStorage, opts.provider) : undefined;
35
+ if (modelId && !model) {
30
36
  const scope = opts.provider ? ` in provider "${opts.provider}"` : "";
31
37
  throw new Error(`Model "${modelId}" not found${scope} in pi-ai registry.`);
32
38
  }
@@ -45,15 +51,22 @@ export async function createMuSession(opts) {
45
51
  createWriteTool(opts.cwd),
46
52
  createEditTool(opts.cwd),
47
53
  ];
48
- const { session } = await createAgentSession({
54
+ const requestedThinking = opts.thinking?.trim();
55
+ const thinkingLevel = requestedThinking && requestedThinking.length > 0
56
+ ? requestedThinking
57
+ : shouldRestoreSessionConfig
58
+ ? undefined
59
+ : "minimal";
60
+ const createOpts = {
49
61
  cwd: opts.cwd,
50
- model,
62
+ ...(model ? { model } : {}),
51
63
  tools,
52
- thinkingLevel: (opts.thinking ?? "minimal"),
64
+ ...(thinkingLevel ? { thinkingLevel: thinkingLevel } : {}),
53
65
  sessionManager: createSessionManager(SessionManager, opts.cwd, opts.session),
54
66
  settingsManager,
55
67
  resourceLoader,
56
68
  authStorage,
57
- });
69
+ };
70
+ const { session } = await createAgentSession(createOpts);
58
71
  return session;
59
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session_turn.d.ts","sourceRoot":"","sources":["../src/session_turn.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjG,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;AACrD,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,EAAE,uBAAuB,GAAG,IAAI,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEL,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAIlD;AAsQD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACvE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAyCA;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACnE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA+F7B"}
1
+ {"version":3,"file":"session_turn.d.ts","sourceRoot":"","sources":["../src/session_turn.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjG,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;AACrD,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,EAAE,uBAAuB,GAAG,IAAI,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEL,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAIlD;AAuUD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACvE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAyCA;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACnE,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgG7B"}
@@ -65,6 +65,29 @@ function defaultSessionDirForKind(repoRoot, sessionKind) {
65
65
  return join(storeDir, "control-plane", "operator-sessions");
66
66
  }
67
67
  }
68
+ function normalizePathForPrefixMatch(path) {
69
+ return resolve(path).replaceAll("\\", "/");
70
+ }
71
+ function inferSessionKindFromSessionDir(repoRoot, sessionDir) {
72
+ const storeDir = getStorePaths(repoRoot).storeDir;
73
+ const normalizedSessionDir = normalizePathForPrefixMatch(sessionDir);
74
+ const operatorDir = normalizePathForPrefixMatch(join(storeDir, "operator", "sessions"));
75
+ if (normalizedSessionDir === operatorDir || normalizedSessionDir.startsWith(`${operatorDir}/`)) {
76
+ return "operator";
77
+ }
78
+ const cpOperatorDir = normalizePathForPrefixMatch(join(storeDir, "control-plane", "operator-sessions"));
79
+ if (normalizedSessionDir === cpOperatorDir || normalizedSessionDir.startsWith(`${cpOperatorDir}/`)) {
80
+ return "cp_operator";
81
+ }
82
+ return null;
83
+ }
84
+ function defaultSessionLookupTargets(repoRoot) {
85
+ const storeDir = getStorePaths(repoRoot).storeDir;
86
+ return [
87
+ { sessionKind: "operator", sessionDir: join(storeDir, "operator", "sessions") },
88
+ { sessionKind: "cp_operator", sessionDir: join(storeDir, "control-plane", "operator-sessions") },
89
+ ];
90
+ }
68
91
  async function pathExists(path) {
69
92
  try {
70
93
  await stat(path);
@@ -208,9 +231,7 @@ function safeSessionFile(session) {
208
231
  return typeof value === "string" && value.trim().length > 0 ? value : null;
209
232
  }
210
233
  async function resolveSessionTarget(opts) {
211
- const sessionDir = opts.request.session_dir
212
- ? resolveRepoPath(opts.repoRoot, opts.request.session_dir)
213
- : defaultSessionDirForKind(opts.repoRoot, opts.normalizedSessionKind);
234
+ const explicitSessionDir = opts.request.session_dir ? resolveRepoPath(opts.repoRoot, opts.request.session_dir) : null;
214
235
  if (opts.request.session_file) {
215
236
  const sessionFile = resolveRepoPath(opts.repoRoot, opts.request.session_file);
216
237
  if (!(await pathExists(sessionFile))) {
@@ -223,22 +244,55 @@ async function resolveSessionTarget(opts) {
223
244
  if (headerId !== opts.request.session_id) {
224
245
  throw new SessionTurnError(409, `session_file header id mismatch (expected ${opts.request.session_id}, found ${headerId})`);
225
246
  }
247
+ const sessionDir = explicitSessionDir ?? dirname(sessionFile);
248
+ return {
249
+ sessionFile,
250
+ sessionDir,
251
+ sessionKind: opts.normalizedSessionKind ?? inferSessionKindFromSessionDir(opts.repoRoot, sessionDir),
252
+ };
253
+ }
254
+ if (explicitSessionDir || opts.normalizedSessionKind) {
255
+ const sessionDir = explicitSessionDir ?? defaultSessionDirForKind(opts.repoRoot, opts.normalizedSessionKind);
256
+ if (!(await directoryExists(sessionDir))) {
257
+ throw new SessionTurnError(404, `session directory not found: ${sessionDir}`);
258
+ }
259
+ const sessionFile = await resolveSessionFileById({
260
+ sessionDir,
261
+ sessionId: opts.request.session_id,
262
+ });
263
+ if (!sessionFile) {
264
+ throw new SessionTurnError(404, `session_id not found in ${sessionDir}: ${opts.request.session_id}`);
265
+ }
226
266
  return {
227
267
  sessionFile,
228
- sessionDir: opts.request.session_dir ? sessionDir : dirname(sessionFile),
268
+ sessionDir,
269
+ sessionKind: opts.normalizedSessionKind ?? inferSessionKindFromSessionDir(opts.repoRoot, sessionDir),
229
270
  };
230
271
  }
231
- if (!(await directoryExists(sessionDir))) {
232
- throw new SessionTurnError(404, `session directory not found: ${sessionDir}`);
272
+ const matches = [];
273
+ for (const target of defaultSessionLookupTargets(opts.repoRoot)) {
274
+ if (!(await directoryExists(target.sessionDir))) {
275
+ continue;
276
+ }
277
+ const sessionFile = await resolveSessionFileById({
278
+ sessionDir: target.sessionDir,
279
+ sessionId: opts.request.session_id,
280
+ });
281
+ if (sessionFile) {
282
+ matches.push({
283
+ sessionFile,
284
+ sessionDir: target.sessionDir,
285
+ sessionKind: target.sessionKind,
286
+ });
287
+ }
288
+ }
289
+ if (matches.length === 1) {
290
+ return matches[0];
233
291
  }
234
- const sessionFile = await resolveSessionFileById({
235
- sessionDir,
236
- sessionId: opts.request.session_id,
237
- });
238
- if (!sessionFile) {
239
- throw new SessionTurnError(404, `session_id not found in ${sessionDir}: ${opts.request.session_id}`);
292
+ if (matches.length > 1) {
293
+ throw new SessionTurnError(409, `session_id is ambiguous across operator/cp_operator stores: ${opts.request.session_id} (pass --session-kind or --session-dir)`);
240
294
  }
241
- return { sessionFile, sessionDir };
295
+ throw new SessionTurnError(404, `session_id not found in operator/cp_operator stores: ${opts.request.session_id}`);
242
296
  }
243
297
  export function parseSessionTurnRequest(body) {
244
298
  const sessionId = nonEmptyString(body.session_id);
@@ -289,18 +343,19 @@ export async function executeSessionTurn(opts) {
289
343
  request: opts.request,
290
344
  normalizedSessionKind,
291
345
  });
346
+ const effectiveSessionKind = target.sessionKind ?? normalizedSessionKind;
292
347
  const sessionFactory = opts.sessionFactory ?? createMuSession;
293
348
  const session = await sessionFactory({
294
349
  cwd: opts.repoRoot,
295
350
  systemPrompt: systemPromptForTurn({
296
- sessionKind: normalizedSessionKind,
351
+ sessionKind: effectiveSessionKind,
297
352
  extensionProfile,
298
353
  }),
299
354
  provider: opts.request.provider ?? undefined,
300
355
  model: opts.request.model ?? undefined,
301
356
  thinking: opts.request.thinking ?? undefined,
302
357
  extensionPaths: extensionPathsForTurn({
303
- sessionKind: normalizedSessionKind,
358
+ sessionKind: effectiveSessionKind,
304
359
  extensionProfile,
305
360
  }),
306
361
  session: {
@@ -365,7 +420,7 @@ export async function executeSessionTurn(opts) {
365
420
  }
366
421
  return {
367
422
  session_id: resolvedSessionId ?? opts.request.session_id,
368
- session_kind: normalizedSessionKind,
423
+ session_kind: effectiveSessionKind,
369
424
  session_file: resolvedSessionFile ?? target.sessionFile,
370
425
  context_entry_id: contextEntryId,
371
426
  reply,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-agent",
3
- "version": "26.2.101",
3
+ "version": "26.2.103",
4
4
  "description": "Shared operator runtime for mu assistant sessions and serve extensions.",
5
5
  "keywords": [
6
6
  "mu",
@@ -24,10 +24,10 @@
24
24
  "themes/**"
25
25
  ],
26
26
  "dependencies": {
27
- "@femtomc/mu-core": "26.2.101",
28
- "@mariozechner/pi-agent-core": "^0.53.0",
29
- "@mariozechner/pi-ai": "^0.53.0",
30
- "@mariozechner/pi-coding-agent": "^0.53.0",
27
+ "@femtomc/mu-core": "26.2.103",
28
+ "@mariozechner/pi-agent-core": "^0.54.2",
29
+ "@mariozechner/pi-ai": "^0.54.2",
30
+ "@mariozechner/pi-coding-agent": "^0.54.2",
31
31
  "zod": "^4.1.9"
32
32
  }
33
33
  }
@@ -19,5 +19,6 @@ Using `mu`:
19
19
  - The `mu` CLI is self-explanatory: poke around with `mu --help` to understand.
20
20
  - Use `mu memory search|timeline|stats` to access contextual memory from past interactions.
21
21
  - Use `mu memory index status|rebuild` to inspect/refresh local memory index health when needed.
22
+ - Use `mu control harness [provider] --json --pretty` to inspect harness adapter/provider/model configuration and model capability vectors.
22
23
  - Use `mu heartbeats` and `mu cron` to access persistent scheduled processes that broadcast to the user.
23
24
 
@@ -44,6 +44,7 @@ Notes:
44
44
  - `set` and `update` are both upsert-style single-doc writes.
45
45
  - `replace` is whole-inventory replacement.
46
46
  - Tool results include normalized `hud_docs` for downstream transport/rendering.
47
+ - Advisory preset-shape warnings are surfaced in tool `details.preset_warnings` when `metadata.style_preset` and doc shape diverge (non-blocking).
47
48
 
48
49
  ### Command (`/mu hud ...`)
49
50
 
@@ -72,14 +73,15 @@ Minimum practical fields:
72
73
  Common optional fields:
73
74
 
74
75
  - `scope` (for root/session/issue scoping)
75
- - `chips` (`[{key,label,tone?}]`)
76
+ - `title_style` / `snapshot_style` (`{weight?:"normal|strong", italic?:boolean, code?:boolean}`)
77
+ - `chips` (`[{key,label,tone?,style?}]`)
76
78
  - `sections`:
77
- - `kv` (key/value)
78
- - `checklist` (checkbox-style progress)
79
- - `activity` (recent lines)
80
- - `text` (free text)
81
- - `actions` (`[{id,label,command_text,kind?}]`)
82
- - `metadata` (machine-readable extras)
79
+ - `kv` (key/value; supports `title_style` + item `value_style`)
80
+ - `checklist` (checkbox-style progress; supports `title_style` + item `style`)
81
+ - `activity` (recent lines; supports `title_style`)
82
+ - `text` (free text; supports `title_style` + `style`)
83
+ - `actions` (`[{id,label,command_text,kind?,style?}]`)
84
+ - `metadata` (machine-readable extras; optional `style_preset` convention: `planning|subagents`)
83
85
 
84
86
  Example checklist doc:
85
87
 
@@ -92,7 +94,7 @@ Example checklist doc:
92
94
  "title": "Planning HUD",
93
95
  "scope": "mu-root-123",
94
96
  "chips": [
95
- { "key": "phase", "label": "phase:drafting", "tone": "accent" },
97
+ { "key": "phase", "label": "phase:drafting", "tone": "accent", "style": { "weight": "strong" } },
96
98
  { "key": "steps", "label": "steps:2/5", "tone": "dim" }
97
99
  ],
98
100
  "sections": [
@@ -107,11 +109,11 @@ Example checklist doc:
107
109
  }
108
110
  ],
109
111
  "actions": [
110
- { "id": "snapshot", "label": "Snapshot", "command_text": "/mu hud snapshot", "kind": "secondary" }
112
+ { "id": "snapshot", "label": "Snapshot", "command_text": "/mu hud snapshot", "kind": "secondary", "style": { "italic": true } }
111
113
  ],
112
114
  "snapshot_compact": "HUD(plan) · phase=drafting · steps=2/5",
113
115
  "updated_at_ms": 1771853115000,
114
- "metadata": { "phase": "drafting" }
116
+ "metadata": { "style_preset": "planning", "phase": "drafting" }
115
117
  }
116
118
  }
117
119
  ```
@@ -136,9 +136,6 @@ mu memory stats --pretty
136
136
  - adjust anchors (`issue_id`, `run_id`, `session_id`, `topic`, channel metadata)
137
137
  - rebuild selected sources only
138
138
 
139
- Compatibility note:
140
- - `mu context ...` remains an alias to `mu memory ...`.
141
-
142
139
  ## Evaluation scenarios
143
140
 
144
141
  1. **Issue-scoped context retrieval**