@moxxy/cli 0.14.3 → 0.14.4

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
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { z as z$1, createMutex, defineTunnelProvider, definePlugin, defineProvider, defineTool, MoxxyError, asTurnId, defineMode, asPluginId, defineCommand, defineChannel, defineWorkflowExecutor, toFriendlyError, estimateTextTokens, classifyHttpStatus, createStuckLoopDetector, runCompactionIfNeeded, runElisionIfNeeded, collectProviderStream, usageEventFields, isContextOverflowError, emitRequestsAndDetectStuck, executeToolUses, buildSystemPromptWithSkills, projectMessages, defineCompactor, defineCacheStrategy, denyByDefaultResolver, createAllowListResolver, zodToJsonSchema, fileDiffSummary, runSingleShotTurn, defineSurface, runManualCompaction, isFileDiffDisplay, renderFrontmatter, defineEmbedder, migrateModeName, skillFrontmatterSchema, asSkillId, startChannelWith, parseFrontmatterFile, createDeferredPermissionResolver, getInstallHint, defineTranscriber, summarizeTokensByModel, countNodes, moxxyPackageSchema, encodeLoginPrompt, classifyNetworkError, addModelTotals, createJsonFileStore, ISOLATION_RANK, MOXXY_PCM16_24KHZ_MIME, fileDiffVerb, parseFrontmatter, createCallbackResolver, autoAllowResolver, asSessionId, asToolCallId, defineViewRenderer, DEFAULT_VIEW_TAGS, isSafeViewUrl, evaluateToolRule, summarizeSessionTokensFromEvents, toDiffRows, diffGutterNo, computeElisionState, toolResultStubbed, toolResultStub, toolResultBytes, conversationalStubbed, conversationalStub, asEventId } from '@moxxy/sdk';
4
4
  import * as fs32 from 'fs';
5
- import fs32__default, { existsSync, promises, ReadStream, mkdirSync, statSync, readdirSync, writeFileSync, readFileSync, unlinkSync, chmodSync, watch, createReadStream } from 'fs';
5
+ import fs32__default, { existsSync, promises, ReadStream, mkdirSync, rmSync, statSync, readdirSync, writeFileSync, readFileSync, unlinkSync, chmodSync, watch, createReadStream } from 'fs';
6
6
  import * as path3 from 'path';
7
7
  import path3__default, { join, dirname, resolve, relative, isAbsolute, basename } from 'path';
8
8
  import { isCliTunnelAvailable, writeFileAtomic, spawnCliTunnel, moxxyPath, moxxyHome, bearerTokenMatches, resolveChannelToken, rotateChannelToken, readRequestBody, MOXXY_WS_SUBPROTOCOL, bearerGuard, tokenFromWsProtocolHeader, writeFileAtomicSync } from '@moxxy/sdk/server';
@@ -138658,7 +138658,7 @@ var CollaborationState = class {
138658
138658
  return;
138659
138659
  agent.status = status;
138660
138660
  this.emitFn({ kind: "agent_status", agentId, status, ...detail ? { detail } : {} });
138661
- if (status === "crashed" || status === "killed")
138661
+ if (status === "crashed" || status === "killed" || status === "failed")
138662
138662
  this.releaseAllFor(agentId);
138663
138663
  }
