@moxxy/cli 0.14.5 → 0.14.7

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, writeFileSync, rmSync, statSync, readdirSync, readFileSync, unlinkSync, chmodSync, watch, createReadStream } from 'fs';
5
+ import fs32__default, { existsSync, promises, readFileSync, ReadStream, mkdirSync, writeFileSync, rmSync, statSync, readdirSync, 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';
@@ -85713,17 +85713,25 @@ function handleCollabEvent(e3, ref, root) {
85713
85713
  if (typeof item.id === "string") {
85714
85714
  const existing = block.tasks.find((x4) => x4.id === item.id);
85715
85715
  const owner = typeof item.owner === "string" ? item.owner : null;
85716
+ const paths = Array.isArray(item.paths) ? item.paths.filter((x4) => typeof x4 === "string") : void 0;
85717
+ const detail = typeof item.detail === "string" ? item.detail : void 0;
85716
85718
  if (!existing) {
85717
85719
  block.tasks.push({
85718
85720
  id: item.id,
85719
85721
  title: String(item.title ?? ""),
85720
85722
  status: String(item.status ?? "open"),
85721
- owner
85723
+ owner,
85724
+ ...paths && paths.length > 0 ? { paths } : {},
85725
+ ...detail ? { detail } : {}
85722
85726
  });
85723
85727
  } else {
85724
85728
  existing.title = String(item.title ?? existing.title);
85725
85729
  existing.status = String(item.status ?? existing.status);
85726
85730
  existing.owner = owner ?? existing.owner;
85731
+ if (paths && paths.length > 0)
85732
+ existing.paths = paths;
85733
+ if (detail)
85734
+ existing.detail = detail;
85727
85735
  }
85728
85736
  }
85729
85737
  break;
@@ -136052,6 +136060,7 @@ var COLLAB_SCAFFOLD_DIR = ".moxxy-collab";
136052
136060
  var CONTRACTS_FILENAME = "CONTRACTS.md";
136053
136061
  var ROSTER_FILENAME = "roster.json";
136054
136062
  var BRIEF_FILENAME = "BRIEF.md";
136063
+ var CONVERSATION_FILENAME = "CONVERSATION.md";
136055
136064
  function collabRunId(sessionId, turnId) {
136056
136065
  const tail = (s2) => s2.replace(/[^a-zA-Z0-9]/g, "").slice(-6) || "x";
136057
136066
  return `${tail(sessionId)}-${tail(turnId)}`;
@@ -136065,6 +136074,9 @@ function hubSocketPath(runId) {
136065
136074
  function peerSocketPath(runId, agentId) {
136066
136075
  return join(collabRunDir(runId), `p-${agentId}.sock`);
136067
136076
  }
136077
+ function charterFilePath(runId, agentId) {
136078
+ return join(collabRunDir(runId), `charter-${agentId}.md`);
136079
+ }
136068
136080
  function worktreeRoot(runId) {
136069
136081
  return join(tmpdir(), "moxxy-collab", runId);
136070
136082
  }
@@ -138242,7 +138254,9 @@ var COLLAB_ENV = {
138242
138254
  Role: "MOXXY_COLLAB_ROLE",
138243
138255
  Subtask: "MOXXY_COLLAB_SUBTASK",
138244
138256
  ParentTask: "MOXXY_COLLAB_PARENT_TASK",
138245
- RunnerSocket: "MOXXY_RUNNER_SOCKET"
138257
+ RunnerSocket: "MOXXY_RUNNER_SOCKET",
138258
+ /** Path (never the body) of this agent's architect-authored role charter. */
138259
+ CharterFile: "MOXXY_COLLAB_CHARTER_FILE"
138246
138260
  };
138247
138261
  var clientPromise = null;
138248
138262
  function getProcessHubClient() {
@@ -139103,6 +139117,9 @@ function stripUndefined(obj) {
139103
139117
  }
139104
139118
  var MAX_MSG_CHARS = 600;
139105
139119
  var MAX_TOTAL_CHARS = 6e3;
139120
+ var CONVERSATION_MSG_CHARS = 1200;
139121
+ var CONVERSATION_TOTAL_CHARS = 48e3;
139122
+ var SUMMARY_GUARD_CHARS = 4e3;
139106
139123
  function clip(s2, n2) {
139107
139124
  const t2 = s2.trim().replace(/\s+/g, " ");
139108
139125
  return t2.length > n2 ? `${t2.slice(0, n2 - 1)}\u2026` : t2;
@@ -139119,35 +139136,141 @@ function digestTurns(events) {
139119
139136
  }
139120
139137
  return out;
139121
139138
  }
139122
- function buildBrief(task, events) {
139123
- const turns = digestTurns(events);
139124
- const trimmed = turns.length > 0 && turns[turns.length - 1].role === "user" && turns[turns.length - 1].text.trim() === task.trim() ? turns.slice(0, -1) : turns;
139125
- const recent = trimmed.slice(-12);
139139
+ function withoutGoalTail(turns, task) {
139140
+ const last = turns[turns.length - 1];
139141
+ return turns.length > 0 && last.role === "user" && last.text.trim() === task.trim() ? turns.slice(0, -1) : turns;
139142
+ }
139143
+ function buildBrief(task, summary) {
139126
139144
  const lines = [
139127
139145
  "# Collaboration brief",
139128
139146
  "",
139129
- "This is the shared context for the whole team. It is the user's goal and the",
139130
- "conversation that led to it \u2014 read it before planning so your work fits the",
139131
- "real intent, not just your narrow sub-task.",
139147
+ "This is the team's shared brief \u2014 the goal and the key requirements,",
139148
+ "constraints, and decisions distilled from the user's conversation. The full",
139149
+ "transcript is NOT in your context; if you need a specific detail this summary",
139150
+ "omits, read or grep `.moxxy-collab/CONVERSATION.md` (do not load it wholesale).",
139151
+ "",
139152
+ "## Goal",
139153
+ "",
139154
+ clip(task, 1500) || "(no goal text)",
139155
+ "",
139156
+ "## Summary",
139157
+ "",
139158
+ clip(summary, SUMMARY_GUARD_CHARS) || "(no summary available)"
139159
+ ];
139160
+ return `${lines.join("\n")}
139161
+ `;
139162
+ }
139163
+ function heuristicSummary(task, events) {
139164
+ const recent = withoutGoalTail(digestTurns(events), task).slice(-12);
139165
+ if (recent.length === 0)
139166
+ return "(no prior conversation to summarize)";
139167
+ const lines = ["(heuristic summary \u2014 LLM summarizer unavailable; recent turns:)"];
139168
+ for (const t2 of recent) {
139169
+ lines.push(`- **${t2.role === "user" ? "User" : "Assistant"}:** ${clip(t2.text, MAX_MSG_CHARS)}`);
139170
+ }
139171
+ let body = lines.join("\n");
139172
+ if (body.length > MAX_TOTAL_CHARS)
139173
+ body = `${body.slice(0, MAX_TOTAL_CHARS - 1)}\u2026`;
139174
+ return body;
139175
+ }
139176
+ function buildConversation(task, events) {
139177
+ const turns = digestTurns(events);
139178
+ const lines = [
139179
+ "# Full conversation (recall-only)",
139180
+ "",
139181
+ "Not loaded into any agent by default. Read or grep this only when you need a",
139182
+ "specific detail the brief summary omits.",
139132
139183
  "",
139133
139184
  "## Goal",
139134
139185
  "",
139135
- clip(task, 1500) || "(no goal text)"
139186
+ clip(task, 1500) || "(no goal text)",
139187
+ "",
139188
+ "## Conversation",
139189
+ ""
139136
139190
  ];
139137
- if (recent.length > 0) {
139138
- lines.push("", "## Conversation so far", "");
139139
- for (const t2 of recent) {
139140
- const who = t2.role === "user" ? "User" : "Assistant";
139141
- lines.push(`- **${who}:** ${clip(t2.text, MAX_MSG_CHARS)}`);
139191
+ if (turns.length === 0) {
139192
+ lines.push("(no prior conversation)");
139193
+ } else {
139194
+ for (const t2 of turns) {
139195
+ lines.push(`- **${t2.role === "user" ? "User" : "Assistant"}:** ${clip(t2.text, CONVERSATION_MSG_CHARS)}`);
139142
139196
  }
139143
139197
  }
139144
- let brief = lines.join("\n");
139145
- if (brief.length > MAX_TOTAL_CHARS) {
139146
- brief = `${brief.slice(0, MAX_TOTAL_CHARS - 1)}\u2026`;
139147
- }
139148
- return `${brief}
139198
+ let body = lines.join("\n");
139199
+ if (body.length > CONVERSATION_TOTAL_CHARS)
139200
+ body = `${body.slice(0, CONVERSATION_TOTAL_CHARS - 1)}\u2026`;
139201
+ return `${body}
139149
139202
  `;
139150
139203
  }
139204
+
139205
+ // ../mode-collaborative/dist/summarize.js
139206
+ var MAX_SUMMARIZE_INPUT_CHARS = 48e3;
139207
+ var SUMMARY_MAX_TOKENS = 700;
139208
+ var COLLAB_SUMMARY_SYSTEM = `You write a SHORT shared brief for a team of AI agents about to build ONE deliverable together. From the user's conversation, extract ONLY:
139209
+ 1. The overall goal, in one or two sentences.
139210
+ 2. The concrete requirements and constraints.
139211
+ 3. Any decisions already made, and the reason.
139212
+ 4. Explicit do-nots / out-of-scope.
139213
+ Use terse bullet points. Do NOT restate the raw conversation, do NOT invent details, omit chit-chat and pleasantries. Output ONLY the brief text \u2014 no preamble, no sign-off. Keep it well under 400 words.`;
139214
+ async function summarizeConversation(args) {
139215
+ const { task, events, provider, model, signal } = args;
139216
+ if (!provider || !model)
139217
+ return null;
139218
+ const turns = digestTurns(events);
139219
+ if (turns.length === 0)
139220
+ return null;
139221
+ const joined = turns.map((t2) => `[${t2.role}] ${t2.text}`).join("\n");
139222
+ const input = joined.length > MAX_SUMMARIZE_INPUT_CHARS ? `${joined.slice(0, MAX_SUMMARIZE_INPUT_CHARS / 2)}
139223
+ [... transcript truncated ...]
139224
+ ${joined.slice(-MAX_SUMMARIZE_INPUT_CHARS / 2)}` : joined;
139225
+ try {
139226
+ let out = "";
139227
+ for await (const event of provider.stream({
139228
+ model,
139229
+ system: COLLAB_SUMMARY_SYSTEM,
139230
+ messages: [
139231
+ {
139232
+ role: "user",
139233
+ content: [
139234
+ {
139235
+ type: "text",
139236
+ text: `Task headline: ${task}
139237
+
139238
+ The user's conversation that produced this task:
139239
+
139240
+ ${input}`
139241
+ }
139242
+ ]
139243
+ }
139244
+ ],
139245
+ maxTokens: SUMMARY_MAX_TOKENS,
139246
+ ...signal ? { signal } : {}
139247
+ })) {
139248
+ if (event.type === "text_delta")
139249
+ out += event.delta;
139250
+ if (event.type === "error")
139251
+ return null;
139252
+ }
139253
+ const trimmed = out.trim();
139254
+ return trimmed.length > 0 ? trimmed : null;
139255
+ } catch {
139256
+ return null;
139257
+ }
139258
+ }
139259
+ function moxxyHome2() {
139260
+ return process.env.MOXXY_HOME ?? join(homedir(), ".moxxy");
139261
+ }
139262
+ function collabRunsDir() {
139263
+ return join(moxxyHome2(), "collab", "runs");
139264
+ }
139265
+ function writeRunRecord(rec) {
139266
+ try {
139267
+ const dir = collabRunsDir();
139268
+ mkdirSync(dir, { recursive: true });
139269
+ writeFileSync(join(dir, `${rec.runId}.json`), `${JSON.stringify(rec, null, 2)}
139270
+ `);
139271
+ } catch {
139272
+ }
139273
+ }
139151
139274
  var IDENTITY = ["-c", "user.name=moxxy-collab", "-c", "user.email=collab@moxxy.local"];
139152
139275
  function git(cwd2, args) {
139153
139276
  return new Promise((resolve13) => {
@@ -139297,6 +139420,7 @@ var PeerSupervisor = class {
139297
139420
  [COLLAB_ENV.Subtask]: args.entry.subtask,
139298
139421
  [COLLAB_ENV.ParentTask]: this.opts.parentTask,
139299
139422
  [COLLAB_ENV.RunnerSocket]: socket,
139423
+ ...args.charterFile ? { [COLLAB_ENV.CharterFile]: args.charterFile } : {},
139300
139424
  MOXXY_SESSION_ID: `${this.opts.coordinatorSessionId}::${args.entry.id}`,
139301
139425
  MOXXY_MODE: args.mode
139302
139426
  };
@@ -139526,14 +139650,22 @@ async function* runCollaborative(ctx, deps) {
139526
139650
  return;
139527
139651
  }
139528
139652
  const runId = collabRunId(String(ctx.sessionId), String(ctx.turnId));
139653
+ const startedAtMs = Date.now();
139529
139654
  const worktrees = /* @__PURE__ */ new Map();
139530
139655
  let hub = null;
139531
139656
  let supervisor = null;
139532
139657
  let unsubscribe = null;
139658
+ let archiveParallel = false;
139659
+ let archiveGitRepo = false;
139660
+ let briefText = "";
139661
+ let mergeForArchive;
139662
+ let completed = false;
139533
139663
  try {
139534
139664
  mkdirSync(collabRunDir(runId), { recursive: true });
139535
139665
  const { installed: gitInstalled2, repo: gitRepo } = await detectGit(cwd2);
139536
139666
  const parallel = cfg.concurrency === "parallel" && gitRepo;
139667
+ archiveParallel = parallel;
139668
+ archiveGitRepo = gitRepo;
139537
139669
  if (!parallel) {
139538
139670
  yield await ctx.emit(plugin4(ctx, "collab_fallback_sequential", {
139539
139671
  reason: !gitInstalled2 ? "git is not installed \u2014 running agents sequentially in your workspace" : !gitRepo ? "this folder is not a git repository \u2014 running agents sequentially in your workspace" : "sequential mode selected"
@@ -139571,9 +139703,24 @@ async function* runCollaborative(ctx, deps) {
139571
139703
  supervisor = (deps.createSupervisor ?? ((o2) => new PeerSupervisor(o2)))(supervisorOpts, hub);
139572
139704
  yield await ctx.emit(plugin4(ctx, "collab_started", { task, parallel, gitInstalled: gitInstalled2, gitRepo }));
139573
139705
  try {
139706
+ const events = ctx.log.slice();
139574
139707
  mkdirSync(join(cwd2, COLLAB_SCAFFOLD_DIR), { recursive: true });
139575
- writeFileSync(join(cwd2, COLLAB_SCAFFOLD_DIR, BRIEF_FILENAME), buildBrief(task, ctx.log.slice()));
139576
- yield await ctx.emit(plugin4(ctx, "collab_brief_written", { path: join(COLLAB_SCAFFOLD_DIR, BRIEF_FILENAME) }));
139708
+ writeFileSync(join(cwd2, COLLAB_SCAFFOLD_DIR, CONVERSATION_FILENAME), buildConversation(task, events));
139709
+ const llmSummary = await summarizeConversation({
139710
+ task,
139711
+ events,
139712
+ provider: ctx.provider,
139713
+ model: ctx.model,
139714
+ signal: ctx.signal
139715
+ }).catch(() => null);
139716
+ const summary = llmSummary ?? heuristicSummary(task, events);
139717
+ briefText = buildBrief(task, summary);
139718
+ writeFileSync(join(cwd2, COLLAB_SCAFFOLD_DIR, BRIEF_FILENAME), briefText);
139719
+ yield await ctx.emit(plugin4(ctx, "collab_brief_written", {
139720
+ path: join(COLLAB_SCAFFOLD_DIR, BRIEF_FILENAME),
139721
+ conversationPath: join(COLLAB_SCAFFOLD_DIR, CONVERSATION_FILENAME),
139722
+ summarized: Boolean(llmSummary)
139723
+ }));
139577
139724
  } catch {
139578
139725
  }
139579
139726
  supervisor.spawn({ entry: architectEntry, cwd: cwd2, mode: COLLAB_ARCHITECT_MODE_NAME });
@@ -139600,8 +139747,12 @@ ${why}` : ""}`));
139600
139747
  if (cfg.requireRosterApproval && ctx.approval) {
139601
139748
  const decision = await ctx.approval.confirm({
139602
139749
  title: `Team of ${roster.length} agent${roster.length === 1 ? "" : "s"} \u2014 review before launch`,
139603
- body: roster.map((r2, i2) => `${i2 + 1}. [${r2.id}] ${r2.name} \u2014 ${r2.role}
139604
- ${r2.subtask}`).join("\n\n"),
139750
+ body: roster.map((r2, i2) => {
139751
+ const charterLine = r2.charter ? `
139752
+ charter: ${r2.charter.replace(/\s+/g, " ").slice(0, 140)}${r2.charter.length > 140 ? "\u2026" : ""}` : "";
139753
+ return `${i2 + 1}. [${r2.id}] ${r2.name} \u2014 ${r2.role}
139754
+ ${r2.subtask}${charterLine}`;
139755
+ }).join("\n\n"),
139605
139756
  kind: "collab.roster",
139606
139757
  defaultOptionId: "launch",
139607
139758
  options: [
@@ -139627,7 +139778,8 @@ ${why}` : ""}`));
139627
139778
  const wt3 = worktreePath(runId, entry.id);
139628
139779
  await addWorktree({ repoCwd: cwd2, path: wt3, branch: collabBranch(runId, entry.id), baseSha });
139629
139780
  worktrees.set(entry.id, wt3);
139630
- supervisor.spawn({ entry, cwd: wt3, mode: COLLAB_PEER_MODE_NAME });
139781
+ const charterFile = writeCharterFile(runId, entry);
139782
+ supervisor.spawn({ entry, cwd: wt3, mode: COLLAB_PEER_MODE_NAME, ...charterFile ? { charterFile } : {} });
139631
139783
  yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
139632
139784
  }
139633
139785
  await waitForAgents(hub, supervisor, roster.map((r2) => r2.id), ctx.signal, cfg.wallClockMs);
@@ -139640,7 +139792,8 @@ ${why}` : ""}`));
139640
139792
  if (ctx.signal.aborted)
139641
139793
  break;
139642
139794
  hub.state.addAgent(entry);
139643
- supervisor.spawn({ entry, cwd: cwd2, mode: COLLAB_PEER_MODE_NAME });
139795
+ const charterFile = writeCharterFile(runId, entry);
139796
+ supervisor.spawn({ entry, cwd: cwd2, mode: COLLAB_PEER_MODE_NAME, ...charterFile ? { charterFile } : {} });
139644
139797
  yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
139645
139798
  const ok = await waitForAgent(hub, supervisor, entry.id, ctx.signal, cfg.wallClockMs);
139646
139799
  if (!ok)
@@ -139665,6 +139818,12 @@ ${why}` : ""}`));
139665
139818
  board: hub.state.boardItems(),
139666
139819
  mergePolicy: cfg.mergePolicy
139667
139820
  });
139821
+ mergeForArchive = {
139822
+ merged: result.merged,
139823
+ promoted: result.promoted,
139824
+ conflicts: result.conflicts.length,
139825
+ ...result.stagingBranch ? { stagingBranch: result.stagingBranch } : {}
139826
+ };
139668
139827
  yield await ctx.emit(plugin4(ctx, "collab_merge", result));
139669
139828
  for (const c2 of result.conflicts) {
139670
139829
  yield await ctx.emit(plugin4(ctx, "collab_conflict", c2));
@@ -139678,10 +139837,54 @@ ${why}` : ""}`));
139678
139837
  ${summaryBlock}${mergeNote ? `
139679
139838
 
139680
139839
  ${mergeNote}` : ""}`));
139840
+ completed = true;
139681
139841
  yield await ctx.emit(plugin4(ctx, "collab_completed", { done: doneIds, total: roster.length }));
139682
139842
  } finally {
139683
139843
  if (supervisor)
139684
139844
  await supervisor.shutdownAll("collaboration complete");
139845
+ if (hub) {
139846
+ try {
139847
+ const agents = hub.state.rosterView().agents;
139848
+ const implementers = agents.filter((a2) => a2.role !== "architect");
139849
+ writeRunRecord({
139850
+ runId,
139851
+ task,
139852
+ startedAtMs,
139853
+ finishedAtMs: Date.now(),
139854
+ outcome: completed ? "completed" : ctx.signal.aborted ? "aborted" : "failed",
139855
+ parallel: archiveParallel,
139856
+ gitRepo: archiveGitRepo,
139857
+ agents: agents.map((a2) => ({
139858
+ id: a2.id,
139859
+ name: a2.name,
139860
+ role: a2.role,
139861
+ status: a2.status,
139862
+ subtask: a2.subtask,
139863
+ ...a2.doneSummary ? { doneSummary: a2.doneSummary } : {}
139864
+ })),
139865
+ doneCount: implementers.filter((a2) => a2.status === "done").length,
139866
+ totalCount: implementers.length,
139867
+ board: hub.state.boardItems().map((b3) => ({
139868
+ id: b3.id,
139869
+ title: b3.title,
139870
+ status: b3.status,
139871
+ ...b3.owner ? { owner: b3.owner } : {},
139872
+ ...b3.paths ? { paths: b3.paths } : {}
139873
+ })),
139874
+ contracts: hub.state.contractList().map((c2) => ({
139875
+ id: c2.id,
139876
+ title: c2.title,
139877
+ owner: c2.owner,
139878
+ status: c2.status,
139879
+ version: c2.version
139880
+ })),
139881
+ messageCount: hub.state.allMessages().length,
139882
+ ...mergeForArchive ? { merge: mergeForArchive } : {},
139883
+ ...briefText ? { brief: briefText } : {}
139884
+ });
139885
+ } catch {
139886
+ }
139887
+ }
139685
139888
  if (unsubscribe)
139686
139889
  unsubscribe();
139687
139890
  unregisterActiveHub(String(ctx.sessionId));
@@ -139729,10 +139932,13 @@ function readRoster(path62, maxAgents) {
139729
139932
  out.push({
139730
139933
  id,
139731
139934
  name: typeof r2.name === "string" ? r2.name : id,
139732
- role: "implementer",
139935
+ // Carry the architect's proposed role (pm/designer/developer/qa/writer/…)
139936
+ // so the team is cross-functional, not a pool of identical implementers.
139937
+ role: cleanRole(r2.role),
139733
139938
  subtask: r2.subtask,
139734
139939
  ...Array.isArray(r2.ownedPaths) ? { ownedPaths: r2.ownedPaths.filter((p3) => typeof p3 === "string") } : {},
139735
- ...typeof r2.model === "string" ? { model: r2.model } : {}
139940
+ ...typeof r2.model === "string" ? { model: r2.model } : {},
139941
+ ...typeof r2.charter === "string" && r2.charter.trim() ? { charter: cleanCharter(r2.charter) } : {}
139736
139942
  });
139737
139943
  if (out.length >= maxAgents)
139738
139944
  break;
@@ -139745,6 +139951,29 @@ function readRoster(path62, maxAgents) {
139745
139951
  function slug(s2) {
139746
139952
  return s2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
139747
139953
  }
139954
+ function cleanRole(raw) {
139955
+ if (typeof raw !== "string")
139956
+ return "implementer";
139957
+ const r2 = raw.toLowerCase().replace(/[^a-z0-9 -]/g, "").replace(/\s+/g, " ").trim().slice(0, 24);
139958
+ if (!r2 || r2 === ARCHITECT_AGENT_ID)
139959
+ return "implementer";
139960
+ return r2;
139961
+ }
139962
+ function cleanCharter(raw) {
139963
+ return raw.replace(/\u0000/g, "").trim().slice(0, 2e3);
139964
+ }
139965
+ function writeCharterFile(runId, entry) {
139966
+ if (!entry.charter)
139967
+ return void 0;
139968
+ const p3 = charterFilePath(runId, entry.id);
139969
+ try {
139970
+ writeFileSync(p3, `${entry.charter}
139971
+ `);
139972
+ return p3;
139973
+ } catch {
139974
+ return void 0;
139975
+ }
139976
+ }
139748
139977
  function statusOf(hub, id) {
139749
139978
  return hub.state.rosterView().agents.find((a2) => a2.id === id)?.status;
139750
139979
  }
@@ -139870,7 +140099,7 @@ function emitAbort(ctx, reason) {
139870
140099
  var COLLAB_COMMON = `You are one agent on a TEAM of separate agents collaborating on one task in a shared workspace. You are a peer, not in charge \u2014 you cooperate.
139871
140100
 
139872
140101
  Know the WHOLE picture before you act:
139873
- - ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} is the shared brief \u2014 the user's overall goal and the conversation/intent behind it. Read it FIRST so your work serves the real goal, not just the literal words of your sub-task.
140102
+ - ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} is the shared brief \u2014 a concise summary of the user's goal and the key requirements/constraints/decisions. Read it FIRST so your work serves the real goal, not just the literal words of your sub-task. The full conversation is NOT in your context; if you need a specific detail the brief omits, read or grep ${COLLAB_SCAFFOLD_DIR}/${CONVERSATION_FILENAME} \u2014 do NOT load it wholesale.
139874
140103
  - Before planning, recall() any relevant prior knowledge about this workspace/task. When you discover a durable fact (a decision, a gotcha, an interface, a convention), memory_save it so the team \u2014 and future work \u2014 keeps it.
139875
140104
 
139876
140105
  The team coordinates through a shared hub (use these tools):
@@ -139885,25 +140114,34 @@ The team coordinates through a shared hub (use these tools):
139885
140114
 
139886
140115
  Cooperation rules:
139887
140116
  - Build strictly to the shared contracts. If you must change a shared boundary, use collab_contract_propose_change and wait for acks \u2014 never break a contract unilaterally.
139888
- - The human may step in at any time: honor any directive you receive (it overrides your current plan), and if the team is paused, finish your current edit and wait.
140117
+ - The human may step in at any time. When you receive a HUMAN directive or a message from "human", treat it as authoritative (it overrides your current plan), and REPLY to them with collab_send to "human" \u2014 acknowledge it and say what you'll do (or ask a brief clarifying question). Don't go silent on the human. If the team is paused, finish your current edit and wait.
139889
140118
  - Keep teammates informed: broadcast meaningful progress and blockers.
139890
140119
  - When YOUR sub-task is fully complete and verified, call collab_done with a short summary. The run finishes when everyone is done.`;
139891
140120
  var COLLAB_PEER_PROMPT = `${COLLAB_COMMON}
139892
140121
 
139893
- You are an IMPLEMENTER. Your sub-task is provided. Start by reading ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} (the goal + intent) and ${COLLAB_SCAFFOLD_DIR}/${CONTRACTS_FILENAME}, and calling collab_contracts, collab_roster, and collab_board so you know the plan and who owns what. Claim your files, implement against the contracts, coordinate on intersections, then call collab_done.`;
140122
+ You are a TEAM MEMBER with a specific role (given below) and sub-task. Work as that role \u2014 a writer writes, a designer designs, a developer builds, a QA reviews, a PM sequences + verifies. Start by reading ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} (the goal + intent) and ${COLLAB_SCAFFOLD_DIR}/${CONTRACTS_FILENAME}, and calling collab_contracts, collab_roster, and collab_board so you know the plan and who owns what. Claim your files, deliver your part against the contracts, coordinate on intersections, then call collab_done.`;
139894
140123
  var COLLAB_ARCHITECT_PROMPT = `${COLLAB_COMMON}
139895
140124
 
139896
140125
  You are the ARCHITECT \u2014 you run FIRST and set the team up for success. Your job, in order:
139897
- 0. Read ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} \u2014 the user's goal and the conversation behind it \u2014 so you decompose toward what they actually want.
140126
+ 0. Read ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} \u2014 the goal + key-requirements summary \u2014 so you decompose toward what the user actually wants. If you need a detail it omits, grep ${COLLAB_SCAFFOLD_DIR}/${CONVERSATION_FILENAME}.
139898
140127
  1. Explore the workspace to understand the task and its boundaries.
139899
- 2. Decompose the task into INDEPENDENT sub-tasks with DISJOINT file ownership (minimize overlap so agents rarely touch the same files).
139900
- 3. Define the shared CONTRACTS \u2014 the interfaces, types, API shapes, and module boundaries where the implementers' work meets. Publish each with collab_contract_publish (give an owner + consumers).
140128
+ 2. Assemble the RIGHT TEAM for THIS deliverable and decompose into INDEPENDENT sub-tasks with DISJOINT ownership (minimize overlap). Pick the roles the work actually needs \u2014 a coding task wants developers + a QA reviewer; a document/plan/design deliverable wants a writer, a researcher, a designer, an editor; a product effort may want a PM to sequence + verify. Don't default everyone to a generic "implementer".
140129
+ 3. Define the shared CONTRACTS \u2014 the interfaces, types, API shapes, section outlines, or boundaries where the team's work meets. Publish each with collab_contract_publish (give an owner + consumers).
139901
140130
  4. Write two files into the repo:
139902
- - ${COLLAB_SCAFFOLD_DIR}/${CONTRACTS_FILENAME} \u2014 human-readable contracts/boundaries the implementers must follow.
139903
- - ${COLLAB_SCAFFOLD_DIR}/${ROSTER_FILENAME} \u2014 a JSON array proposing the implementer roster. Each entry: { "id": "kebab-slug", "name": "Role Name", "role": "implementer", "subtask": "what this agent builds", "ownedPaths": ["dir/", "file.ts"] }. Do NOT include yourself.
140131
+ - ${COLLAB_SCAFFOLD_DIR}/${CONTRACTS_FILENAME} \u2014 human-readable contracts/boundaries the team must follow.
140132
+ - ${COLLAB_SCAFFOLD_DIR}/${ROSTER_FILENAME} \u2014 a JSON array proposing the team. Each entry: { "id": "kebab-slug", "name": "Display Name", "role": "<function>", "subtask": "what this agent delivers", "ownedPaths": ["dir/", "file.md"], "charter": "..." }. "role" is the agent's FUNCTION \u2014 e.g. "developer", "designer", "pm", "qa", "writer", "researcher", "editor" \u2014 choose what fits. For EACH agent write a tailored "charter": a short system-prompt-style brief (roughly 4-8 sentences, plain prose in the second person \u2014 "You are \u2026", no markdown headings) giving THIS agent, for THIS task: (a) its persona/expertise, (b) its concrete responsibilities and scope, (c) the quality bar it must hit, (d) how it works with the rest of the team, (e) its definition of done. Make each charter specific to the deliverable \u2014 this is how you create proper, task-suited roles instead of generic workers. Do NOT include yourself, and do NOT use "architect" (that's you).
139904
140133
  5. Broadcast a short kickoff summary, then call collab_done.
139905
140134
 
139906
140135
  After the implementers start, you stay available as the BROKER: answer interface questions, and when an implementer proposes a contract change, review it and (if sound) commit it with collab_contract_update so everyone re-syncs.`;
140136
+ function peerPromptWithCharter(charter) {
140137
+ if (!charter || !charter.trim())
140138
+ return COLLAB_PEER_PROMPT;
140139
+ return `${COLLAB_PEER_PROMPT}
140140
+
140141
+ ## Your charter
140142
+
140143
+ ${charter.trim()}`;
140144
+ }
139907
140145
  var COLLAB_DONE_TOOL = "collab_done";
139908
140146
  var DEFAULT_MAX_ITERATIONS = 60;
139909
140147
  var MAX_NOOP_ITERATIONS = 3;
@@ -140169,11 +140407,21 @@ var collabArchitectMode = defineMode({
140169
140407
  badge: { label: "ARCHITECT", tone: "attention" },
140170
140408
  run: (ctx) => runCollabAgentLoop(ctx, { systemPrompt: COLLAB_ARCHITECT_PROMPT })
140171
140409
  });
140410
+ function peerSystemPrompt(env3) {
140411
+ const path62 = env3[COLLAB_ENV.CharterFile]?.trim();
140412
+ if (!path62)
140413
+ return COLLAB_PEER_PROMPT;
140414
+ try {
140415
+ return peerPromptWithCharter(readFileSync(path62, "utf8"));
140416
+ } catch {
140417
+ return COLLAB_PEER_PROMPT;
140418
+ }
140419
+ }
140172
140420
  var collabPeerMode = defineMode({
140173
140421
  name: COLLAB_PEER_MODE_NAME,
140174
- description: "Collaboration peer (internal): an implementer building to the shared contracts.",
140422
+ description: "Collaboration peer (internal): a team member building to the shared contracts.",
140175
140423
  badge: { label: "TEAM", tone: "attention" },
140176
- run: (ctx) => runCollabAgentLoop(ctx, { systemPrompt: COLLAB_PEER_PROMPT })
140424
+ run: (ctx) => runCollabAgentLoop(ctx, { systemPrompt: peerSystemPrompt(ctx.env) })
140177
140425
  });
140178
140426
 
140179
140427
  // ../mode-collaborative/dist/index.js
@@ -140188,8 +140436,8 @@ var collaborativeModePlugin = definePlugin({
140188
140436
  version: "0.0.0",
140189
140437
  modes: [collaborativeMode, collabArchitectMode, collabPeerMode]
140190
140438
  });
140191
- var MAX_SUMMARIZE_INPUT_CHARS = 48e3;
140192
- var SUMMARY_MAX_TOKENS = 1024;
140439
+ var MAX_SUMMARIZE_INPUT_CHARS2 = 48e3;
140440
+ var SUMMARY_MAX_TOKENS2 = 1024;
140193
140441
  var FALLBACK_DIGEST_CHARS = 6e3;
140194
140442
  var SUMMARY_SYSTEM_PROMPT = "You compress conversation history for an AI agent so it can keep working with less context. You are given a line-per-event digest of earlier turns. Write a dense, factual brief the agent can rely on: the task and its current state, key decisions and their reasons, important file paths / identifiers / values, tool outcomes (including failures), and any unresolved questions or TODOs. Do not editorialize, do not invent details, output ONLY the summary text.";
140195
140443
  function createSummarizeCompactor(opts = {}) {
@@ -140256,9 +140504,9 @@ async function providerSummary(text, ctx) {
140256
140504
  const model = ctx.model ?? provider.models[0]?.id;
140257
140505
  if (!model)
140258
140506
  return null;
140259
- const input = text.length > MAX_SUMMARIZE_INPUT_CHARS ? `${text.slice(0, MAX_SUMMARIZE_INPUT_CHARS / 2)}
140507
+ const input = text.length > MAX_SUMMARIZE_INPUT_CHARS2 ? `${text.slice(0, MAX_SUMMARIZE_INPUT_CHARS2 / 2)}
140260
140508
  [... digest truncated ...]
140261
- ${text.slice(-MAX_SUMMARIZE_INPUT_CHARS / 2)}` : text;
140509
+ ${text.slice(-MAX_SUMMARIZE_INPUT_CHARS2 / 2)}` : text;
140262
140510
  try {
140263
140511
  let out = "";
140264
140512
  for await (const event of provider.stream({
@@ -140272,7 +140520,7 @@ ${text.slice(-MAX_SUMMARIZE_INPUT_CHARS / 2)}` : text;
140272
140520
  ${input}` }]
140273
140521
  }
140274
140522
  ],
140275
- maxTokens: SUMMARY_MAX_TOKENS,
140523
+ maxTokens: SUMMARY_MAX_TOKENS2,
140276
140524
  ...ctx.signal ? { signal: ctx.signal } : {}
140277
140525
  })) {
140278
140526
  if (event.type === "text_delta")
@@ -149574,13 +149822,13 @@ async function runWorkflow(workflow, deps, opts = {}) {
149574
149822
  const startedAt = (deps.now ?? Date.now)();
149575
149823
  const result = await executor.run(workflow, deps);
149576
149824
  if (opts.recordDir !== null) {
149577
- await writeRunRecord(workflow, result, startedAt, executor.name, deps, opts.recordDir ?? defaultRunRecordDir()).catch((err) => deps.logger?.warn?.("workflow: failed to write run record", {
149825
+ await writeRunRecord2(workflow, result, startedAt, executor.name, deps, opts.recordDir ?? defaultRunRecordDir()).catch((err) => deps.logger?.warn?.("workflow: failed to write run record", {
149578
149826
  error: err instanceof Error ? err.message : String(err)
149579
149827
  }));
149580
149828
  }
149581
149829
  return result;
149582
149830
  }
149583
- async function writeRunRecord(workflow, result, startedAt, executorName, deps, dir) {
149831
+ async function writeRunRecord2(workflow, result, startedAt, executorName, deps, dir) {
149584
149832
  await promises.mkdir(dir, { recursive: true });
149585
149833
  const stamp = new Date(startedAt).toISOString().replace(/[:.]/g, "-");
149586
149834
  const file = path3.join(dir, `${stamp}-${workflow.name}-${ulid().slice(-6)}.jsonl`);
@@ -153961,11 +154209,11 @@ function servicePlatform() {
153961
154209
  if (process.platform === "linux") return "linux";
153962
154210
  return "unsupported";
153963
154211
  }
153964
- function moxxyHome2() {
154212
+ function moxxyHome3() {
153965
154213
  return process.env.MOXXY_HOME ?? path3__default.join(homedir(), ".moxxy");
153966
154214
  }
153967
154215
  function serviceLogPath(spec) {
153968
- return path3__default.join(moxxyHome2(), "services", `${spec.id}.log`);
154216
+ return path3__default.join(moxxyHome3(), "services", `${spec.id}.log`);
153969
154217
  }
153970
154218
  function nodeBin() {
153971
154219
  return process.execPath;
@@ -156051,13 +156299,15 @@ async function runAgentCommand(argv) {
156051
156299
  }
156052
156300
  function buildSeedTurn(args) {
156053
156301
  const { role, parentTask, subtask } = args;
156054
- const pointer = "Shared team context is in `.moxxy-collab/BRIEF.md` (the user's overall goal + the conversation/intent) and `.moxxy-collab/CONTRACTS.md` (the agreed interfaces). Read them before you start so your work fits the real goal.";
156302
+ const pointer = "Shared team context is in `.moxxy-collab/BRIEF.md` (a concise summary of the user's goal + key requirements) and `.moxxy-collab/CONTRACTS.md` (the agreed interfaces). Read them before you start so your work fits the real goal. If you need a detail the brief omits, read or grep `.moxxy-collab/CONVERSATION.md` (the full transcript) \u2014 do not load it wholesale.";
156055
156303
  if (role === "architect" || !parentTask || parentTask === subtask) {
156056
156304
  return subtask ? `${subtask}
156057
156305
 
156058
156306
  ${pointer}` : pointer;
156059
156307
  }
156060
- return `Overall team goal: ${parentTask}
156308
+ const roleLine = role && role !== "implementer" ? `Your role on the team: ${role}.
156309
+ ` : "";
156310
+ return `${roleLine}Overall team goal: ${parentTask}
156061
156311
 
156062
156312
  Your sub-task: ${subtask}
156063
156313