@moxxy/cli 0.14.6 → 0.14.8
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 +271 -59
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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';
|
|
@@ -136060,6 +136060,7 @@ var COLLAB_SCAFFOLD_DIR = ".moxxy-collab";
|
|
|
136060
136060
|
var CONTRACTS_FILENAME = "CONTRACTS.md";
|
|
136061
136061
|
var ROSTER_FILENAME = "roster.json";
|
|
136062
136062
|
var BRIEF_FILENAME = "BRIEF.md";
|
|
136063
|
+
var CONVERSATION_FILENAME = "CONVERSATION.md";
|
|
136063
136064
|
function collabRunId(sessionId, turnId) {
|
|
136064
136065
|
const tail = (s2) => s2.replace(/[^a-zA-Z0-9]/g, "").slice(-6) || "x";
|
|
136065
136066
|
return `${tail(sessionId)}-${tail(turnId)}`;
|
|
@@ -136073,6 +136074,9 @@ function hubSocketPath(runId) {
|
|
|
136073
136074
|
function peerSocketPath(runId, agentId) {
|
|
136074
136075
|
return join(collabRunDir(runId), `p-${agentId}.sock`);
|
|
136075
136076
|
}
|
|
136077
|
+
function charterFilePath(runId, agentId) {
|
|
136078
|
+
return join(collabRunDir(runId), `charter-${agentId}.md`);
|
|
136079
|
+
}
|
|
136076
136080
|
function worktreeRoot(runId) {
|
|
136077
136081
|
return join(tmpdir(), "moxxy-collab", runId);
|
|
136078
136082
|
}
|
|
@@ -138250,7 +138254,9 @@ var COLLAB_ENV = {
|
|
|
138250
138254
|
Role: "MOXXY_COLLAB_ROLE",
|
|
138251
138255
|
Subtask: "MOXXY_COLLAB_SUBTASK",
|
|
138252
138256
|
ParentTask: "MOXXY_COLLAB_PARENT_TASK",
|
|
138253
|
-
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"
|
|
138254
138260
|
};
|
|
138255
138261
|
var clientPromise = null;
|
|
138256
138262
|
function getProcessHubClient() {
|
|
@@ -139103,7 +139109,7 @@ function resolveCollabConfig(persisted, overrides) {
|
|
|
139103
139109
|
return {
|
|
139104
139110
|
...DEFAULT_COLLAB_CONFIG,
|
|
139105
139111
|
...stripUndefined(fromPrefs),
|
|
139106
|
-
...stripUndefined({})
|
|
139112
|
+
...stripUndefined(overrides ?? {})
|
|
139107
139113
|
};
|
|
139108
139114
|
}
|
|
139109
139115
|
function stripUndefined(obj) {
|
|
@@ -139111,6 +139117,9 @@ function stripUndefined(obj) {
|
|
|
139111
139117
|
}
|
|
139112
139118
|
var MAX_MSG_CHARS = 600;
|
|
139113
139119
|
var MAX_TOTAL_CHARS = 6e3;
|
|
139120
|
+
var CONVERSATION_MSG_CHARS = 1200;
|
|
139121
|
+
var CONVERSATION_TOTAL_CHARS = 48e3;
|
|
139122
|
+
var SUMMARY_GUARD_CHARS = 4e3;
|
|
139114
139123
|
function clip(s2, n2) {
|
|
139115
139124
|
const t2 = s2.trim().replace(/\s+/g, " ");
|
|
139116
139125
|
return t2.length > n2 ? `${t2.slice(0, n2 - 1)}\u2026` : t2;
|
|
@@ -139127,35 +139136,126 @@ function digestTurns(events) {
|
|
|
139127
139136
|
}
|
|
139128
139137
|
return out;
|
|
139129
139138
|
}
|
|
139130
|
-
function
|
|
139131
|
-
const
|
|
139132
|
-
|
|
139133
|
-
|
|
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) {
|
|
139134
139144
|
const lines = [
|
|
139135
139145
|
"# Collaboration brief",
|
|
139136
139146
|
"",
|
|
139137
|
-
"This is the shared
|
|
139138
|
-
"
|
|
139139
|
-
"
|
|
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.",
|
|
139140
139183
|
"",
|
|
139141
139184
|
"## Goal",
|
|
139142
139185
|
"",
|
|
139143
|
-
clip(task, 1500) || "(no goal text)"
|
|
139186
|
+
clip(task, 1500) || "(no goal text)",
|
|
139187
|
+
"",
|
|
139188
|
+
"## Conversation",
|
|
139189
|
+
""
|
|
139144
139190
|
];
|
|
139145
|
-
if (
|
|
139146
|
-
lines.push("
|
|
139147
|
-
|
|
139148
|
-
|
|
139149
|
-
lines.push(`- **${
|
|
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)}`);
|
|
139150
139196
|
}
|
|
139151
139197
|
}
|
|
139152
|
-
let
|
|
139153
|
-
if (
|
|
139154
|
-
|
|
139155
|
-
}
|
|
139156
|
-
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}
|
|
139157
139202
|
`;
|
|
139158
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
|
+
}
|
|
139159
139259
|
function moxxyHome2() {
|
|
139160
139260
|
return process.env.MOXXY_HOME ?? join(homedir(), ".moxxy");
|
|
139161
139261
|
}
|
|
@@ -139192,6 +139292,16 @@ async function detectGit(cwd2) {
|
|
|
139192
139292
|
const repo = installed ? await isGitRepo(cwd2) : false;
|
|
139193
139293
|
return { installed, repo };
|
|
139194
139294
|
}
|
|
139295
|
+
async function tryInitGitRepo(cwd2) {
|
|
139296
|
+
let r2 = await git(cwd2, ["init", "-b", "main"]);
|
|
139297
|
+
if (r2.code !== 0)
|
|
139298
|
+
r2 = await git(cwd2, ["init"]);
|
|
139299
|
+
if (r2.code !== 0 || !await isGitRepo(cwd2))
|
|
139300
|
+
return false;
|
|
139301
|
+
await git(cwd2, ["add", "-A"]);
|
|
139302
|
+
const c2 = await git(cwd2, [...IDENTITY, "commit", "--allow-empty", "-m", "moxxy-collab: initial snapshot", "--no-verify"]);
|
|
139303
|
+
return c2.code === 0 && (await headSha(cwd2)).length > 0;
|
|
139304
|
+
}
|
|
139195
139305
|
async function headSha(cwd2) {
|
|
139196
139306
|
return (await git(cwd2, ["rev-parse", "HEAD"])).stdout.trim();
|
|
139197
139307
|
}
|
|
@@ -139297,6 +139407,19 @@ function peerReaderFor(worktrees, baseSha) {
|
|
|
139297
139407
|
}
|
|
139298
139408
|
};
|
|
139299
139409
|
}
|
|
139410
|
+
function cwdPeerReader(cwd2) {
|
|
139411
|
+
return {
|
|
139412
|
+
async files() {
|
|
139413
|
+
return await isGitRepo(cwd2) ? changedFiles(cwd2) : [];
|
|
139414
|
+
},
|
|
139415
|
+
async read(_agentId, path62) {
|
|
139416
|
+
return readFile(resolveWithin(cwd2, path62), "utf8");
|
|
139417
|
+
},
|
|
139418
|
+
async diff() {
|
|
139419
|
+
return "";
|
|
139420
|
+
}
|
|
139421
|
+
};
|
|
139422
|
+
}
|
|
139300
139423
|
var FORCE_KILL_GRACE_MS = 4e3;
|
|
139301
139424
|
var STDERR_RING = 40;
|
|
139302
139425
|
var PeerSupervisor = class {
|
|
@@ -139320,6 +139443,7 @@ var PeerSupervisor = class {
|
|
|
139320
139443
|
[COLLAB_ENV.Subtask]: args.entry.subtask,
|
|
139321
139444
|
[COLLAB_ENV.ParentTask]: this.opts.parentTask,
|
|
139322
139445
|
[COLLAB_ENV.RunnerSocket]: socket,
|
|
139446
|
+
...args.charterFile ? { [COLLAB_ENV.CharterFile]: args.charterFile } : {},
|
|
139323
139447
|
MOXXY_SESSION_ID: `${this.opts.coordinatorSessionId}::${args.entry.id}`,
|
|
139324
139448
|
MOXXY_MODE: args.mode
|
|
139325
139449
|
};
|
|
@@ -139535,7 +139659,7 @@ async function* runCollaborative(ctx, deps) {
|
|
|
139535
139659
|
yield await ctx.emit(emitAbort(ctx, "aborted before collaboration start"));
|
|
139536
139660
|
return;
|
|
139537
139661
|
}
|
|
139538
|
-
const cfg = deps.config ?? resolveCollabConfig();
|
|
139662
|
+
const cfg = deps.config ?? resolveCollabConfig(void 0, deps.concurrencyOverride ? { concurrency: deps.concurrencyOverride } : void 0);
|
|
139539
139663
|
const cwd2 = deps.cwd ?? process.cwd();
|
|
139540
139664
|
const task = lastUserPromptText(ctx) ?? "";
|
|
139541
139665
|
if (!task.trim()) {
|
|
@@ -139561,17 +139685,22 @@ async function* runCollaborative(ctx, deps) {
|
|
|
139561
139685
|
let completed = false;
|
|
139562
139686
|
try {
|
|
139563
139687
|
mkdirSync(collabRunDir(runId), { recursive: true });
|
|
139564
|
-
const { installed: gitInstalled2, repo:
|
|
139565
|
-
|
|
139566
|
-
|
|
139688
|
+
const { installed: gitInstalled2, repo: alreadyGitRepo } = await (deps.detectGit ?? detectGit)(cwd2);
|
|
139689
|
+
let gitRepo = alreadyGitRepo;
|
|
139690
|
+
if (!gitRepo && gitInstalled2 && cfg.concurrency !== "sequential") {
|
|
139691
|
+
gitRepo = await tryInitGitRepo(cwd2).catch(() => false);
|
|
139692
|
+
}
|
|
139693
|
+
const execMode = resolveExecMode(cfg, gitRepo);
|
|
139694
|
+
const usesGit = execMode === "git-parallel";
|
|
139695
|
+
const runsParallel = execMode !== "sequential";
|
|
139696
|
+
archiveParallel = runsParallel;
|
|
139567
139697
|
archiveGitRepo = gitRepo;
|
|
139568
|
-
|
|
139569
|
-
|
|
139570
|
-
|
|
139571
|
-
}));
|
|
139698
|
+
yield await ctx.emit(plugin4(ctx, "collab_exec_mode", { mode: execMode, gitInstalled: gitInstalled2, gitRepo }));
|
|
139699
|
+
if (execMode === "sequential") {
|
|
139700
|
+
yield await ctx.emit(plugin4(ctx, "collab_fallback_sequential", { reason: "sequential mode \u2014 one agent at a time" }));
|
|
139572
139701
|
}
|
|
139573
139702
|
let baseSha = "";
|
|
139574
|
-
if (
|
|
139703
|
+
if (usesGit) {
|
|
139575
139704
|
const base2 = await resolveBase(cwd2, { snapshotDirty: true });
|
|
139576
139705
|
baseSha = base2.baseSha;
|
|
139577
139706
|
}
|
|
@@ -139585,7 +139714,9 @@ async function* runCollaborative(ctx, deps) {
|
|
|
139585
139714
|
socketPath: hubSocketPath(runId),
|
|
139586
139715
|
task,
|
|
139587
139716
|
roster: [architectEntry],
|
|
139588
|
-
|
|
139717
|
+
// git: read each agent's worktree. non-git: every agent shares one tree, so
|
|
139718
|
+
// peer-read is just "read the live shared workspace".
|
|
139719
|
+
peerReader: usesGit ? peerReaderFor(worktrees, baseSha) : cwdPeerReader(cwd2)
|
|
139589
139720
|
});
|
|
139590
139721
|
registerActiveHub(String(ctx.sessionId), hub);
|
|
139591
139722
|
unsubscribe = hub.subscribe((e3) => {
|
|
@@ -139600,12 +139731,26 @@ async function* runCollaborative(ctx, deps) {
|
|
|
139600
139731
|
signal: ctx.signal
|
|
139601
139732
|
};
|
|
139602
139733
|
supervisor = (deps.createSupervisor ?? ((o2) => new PeerSupervisor(o2)))(supervisorOpts, hub);
|
|
139603
|
-
yield await ctx.emit(plugin4(ctx, "collab_started", { task, parallel, gitInstalled: gitInstalled2, gitRepo }));
|
|
139734
|
+
yield await ctx.emit(plugin4(ctx, "collab_started", { task, parallel: runsParallel, execMode, gitInstalled: gitInstalled2, gitRepo }));
|
|
139604
139735
|
try {
|
|
139605
|
-
|
|
139736
|
+
const events = ctx.log.slice();
|
|
139606
139737
|
mkdirSync(join(cwd2, COLLAB_SCAFFOLD_DIR), { recursive: true });
|
|
139738
|
+
writeFileSync(join(cwd2, COLLAB_SCAFFOLD_DIR, CONVERSATION_FILENAME), buildConversation(task, events));
|
|
139739
|
+
const llmSummary = await summarizeConversation({
|
|
139740
|
+
task,
|
|
139741
|
+
events,
|
|
139742
|
+
provider: ctx.provider,
|
|
139743
|
+
model: ctx.model,
|
|
139744
|
+
signal: ctx.signal
|
|
139745
|
+
}).catch(() => null);
|
|
139746
|
+
const summary = llmSummary ?? heuristicSummary(task, events);
|
|
139747
|
+
briefText = buildBrief(task, summary);
|
|
139607
139748
|
writeFileSync(join(cwd2, COLLAB_SCAFFOLD_DIR, BRIEF_FILENAME), briefText);
|
|
139608
|
-
yield await ctx.emit(plugin4(ctx, "collab_brief_written", {
|
|
139749
|
+
yield await ctx.emit(plugin4(ctx, "collab_brief_written", {
|
|
139750
|
+
path: join(COLLAB_SCAFFOLD_DIR, BRIEF_FILENAME),
|
|
139751
|
+
conversationPath: join(COLLAB_SCAFFOLD_DIR, CONVERSATION_FILENAME),
|
|
139752
|
+
summarized: Boolean(llmSummary)
|
|
139753
|
+
}));
|
|
139609
139754
|
} catch {
|
|
139610
139755
|
}
|
|
139611
139756
|
supervisor.spawn({ entry: architectEntry, cwd: cwd2, mode: COLLAB_ARCHITECT_MODE_NAME });
|
|
@@ -139632,8 +139777,12 @@ ${why}` : ""}`));
|
|
|
139632
139777
|
if (cfg.requireRosterApproval && ctx.approval) {
|
|
139633
139778
|
const decision = await ctx.approval.confirm({
|
|
139634
139779
|
title: `Team of ${roster.length} agent${roster.length === 1 ? "" : "s"} \u2014 review before launch`,
|
|
139635
|
-
body: roster.map((r2, i2) =>
|
|
139636
|
-
|
|
139780
|
+
body: roster.map((r2, i2) => {
|
|
139781
|
+
const charterLine = r2.charter ? `
|
|
139782
|
+
charter: ${r2.charter.replace(/\s+/g, " ").slice(0, 140)}${r2.charter.length > 140 ? "\u2026" : ""}` : "";
|
|
139783
|
+
return `${i2 + 1}. [${r2.id}] ${r2.name} \u2014 ${r2.role}
|
|
139784
|
+
${r2.subtask}${charterLine}`;
|
|
139785
|
+
}).join("\n\n"),
|
|
139637
139786
|
kind: "collab.roster",
|
|
139638
139787
|
defaultOptionId: "launch",
|
|
139639
139788
|
options: [
|
|
@@ -139647,19 +139796,38 @@ ${why}` : ""}`));
|
|
|
139647
139796
|
}
|
|
139648
139797
|
}
|
|
139649
139798
|
yield await ctx.emit(plugin4(ctx, "collab_roster_confirmed", { roster }));
|
|
139650
|
-
if (
|
|
139799
|
+
if (usesGit) {
|
|
139651
139800
|
await commitAll(cwd2, "moxxy-collab: scaffold contracts");
|
|
139652
139801
|
baseSha = await headSha(cwd2);
|
|
139653
139802
|
mkdirSync(worktreeRoot(runId), { recursive: true });
|
|
139654
139803
|
}
|
|
139655
139804
|
const doneIds = [];
|
|
139656
|
-
if (parallel) {
|
|
139805
|
+
if (execMode === "git-parallel") {
|
|
139657
139806
|
for (const entry of roster) {
|
|
139658
139807
|
hub.state.addAgent(entry);
|
|
139659
139808
|
const wt3 = worktreePath(runId, entry.id);
|
|
139660
139809
|
await addWorktree({ repoCwd: cwd2, path: wt3, branch: collabBranch(runId, entry.id), baseSha });
|
|
139661
139810
|
worktrees.set(entry.id, wt3);
|
|
139662
|
-
|
|
139811
|
+
const charterFile = writeCharterFile(runId, entry);
|
|
139812
|
+
supervisor.spawn({ entry, cwd: wt3, mode: COLLAB_PEER_MODE_NAME, ...charterFile ? { charterFile } : {} });
|
|
139813
|
+
yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
|
|
139814
|
+
}
|
|
139815
|
+
await waitForAgents(hub, supervisor, roster.map((r2) => r2.id), ctx.signal, cfg.wallClockMs);
|
|
139816
|
+
yield* surfaceFailures(ctx, hub, supervisor, roster.map((r2) => r2.id));
|
|
139817
|
+
for (const r2 of roster)
|
|
139818
|
+
if (statusOf(hub, r2.id) === "done")
|
|
139819
|
+
doneIds.push(r2.id);
|
|
139820
|
+
} else if (execMode === "cwd-parallel") {
|
|
139821
|
+
for (const entry of roster) {
|
|
139822
|
+
hub.state.addAgent(entry);
|
|
139823
|
+
if (entry.ownedPaths?.length) {
|
|
139824
|
+
const res = hub.state.boardClaim(entry.id, entry.ownedPaths);
|
|
139825
|
+
if (!res.ok) {
|
|
139826
|
+
yield await ctx.emit(plugin4(ctx, "collab_ownership_overlap", { id: entry.id, paths: entry.ownedPaths, ownedBy: res.ownedBy }));
|
|
139827
|
+
}
|
|
139828
|
+
}
|
|
139829
|
+
const charterFile = writeCharterFile(runId, entry);
|
|
139830
|
+
supervisor.spawn({ entry, cwd: cwd2, mode: COLLAB_PEER_MODE_NAME, ...charterFile ? { charterFile } : {} });
|
|
139663
139831
|
yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
|
|
139664
139832
|
}
|
|
139665
139833
|
await waitForAgents(hub, supervisor, roster.map((r2) => r2.id), ctx.signal, cfg.wallClockMs);
|
|
@@ -139672,7 +139840,8 @@ ${why}` : ""}`));
|
|
|
139672
139840
|
if (ctx.signal.aborted)
|
|
139673
139841
|
break;
|
|
139674
139842
|
hub.state.addAgent(entry);
|
|
139675
|
-
|
|
139843
|
+
const charterFile = writeCharterFile(runId, entry);
|
|
139844
|
+
supervisor.spawn({ entry, cwd: cwd2, mode: COLLAB_PEER_MODE_NAME, ...charterFile ? { charterFile } : {} });
|
|
139676
139845
|
yield await ctx.emit(plugin4(ctx, "collab_agent_spawned", { id: entry.id, role: entry.role }));
|
|
139677
139846
|
const ok = await waitForAgent(hub, supervisor, entry.id, ctx.signal, cfg.wallClockMs);
|
|
139678
139847
|
if (!ok)
|
|
@@ -139687,7 +139856,7 @@ ${why}` : ""}`));
|
|
|
139687
139856
|
return;
|
|
139688
139857
|
}
|
|
139689
139858
|
let mergeNote = "";
|
|
139690
|
-
if (
|
|
139859
|
+
if (usesGit && doneIds.length > 0) {
|
|
139691
139860
|
const result = await integrate({
|
|
139692
139861
|
repoCwd: cwd2,
|
|
139693
139862
|
runId,
|
|
@@ -139711,11 +139880,13 @@ ${why}` : ""}`));
|
|
|
139711
139880
|
}
|
|
139712
139881
|
const summaries = hub.state.doneSummaries();
|
|
139713
139882
|
const summaryBlock = summaries.length ? summaries.map((s2) => `- **${s2.agentId}**: ${s2.summary}`).join("\n") : "(no agent reported a completion summary)";
|
|
139883
|
+
const cwdNote = execMode === "cwd-parallel" ? "The team worked together directly in your workspace; please review the result." : "";
|
|
139884
|
+
const tail = [mergeNote, cwdNote].filter(Boolean).join("\n\n");
|
|
139714
139885
|
yield await ctx.emit(assistant(ctx, `Collaboration complete \u2014 ${doneIds.length}/${roster.length} agents finished.
|
|
139715
139886
|
|
|
139716
|
-
${summaryBlock}${
|
|
139887
|
+
${summaryBlock}${tail ? `
|
|
139717
139888
|
|
|
139718
|
-
${
|
|
139889
|
+
${tail}` : ""}`));
|
|
139719
139890
|
completed = true;
|
|
139720
139891
|
yield await ctx.emit(plugin4(ctx, "collab_completed", { done: doneIds, total: roster.length }));
|
|
139721
139892
|
} finally {
|
|
@@ -139816,7 +139987,8 @@ function readRoster(path62, maxAgents) {
|
|
|
139816
139987
|
role: cleanRole(r2.role),
|
|
139817
139988
|
subtask: r2.subtask,
|
|
139818
139989
|
...Array.isArray(r2.ownedPaths) ? { ownedPaths: r2.ownedPaths.filter((p3) => typeof p3 === "string") } : {},
|
|
139819
|
-
...typeof r2.model === "string" ? { model: r2.model } : {}
|
|
139990
|
+
...typeof r2.model === "string" ? { model: r2.model } : {},
|
|
139991
|
+
...typeof r2.charter === "string" && r2.charter.trim() ? { charter: cleanCharter(r2.charter) } : {}
|
|
139820
139992
|
});
|
|
139821
139993
|
if (out.length >= maxAgents)
|
|
139822
139994
|
break;
|
|
@@ -139829,6 +140001,11 @@ function readRoster(path62, maxAgents) {
|
|
|
139829
140001
|
function slug(s2) {
|
|
139830
140002
|
return s2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
139831
140003
|
}
|
|
140004
|
+
function resolveExecMode(cfg, gitRepo) {
|
|
140005
|
+
if (cfg.concurrency === "sequential")
|
|
140006
|
+
return "sequential";
|
|
140007
|
+
return gitRepo ? "git-parallel" : "cwd-parallel";
|
|
140008
|
+
}
|
|
139832
140009
|
function cleanRole(raw) {
|
|
139833
140010
|
if (typeof raw !== "string")
|
|
139834
140011
|
return "implementer";
|
|
@@ -139837,6 +140014,21 @@ function cleanRole(raw) {
|
|
|
139837
140014
|
return "implementer";
|
|
139838
140015
|
return r2;
|
|
139839
140016
|
}
|
|
140017
|
+
function cleanCharter(raw) {
|
|
140018
|
+
return raw.replace(/\u0000/g, "").trim().slice(0, 2e3);
|
|
140019
|
+
}
|
|
140020
|
+
function writeCharterFile(runId, entry) {
|
|
140021
|
+
if (!entry.charter)
|
|
140022
|
+
return void 0;
|
|
140023
|
+
const p3 = charterFilePath(runId, entry.id);
|
|
140024
|
+
try {
|
|
140025
|
+
writeFileSync(p3, `${entry.charter}
|
|
140026
|
+
`);
|
|
140027
|
+
return p3;
|
|
140028
|
+
} catch {
|
|
140029
|
+
return void 0;
|
|
140030
|
+
}
|
|
140031
|
+
}
|
|
139840
140032
|
function statusOf(hub, id) {
|
|
139841
140033
|
return hub.state.rosterView().agents.find((a2) => a2.id === id)?.status;
|
|
139842
140034
|
}
|
|
@@ -139962,7 +140154,7 @@ function emitAbort(ctx, reason) {
|
|
|
139962
140154
|
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.
|
|
139963
140155
|
|
|
139964
140156
|
Know the WHOLE picture before you act:
|
|
139965
|
-
- ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} is the shared brief \u2014 the user's
|
|
140157
|
+
- ${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.
|
|
139966
140158
|
- 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.
|
|
139967
140159
|
|
|
139968
140160
|
The team coordinates through a shared hub (use these tools):
|
|
@@ -139971,12 +140163,13 @@ The team coordinates through a shared hub (use these tools):
|
|
|
139971
140163
|
- collab_send / collab_broadcast \u2014 message a teammate by id, or the whole team.
|
|
139972
140164
|
- collab_board / collab_add_task / collab_update \u2014 the shared task board (what's done / in progress / blocked).
|
|
139973
140165
|
- collab_contracts \u2014 the agreed interfaces/boundaries you must build to.
|
|
139974
|
-
- collab_claim(paths) \u2014
|
|
139975
|
-
- collab_release \u2014 release a claim
|
|
139976
|
-
- collab_peer_read / collab_peer_files / collab_peer_diff \u2014 read a teammate's ACTUAL in-progress work (get their real interface instead of guessing).
|
|
140166
|
+
- collab_claim(paths) \u2014 you may SHARE ONE LIVE WORKSPACE with the other agents (no isolation), so you MUST collab_claim a file BEFORE every edit and only edit files you currently own. If a claim is REJECTED another agent owns it: collab_send that owner, agree who edits, and do NOT touch it until you own it. Claim the NARROWEST set you need (single files, not whole dirs) so you don't block teammates. If you RENAME or MOVE a file, claim BOTH the old and the new path first.
|
|
140167
|
+
- collab_release \u2014 release a claim the moment you finish a file so others can proceed.
|
|
140168
|
+
- collab_peer_read / collab_peer_files / collab_peer_diff \u2014 read a teammate's ACTUAL in-progress work (get their real interface instead of guessing). Only rely on work a teammate has said is done \u2014 a file may be mid-write.
|
|
139977
140169
|
|
|
139978
140170
|
Cooperation rules:
|
|
139979
140171
|
- 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.
|
|
140172
|
+
- Shared/aggregator files (package.json, a lockfile, a barrel index, a shared types file) have ONE owner \u2014 never edit one you don't own; ask its owner via collab_send to make the change for you.
|
|
139980
140173
|
- 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.
|
|
139981
140174
|
- Keep teammates informed: broadcast meaningful progress and blockers.
|
|
139982
140175
|
- When YOUR sub-task is fully complete and verified, call collab_done with a short summary. The run finishes when everyone is done.`;
|
|
@@ -139986,16 +140179,25 @@ You are a TEAM MEMBER with a specific role (given below) and sub-task. Work as t
|
|
|
139986
140179
|
var COLLAB_ARCHITECT_PROMPT = `${COLLAB_COMMON}
|
|
139987
140180
|
|
|
139988
140181
|
You are the ARCHITECT \u2014 you run FIRST and set the team up for success. Your job, in order:
|
|
139989
|
-
0. Read ${COLLAB_SCAFFOLD_DIR}/${BRIEF_FILENAME} \u2014 the
|
|
140182
|
+
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}.
|
|
139990
140183
|
1. Explore the workspace to understand the task and its boundaries.
|
|
139991
|
-
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".
|
|
140184
|
+
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". The team may run together in ONE shared workspace with only advisory file locks, so ownedPaths MUST be DISJOINT \u2014 give each agent its own specific files/leaf dirs with NO overlap. If two roles would both need one shared file (a router, a barrel index, package.json), give it to ONE owner and have the other coordinate via collab_send or a contract; never assign the same path to two agents. Every roster entry MUST declare ownedPaths.
|
|
139992
140185
|
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).
|
|
139993
140186
|
4. Write two files into the repo:
|
|
139994
140187
|
- ${COLLAB_SCAFFOLD_DIR}/${CONTRACTS_FILENAME} \u2014 human-readable contracts/boundaries the team must follow.
|
|
139995
|
-
- ${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"] }. "role" is the agent's FUNCTION \u2014 e.g. "developer", "designer", "pm", "qa", "writer", "researcher", "editor" \u2014 choose what fits. Do NOT include yourself, and do NOT use "architect" (that's you).
|
|
140188
|
+
- ${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).
|
|
139996
140189
|
5. Broadcast a short kickoff summary, then call collab_done.
|
|
139997
140190
|
|
|
139998
140191
|
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.`;
|
|
140192
|
+
function peerPromptWithCharter(charter) {
|
|
140193
|
+
if (!charter || !charter.trim())
|
|
140194
|
+
return COLLAB_PEER_PROMPT;
|
|
140195
|
+
return `${COLLAB_PEER_PROMPT}
|
|
140196
|
+
|
|
140197
|
+
## Your charter
|
|
140198
|
+
|
|
140199
|
+
${charter.trim()}`;
|
|
140200
|
+
}
|
|
139999
140201
|
var COLLAB_DONE_TOOL = "collab_done";
|
|
140000
140202
|
var DEFAULT_MAX_ITERATIONS = 60;
|
|
140001
140203
|
var MAX_NOOP_ITERATIONS = 3;
|
|
@@ -140261,11 +140463,21 @@ var collabArchitectMode = defineMode({
|
|
|
140261
140463
|
badge: { label: "ARCHITECT", tone: "attention" },
|
|
140262
140464
|
run: (ctx) => runCollabAgentLoop(ctx, { systemPrompt: COLLAB_ARCHITECT_PROMPT })
|
|
140263
140465
|
});
|
|
140466
|
+
function peerSystemPrompt(env3) {
|
|
140467
|
+
const path62 = env3[COLLAB_ENV.CharterFile]?.trim();
|
|
140468
|
+
if (!path62)
|
|
140469
|
+
return COLLAB_PEER_PROMPT;
|
|
140470
|
+
try {
|
|
140471
|
+
return peerPromptWithCharter(readFileSync(path62, "utf8"));
|
|
140472
|
+
} catch {
|
|
140473
|
+
return COLLAB_PEER_PROMPT;
|
|
140474
|
+
}
|
|
140475
|
+
}
|
|
140264
140476
|
var collabPeerMode = defineMode({
|
|
140265
140477
|
name: COLLAB_PEER_MODE_NAME,
|
|
140266
|
-
description: "Collaboration peer (internal):
|
|
140478
|
+
description: "Collaboration peer (internal): a team member building to the shared contracts.",
|
|
140267
140479
|
badge: { label: "TEAM", tone: "attention" },
|
|
140268
|
-
run: (ctx) => runCollabAgentLoop(ctx, { systemPrompt:
|
|
140480
|
+
run: (ctx) => runCollabAgentLoop(ctx, { systemPrompt: peerSystemPrompt(ctx.env) })
|
|
140269
140481
|
});
|
|
140270
140482
|
|
|
140271
140483
|
// ../mode-collaborative/dist/index.js
|
|
@@ -140280,8 +140492,8 @@ var collaborativeModePlugin = definePlugin({
|
|
|
140280
140492
|
version: "0.0.0",
|
|
140281
140493
|
modes: [collaborativeMode, collabArchitectMode, collabPeerMode]
|
|
140282
140494
|
});
|
|
140283
|
-
var
|
|
140284
|
-
var
|
|
140495
|
+
var MAX_SUMMARIZE_INPUT_CHARS2 = 48e3;
|
|
140496
|
+
var SUMMARY_MAX_TOKENS2 = 1024;
|
|
140285
140497
|
var FALLBACK_DIGEST_CHARS = 6e3;
|
|
140286
140498
|
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.";
|
|
140287
140499
|
function createSummarizeCompactor(opts = {}) {
|
|
@@ -140348,9 +140560,9 @@ async function providerSummary(text, ctx) {
|
|
|
140348
140560
|
const model = ctx.model ?? provider.models[0]?.id;
|
|
140349
140561
|
if (!model)
|
|
140350
140562
|
return null;
|
|
140351
|
-
const input = text.length >
|
|
140563
|
+
const input = text.length > MAX_SUMMARIZE_INPUT_CHARS2 ? `${text.slice(0, MAX_SUMMARIZE_INPUT_CHARS2 / 2)}
|
|
140352
140564
|
[... digest truncated ...]
|
|
140353
|
-
${text.slice(-
|
|
140565
|
+
${text.slice(-MAX_SUMMARIZE_INPUT_CHARS2 / 2)}` : text;
|
|
140354
140566
|
try {
|
|
140355
140567
|
let out = "";
|
|
140356
140568
|
for await (const event of provider.stream({
|
|
@@ -140364,7 +140576,7 @@ ${text.slice(-MAX_SUMMARIZE_INPUT_CHARS / 2)}` : text;
|
|
|
140364
140576
|
${input}` }]
|
|
140365
140577
|
}
|
|
140366
140578
|
],
|
|
140367
|
-
maxTokens:
|
|
140579
|
+
maxTokens: SUMMARY_MAX_TOKENS2,
|
|
140368
140580
|
...ctx.signal ? { signal: ctx.signal } : {}
|
|
140369
140581
|
})) {
|
|
140370
140582
|
if (event.type === "text_delta")
|
|
@@ -156143,7 +156355,7 @@ async function runAgentCommand(argv) {
|
|
|
156143
156355
|
}
|
|
156144
156356
|
function buildSeedTurn(args) {
|
|
156145
156357
|
const { role, parentTask, subtask } = args;
|
|
156146
|
-
const pointer = "Shared team context is in `.moxxy-collab/BRIEF.md` (the user's
|
|
156358
|
+
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. You may share ONE live workspace with the other agents (no isolation) \u2014 collab_claim before every edit, edit only what you own, and release when done.";
|
|
156147
156359
|
if (role === "architect" || !parentTask || parentTask === subtask) {
|
|
156148
156360
|
return subtask ? `${subtask}
|
|
156149
156361
|
|