138664
138664
  markDone(agentId, summary, artifacts) {
@@ -138671,7 +138671,7 @@ var CollaborationState = class {
138671
138671
  this.emitFn({ kind: "agent_done", agentId, summary, ...artifacts ? { artifacts } : {} });
138672
138672
  }
138673
138673
  allDone() {
138674
- const live = this.agentOrder.map((id) => this.agents.get(id)).filter((a2) => a2.status !== "crashed" && a2.status !== "killed");
138674
+ const live = this.agentOrder.map((id) => this.agents.get(id)).filter((a2) => a2.status !== "crashed" && a2.status !== "killed" && a2.status !== "failed");
138675
138675
  return live.length > 0 && live.every((a2) => a2.status === "done");
138676
138676
  }
138677
138677
  roleOf(agentId) {
@@ -139029,7 +139029,7 @@ async function createCollaborationHub(opts) {
139029
139029
  connections.delete(peer);
139030
139030
  if (agentId) {
139031
139031
  const agent = state.rosterView().agents.find((a2) => a2.id === agentId);
139032
- if (agent && agent.status !== "done" && agent.status !== "killed") {
139032
+ if (agent && agent.status !== "done" && agent.status !== "failed" && agent.status !== "killed") {
139033
139033
  state.setStatus(agentId, "crashed");
139034
139034
  }
139035
139035
  }
@@ -139275,13 +139275,24 @@ var PeerSupervisor = class {
139275
139275
  child.on("exit", () => {
139276
139276
  proc.exited = true;
139277
139277
  });
139278
+ child.on("error", (err) => {
139279
+ proc.exited = true;
139280
+ proc.stderr.push(`spawn error: ${err.message}`);
139281
+ });
139278
139282
  return { socket };
139279
139283
  }
139284
+ /** True once the child has exited or its spawn failed. */
139285
+ hasExited(agentId) {
139286
+ const proc = this.peers.get(agentId);
139287
+ return proc ? proc.exited : false;
139288
+ }
139280
139289
  /** Last stderr lines from a peer — used to diagnose a crash. */
139281
139290
  stderrOf(agentId) {
139282
139291
  return this.peers.get(agentId)?.stderr ?? [];
139283
139292
  }
139284
- /** Best-effort: abort a single peer's in-flight turn via its runner, then kill. */
139293
+ /** Stop a single peer and AWAIT its real exit (with a force-kill fallback), so
139294
+ * callers — e.g. the sequential fallback — can rely on the workspace being
139295
+ * free before the next agent starts. */
139285
139296
  async stop(agentId) {
139286
139297
  const proc = this.peers.get(agentId);
139287
139298
  if (!proc || proc.exited)
@@ -139290,6 +139301,27 @@ var PeerSupervisor = class {
139290
139301
  proc.child.kill("SIGTERM");
139291
139302
  } catch {
139292
139303
  }
139304
+ await new Promise((resolve13) => {
139305
+ if (proc.exited)
139306
+ return resolve13();
139307
+ let settled = false;
139308
+ const done = () => {
139309
+ if (settled)
139310
+ return;
139311
+ settled = true;
139312
+ clearTimeout(timer);
139313
+ resolve13();
139314
+ };
139315
+ proc.child.once("exit", done);
139316
+ const timer = setTimeout(() => {
139317
+ try {
139318
+ proc.child.kill("SIGKILL");
139319
+ } catch {
139320
+ }
139321
+ done();
139322
+ }, FORCE_KILL_GRACE_MS);
139323
+ timer.unref?.();
139324
+ });
139293
139325
  }
139294
139326
  async shutdownAll(_reason) {
139295
139327
  if (this.shuttingDown)
@@ -139423,6 +139455,7 @@ function releaseCollabLock(sessionId) {
139423
139455
 
139424
139456
  // ../mode-collaborative/dist/collab-loop.js
139425
139457
  var POLL_MS = 500;
139458
+ var BOOT_DEADLINE_MS = 9e4;
139426
139459
  function runCollaborativeMode(ctx) {
139427
139460
  return runCollaborative(ctx, {});
139428
139461
  }
@@ -139491,13 +139524,18 @@ async function* runCollaborative(ctx, deps) {
139491
139524
  yield await ctx.emit(plugin4(ctx, "collab_started", { task, parallel, gitInstalled: gitInstalled2, gitRepo }));
139492
139525
  supervisor.spawn({ entry: architectEntry, cwd: cwd2, mode: COLLAB_ARCHITECT_MODE_NAME });
139493
139526
  yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: ARCHITECT_AGENT_ID, role: "architect" }));
139494
- const architectOk = await waitForAgent(hub, ARCHITECT_AGENT_ID, ctx.signal, cfg.wallClockMs);
139527
+ const architectOk = await waitForAgent(hub, supervisor, ARCHITECT_AGENT_ID, ctx.signal, cfg.wallClockMs);
139495
139528
  if (ctx.signal.aborted) {
139496
139529
  yield await ctx.emit(emitAbort(ctx, "aborted during design"));
139497
139530
  return;
139498
139531
  }
139499
139532
  if (!architectOk) {
139500
- yield await ctx.emit(assistant(ctx, "The architect did not finish the design. Stopping the collaboration."));
139533
+ const why = supervisor.stderrOf(ARCHITECT_AGENT_ID).slice(-4).join("\n");
139534
+ yield await ctx.emit(plugin4(ctx, "collab_agent_failed", { id: ARCHITECT_AGENT_ID, status: statusOf(hub, ARCHITECT_AGENT_ID), stderr: supervisor.stderrOf(ARCHITECT_AGENT_ID).slice(-6) }));
139535
+ yield await ctx.emit(assistant(ctx, `The architect did not finish the design \u2014 stopping the collaboration.${why ? `
139536
+
139537
+ Last diagnostics:
139538
+ ${why}` : ""}`));
139501
139539
  return;
139502
139540
  }
139503
139541
  let roster = readRoster(join(cwd2, COLLAB_SCAFFOLD_DIR, ROSTER_FILENAME), cfg.maxAgents);
@@ -139538,7 +139576,8 @@ async function* runCollaborative(ctx, deps) {
139538
139576
  supervisor.spawn({ entry, cwd: wt3, mode: COLLAB_PEER_MODE_NAME });
139539
139577
  yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
139540
139578
  }
139541
- await waitForAgents(hub, roster.map((r2) => r2.id), ctx.signal, cfg.wallClockMs);
139579
+ await waitForAgents(hub, supervisor, roster.map((r2) => r2.id), ctx.signal, cfg.wallClockMs);
139580
+ yield* surfaceFailures(ctx, hub, supervisor, roster.map((r2) => r2.id));
139542
139581
  for (const r2 of roster)
139543
139582
  if (statusOf(hub, r2.id) === "done")
139544
139583
  doneIds.push(r2.id);
@@ -139549,7 +139588,9 @@ async function* runCollaborative(ctx, deps) {
139549
139588
  hub.state.addAgent(entry);
139550
139589
  supervisor.spawn({ entry, cwd: cwd2, mode: COLLAB_PEER_MODE_NAME });
139551
139590
  yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
139552
- const ok = await waitForAgent(hub, entry.id, ctx.signal, cfg.wallClockMs);
139591
+ const ok = await waitForAgent(hub, supervisor, entry.id, ctx.signal, cfg.wallClockMs);
139592
+ if (!ok)
139593
+ yield* surfaceFailures(ctx, hub, supervisor, [entry.id]);
139553
139594
  await supervisor.stop(entry.id);
139554
139595
  if (ok && statusOf(hub, entry.id) === "done")
139555
139596
  doneIds.push(entry.id);
@@ -139593,6 +139634,16 @@ ${mergeNote}` : ""}`));
139593
139634
  if (hub)
139594
139635
  await hub.close();
139595
139636
  releaseCollabLock(String(ctx.sessionId));
139637
+ for (const wt3 of worktrees.values()) {
139638
+ await removeWorktree(cwd2, wt3).catch(() => void 0);
139639
+ }
139640
+ try {
139641
+ rmSync(collabRunDir(runId), { recursive: true, force: true });
139642
+ rmSync(worktreeRoot(runId), { recursive: true, force: true });
139643
+ } catch {
139644
+ }
139645
+ if (worktrees.size > 0)
139646
+ await git(cwd2, ["worktree", "prune"]).catch(() => void 0);
139596
139647
  }
139597
139648
  }
139598
139649
  function lastUserPromptText(ctx) {
@@ -139643,30 +139694,62 @@ function slug(s2) {
139643
139694
  function statusOf(hub, id) {
139644
139695
  return hub.state.rosterView().agents.find((a2) => a2.id === id)?.status;
139645
139696
  }
139646
- async function waitForAgent(hub, id, signal, timeoutMs) {
139647
- const deadline = Date.now() + timeoutMs;
139697
+ function agentSettled(hub, supervisor, id, connected, bootDeadlineAt) {
139698
+ const status = statusOf(hub, id);
139699
+ if (status && status !== "pending")
139700
+ connected.add(id);
139701
+ if (status === "done")
139702
+ return "done";
139703
+ if (status === "failed" || status === "crashed" || status === "killed")
139704
+ return "failed";
139705
+ if (supervisor?.hasExited(id))
139706
+ return "failed";
139707
+ if (!connected.has(id) && Date.now() > bootDeadlineAt)
139708
+ return "failed";
139709
+ return void 0;
139710
+ }
139711
+ async function waitForAgent(hub, supervisor, id, signal, wallClockMs) {
139712
+ const wallDeadline = Date.now() + wallClockMs;
139713
+ const bootDeadlineAt = Date.now() + BOOT_DEADLINE_MS;
139714
+ const connected = /* @__PURE__ */ new Set();
139648
139715
  for (; ; ) {
139649
- const status = statusOf(hub, id);
139650
- if (status === "done")
139716
+ const settled = agentSettled(hub, supervisor, id, connected, bootDeadlineAt);
139717
+ if (settled === "done")
139651
139718
  return true;
139652
- if (status === "crashed" || status === "killed")
139719
+ if (settled === "failed")
139653
139720
  return false;
139654
- if (signal.aborted || Date.now() > deadline)
139721
+ if (signal.aborted || Date.now() > wallDeadline)
139655
139722
  return false;
139656
139723
  await sleep5(POLL_MS, signal);
139657
139724
  }
139658
139725
  }
139659
- async function waitForAgents(hub, ids, signal, timeoutMs) {
139660
- const deadline = Date.now() + timeoutMs;
139661
- const terminal = (s2) => s2 === "done" || s2 === "crashed" || s2 === "killed";
139726
+ async function waitForAgents(hub, supervisor, ids, signal, wallClockMs) {
139727
+ const wallDeadline = Date.now() + wallClockMs;
139728
+ const bootDeadlineAt = Date.now() + BOOT_DEADLINE_MS;
139729
+ const connected = /* @__PURE__ */ new Set();
139662
139730
  for (; ; ) {
139663
- if (ids.every((id) => terminal(statusOf(hub, id))))
139731
+ if (ids.every((id) => agentSettled(hub, supervisor, id, connected, bootDeadlineAt) !== void 0))
139664
139732
  return;
139665
- if (signal.aborted || Date.now() > deadline)
139733
+ if (signal.aborted || Date.now() > wallDeadline)
139666
139734
  return;
139667
139735
  await sleep5(POLL_MS, signal);
139668
139736
  }
139669
139737
  }
139738
+ async function* surfaceFailures(ctx, hub, supervisor, ids) {
139739
+ for (const id of ids) {
139740
+ const status = statusOf(hub, id);
139741
+ if (status === "done")
139742
+ continue;
139743
+ if (status !== "failed" && status !== "crashed" && status !== "killed") {
139744
+ hub.state.setStatus(id, "crashed", "did not reach a terminal status");
139745
+ }
139746
+ yield await ctx.emit(plugin4(ctx, "collab_agent_failed", {
139747
+ id,
139748
+ status: statusOf(hub, id),
139749
+ stderr: supervisor.stderrOf(id).slice(-6)
139750
+ }));
139751
+ }
139752
+ }
139670
139753
  function sleep5(ms, signal) {
139671
139754
  return new Promise((resolve13) => {
139672
139755
  if (signal.aborted)
@@ -139788,6 +139871,8 @@ async function* runCollabAgentLoop(ctx, opts) {
139788
139871
  permissions: autoApprove
139789
139872
  };
139790
139873
  const hub = await getProcessHubClient();
139874
+ if (hub)
139875
+ await hub.setStatus("working").catch(() => void 0);
139791
139876
  const detector = createStuckLoopDetector();
139792
139877
  const maxIterations = ctx.maxIterations ?? DEFAULT_MAX_ITERATIONS;
139793
139878
  let noop3 = 0;
@@ -144390,12 +144475,9 @@ var REMOTE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
144390
144475
  // Voice input (capability-probed; transcribe fails coded without a transcriber).
144391
144476
  "session.hasTranscriber",
144392
144477
  "session.transcribe",
144393
- // Per-workspace transcript log (the mobile ChatStoreBridge persists through
144394
- // these; they're scoped to a workspace's NDJSON log, not host config).
144395
- "chat.append",
144396
- "chat.loadSegment",
144397
- "chat.clearLog",
144398
- "chat.migrate",
144478
+ // Read a workspace's transcript history from the runner's authoritative log
144479
+ // (a paired phone may read history, scoped to a workspace, not host config).
144480
+ "chat.loadHistory",
144399
144481
  // Workflows: READ + run an existing one only. Authoring (`workflows.save`,
144400
144482
  // `workflows.validateDraft`, `workflows.setEnabled`) is host-only — a paired
144401
144483
  // phone must not rewrite or re-enable the host's workflows.
@@ -144603,25 +144685,13 @@ var ipcInputSchemas = {
144603
144685
  "mobileGateway.status": z.undefined(),
144604
144686
  "mobileGateway.rotateToken": z.undefined(),
144605
144687
  "mobileGateway.setEnabled": z.object({ enabled: z.boolean() }).strict(),
144606
- "chat.append": z.object({
144607
- workspaceId: z.string().min(1).max(256),
144608
- events: z.array(z.unknown()).max(1e4)
144609
- }),
144610
- "chat.loadSegment": z.object({
144688
+ // `before` is a runner `seq` cursor; the page is RAW events. The runner itself
144689
+ // re-validates and caps at its own MAX_HISTORY_PAGE_LIMIT (2000), so bound the
144690
+ // renderer's raw-window request to that ceiling.
144691
+ "chat.loadHistory": z.object({
144611
144692
  workspaceId: z.string().min(1).max(256),
144612
144693
  before: z.number().int().nonnegative().nullable(),
144613
- limit: z.number().int().positive().max(1e3)
144614
- }),
144615
- "chat.clearLog": z.object({ workspaceId: z.string().min(1).max(256) }),
144616
- // chat.migrate writes the supplied events straight into per-workspace NDJSON
144617
- // logs on disk, so it's a filesystem-touching command: bound both the number
144618
- // of workspaces and the events per workspace, and lock the workspaceId to a
144619
- // non-empty bounded slug so it can't traverse out of the log directory.
144620
- "chat.migrate": z.object({
144621
- workspaces: z.array(z.object({
144622
- workspaceId: z.string().min(1).max(256),
144623
- events: z.array(z.unknown()).max(1e4)
144624
- })).max(100)
144694
+ limit: z.number().int().positive().max(2e3)
144625
144695
  }),
144626
144696
  // Vault writes are security-sensitive: lock the key name to a safe slug
144627
144697
  // (letters/digits + . _ / - , no traversal) and bound the secret size.
@@ -144866,13 +144936,7 @@ var MobileSessionHost = class {
144866
144936
  this.bus.handle("ask.respond", async ({ requestId, response }) => {
144867
144937
  this.answerAsk(requestId, response);
144868
144938
  });
144869
- this.bus.handle("chat.loadSegment", async () => ({ events: [], prevCursor: null }));
144870
- this.bus.handle("chat.append", async () => {
144871
- });
144872
- this.bus.handle("chat.clearLog", async () => {
144873
- });
144874
- this.bus.handle("chat.migrate", async () => {
144875
- });
144939
+ this.bus.handle("chat.loadHistory", async () => ({ events: [], prevCursor: null }));
144876
144940
  }
144877
144941
  /** Stream session events to clients + install the ask resolvers. */
144878
144942
  wire() {
@@ -155909,6 +155973,16 @@ async function runAgentCommand(argv) {
155909
155973
  for await (const _2 of session.runTurn(subtask)) void _2;
155910
155974
  } catch {
155911
155975
  }
155976
+ try {
155977
+ const hub = await getProcessHubClient();
155978
+ if (hub) {
155979
+ const mine = (await hub.roster()).agents.find((a2) => a2.id === hub.agentId);
155980
+ if (mine && mine.status !== "done") {
155981
+ await hub.setStatus("failed", "turn ended without calling collab_done");
155982
+ }
155983
+ }
155984
+ } catch {
155985
+ }
155912
155986
  })();
155913
155987
  await runUntilSignal2(runnerServer, session, turnDone);
155914
155988
  return 0;