@geteigen/cli 0.0.0-stage → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -2
- package/dist/agent.d.ts +32 -0
- package/dist/agent.js +246 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +33 -0
- package/dist/cli/src/agent.d.ts +24 -0
- package/dist/cli/src/agent.js +263 -0
- package/dist/cli/src/agentTools.d.ts +6 -0
- package/dist/cli/src/agentTools.js +104 -0
- package/dist/cli/src/auth.d.ts +5 -0
- package/dist/cli/src/auth.js +33 -0
- package/dist/cli/src/cliEnv.d.ts +6 -0
- package/dist/cli/src/cliEnv.js +26 -0
- package/dist/cli/src/config.d.ts +11 -0
- package/dist/cli/src/config.js +51 -0
- package/dist/cli/src/cursorAuth.d.ts +10 -0
- package/dist/cli/src/cursorAuth.js +92 -0
- package/dist/cli/src/gitAuthEnv.d.ts +5 -0
- package/dist/cli/src/gitAuthEnv.js +35 -0
- package/dist/cli/src/index.d.ts +2 -0
- package/dist/cli/src/index.js +244 -0
- package/dist/cli/src/listModels.d.ts +14 -0
- package/dist/cli/src/listModels.js +196 -0
- package/dist/cli/src/listSessions.d.ts +9 -0
- package/dist/cli/src/listSessions.js +37 -0
- package/dist/cli/src/machineConfig.d.ts +20 -0
- package/dist/cli/src/machineConfig.js +66 -0
- package/dist/cli/src/pickFolder.d.ts +5 -0
- package/dist/cli/src/pickFolder.js +65 -0
- package/dist/cli/src/proc.d.ts +31 -0
- package/dist/cli/src/proc.js +97 -0
- package/dist/cli/src/service.d.ts +17 -0
- package/dist/cli/src/service.js +255 -0
- package/dist/cli/src/workRoots.d.ts +30 -0
- package/dist/cli/src/workRoots.js +228 -0
- package/dist/cli/src/worker.d.ts +8 -0
- package/dist/cli/src/worker.js +51 -0
- package/dist/cli/src/workerCore.d.ts +47 -0
- package/dist/cli/src/workerCore.js +721 -0
- package/dist/cli/src/workspace.d.ts +10 -0
- package/dist/cli/src/workspace.js +329 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +51 -0
- package/dist/cursorAuth.d.ts +10 -0
- package/dist/cursorAuth.js +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +108 -0
- package/dist/listModels.d.ts +6 -0
- package/dist/listModels.js +62 -0
- package/dist/listSessions.d.ts +9 -0
- package/dist/listSessions.js +37 -0
- package/dist/pickFolder.d.ts +5 -0
- package/dist/pickFolder.js +65 -0
- package/dist/shared/actionDrafts.d.ts +83 -0
- package/dist/shared/actionDrafts.js +169 -0
- package/dist/shared/agentArtifacts.d.ts +49 -0
- package/dist/shared/agentArtifacts.js +169 -0
- package/dist/shared/agentLabel.d.ts +18 -0
- package/dist/shared/agentLabel.js +22 -0
- package/dist/shared/agentMentions.d.ts +38 -0
- package/dist/shared/agentMentions.js +110 -0
- package/dist/shared/agentRepos.d.ts +28 -0
- package/dist/shared/agentRepos.js +60 -0
- package/dist/shared/agentRunStatus.d.ts +12 -0
- package/dist/shared/agentRunStatus.js +17 -0
- package/dist/shared/agentUsage.d.ts +24 -0
- package/dist/shared/agentUsage.js +139 -0
- package/dist/shared/approvals.d.ts +44 -0
- package/dist/shared/approvals.js +97 -0
- package/dist/shared/askEigen.d.ts +190 -0
- package/dist/shared/askEigen.js +76 -0
- package/dist/shared/attribution.d.ts +25 -0
- package/dist/shared/attribution.js +63 -0
- package/dist/shared/automations.d.ts +135 -0
- package/dist/shared/automations.js +553 -0
- package/dist/shared/backends/claudeCode.d.ts +44 -0
- package/dist/shared/backends/claudeCode.js +548 -0
- package/dist/shared/backends/codex.d.ts +15 -0
- package/dist/shared/backends/codex.js +287 -0
- package/dist/shared/backends/cursor.d.ts +111 -0
- package/dist/shared/backends/cursor.js +1216 -0
- package/dist/shared/backends/index.d.ts +18 -0
- package/dist/shared/backends/index.js +58 -0
- package/dist/shared/backends/opencode.d.ts +43 -0
- package/dist/shared/backends/opencode.js +415 -0
- package/dist/shared/backends/types.d.ts +171 -0
- package/dist/shared/backends/types.js +19 -0
- package/dist/shared/backends/usage.d.ts +8 -0
- package/dist/shared/backends/usage.js +71 -0
- package/dist/shared/billingPlans.d.ts +60 -0
- package/dist/shared/billingPlans.js +262 -0
- package/dist/shared/brain.d.ts +353 -0
- package/dist/shared/brain.js +487 -0
- package/dist/shared/clarifyingQuestions.d.ts +13 -0
- package/dist/shared/clarifyingQuestions.js +207 -0
- package/dist/shared/claudeModels.d.ts +10 -0
- package/dist/shared/claudeModels.js +52 -0
- package/dist/shared/cloudBackends.d.ts +21 -0
- package/dist/shared/cloudBackends.js +29 -0
- package/dist/shared/codexModels.d.ts +10 -0
- package/dist/shared/codexModels.js +28 -0
- package/dist/shared/contextWindows.d.ts +2 -0
- package/dist/shared/contextWindows.js +30 -0
- package/dist/shared/cursorMcpConfig.d.ts +14 -0
- package/dist/shared/cursorMcpConfig.js +43 -0
- package/dist/shared/cursorProjectMcp.d.ts +10 -0
- package/dist/shared/cursorProjectMcp.js +42 -0
- package/dist/shared/docs.d.ts +59 -0
- package/dist/shared/docs.js +87 -0
- package/dist/shared/events.d.ts +932 -0
- package/dist/shared/events.js +57 -0
- package/dist/shared/integrations.d.ts +266 -0
- package/dist/shared/integrations.js +449 -0
- package/dist/shared/issueTranscript.d.ts +64 -0
- package/dist/shared/issueTranscript.js +387 -0
- package/dist/shared/issues.d.ts +139 -0
- package/dist/shared/issues.js +217 -0
- package/dist/shared/machines.d.ts +119 -0
- package/dist/shared/machines.js +58 -0
- package/dist/shared/markdownMedia.d.ts +13 -0
- package/dist/shared/markdownMedia.js +41 -0
- package/dist/shared/notificationText.d.ts +6 -0
- package/dist/shared/notificationText.js +38 -0
- package/dist/shared/opencodeModels.d.ts +19 -0
- package/dist/shared/opencodeModels.js +62 -0
- package/dist/shared/opencodeSession.d.ts +7 -0
- package/dist/shared/opencodeSession.js +30 -0
- package/dist/shared/orchestration.d.ts +58 -0
- package/dist/shared/orchestration.js +12 -0
- package/dist/shared/orgs.d.ts +47 -0
- package/dist/shared/orgs.js +49 -0
- package/dist/shared/personalOrg.d.ts +5 -0
- package/dist/shared/personalOrg.js +12 -0
- package/dist/shared/plans.d.ts +3 -0
- package/dist/shared/plans.js +19 -0
- package/dist/shared/providerModels.d.ts +40 -0
- package/dist/shared/providerModels.js +503 -0
- package/dist/shared/roomAttention.d.ts +17 -0
- package/dist/shared/roomAttention.js +36 -0
- package/dist/shared/roomContext.d.ts +120 -0
- package/dist/shared/roomContext.js +133 -0
- package/dist/shared/roomPermissions.d.ts +56 -0
- package/dist/shared/roomPermissions.js +133 -0
- package/dist/shared/sessionSeats.d.ts +29 -0
- package/dist/shared/sessionSeats.js +57 -0
- package/dist/shared/slashCommands.d.ts +39 -0
- package/dist/shared/slashCommands.js +166 -0
- package/dist/shared/space.d.ts +33 -0
- package/dist/shared/space.js +54 -0
- package/dist/shared/spaceFeed.d.ts +78 -0
- package/dist/shared/spaceFeed.js +194 -0
- package/dist/shared/stringifyUnknown.d.ts +1 -0
- package/dist/shared/stringifyUnknown.js +1 -0
- package/dist/shared/swarm.d.ts +320 -0
- package/dist/shared/swarm.js +328 -0
- package/dist/shared/swarmVisual.d.ts +79 -0
- package/dist/shared/swarmVisual.js +212 -0
- package/dist/shared/terminalFilter.d.ts +13 -0
- package/dist/shared/terminalFilter.js +46 -0
- package/dist/shared/toolIntegration.d.ts +56 -0
- package/dist/shared/toolIntegration.js +180 -0
- package/dist/shared/typing.d.ts +9 -0
- package/dist/shared/typing.js +37 -0
- package/dist/shared/uploads.d.ts +31 -0
- package/dist/shared/uploads.js +65 -0
- package/dist/shared/voiceApprovals.d.ts +36 -0
- package/dist/shared/voiceApprovals.js +229 -0
- package/dist/shared/workspace.d.ts +49 -0
- package/dist/shared/workspace.js +53 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +266 -0
- package/package.json +51 -4
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
export const SWARM_STATUSES = [
|
|
2
|
+
"draft",
|
|
3
|
+
"planning",
|
|
4
|
+
"researching",
|
|
5
|
+
"awaiting_approval",
|
|
6
|
+
"building",
|
|
7
|
+
"paused",
|
|
8
|
+
"stopped",
|
|
9
|
+
"done",
|
|
10
|
+
"failed",
|
|
11
|
+
];
|
|
12
|
+
/** Statuses where the runner schedules agent cycles. */
|
|
13
|
+
export const ACTIVE_SWARM_STATUSES = ["planning", "researching", "building"];
|
|
14
|
+
export const TERMINAL_SWARM_STATUSES = ["stopped", "done", "failed"];
|
|
15
|
+
export const SWARM_ROLES = [
|
|
16
|
+
"orchestrator",
|
|
17
|
+
"researcher",
|
|
18
|
+
"critic",
|
|
19
|
+
"ranker",
|
|
20
|
+
"synthesizer",
|
|
21
|
+
"verifier",
|
|
22
|
+
"engineer",
|
|
23
|
+
"integrator",
|
|
24
|
+
];
|
|
25
|
+
/** Roles that write code and can only be spawned in the build phase. */
|
|
26
|
+
export const REPO_ROLES = ["engineer", "integrator"];
|
|
27
|
+
export const WORKER_ROLES = SWARM_ROLES.filter((role) => role !== "orchestrator");
|
|
28
|
+
export const SWARM_TASK_STATUSES = [
|
|
29
|
+
"pending",
|
|
30
|
+
"claimed",
|
|
31
|
+
"done",
|
|
32
|
+
"failed",
|
|
33
|
+
"cancelled",
|
|
34
|
+
];
|
|
35
|
+
export const SWARM_TASK_KINDS = [
|
|
36
|
+
"research",
|
|
37
|
+
"critique",
|
|
38
|
+
"rank",
|
|
39
|
+
"synthesize",
|
|
40
|
+
"verify",
|
|
41
|
+
"build",
|
|
42
|
+
"integrate",
|
|
43
|
+
];
|
|
44
|
+
export const SWARM_POST_KINDS = [
|
|
45
|
+
"finding",
|
|
46
|
+
"question",
|
|
47
|
+
"answer",
|
|
48
|
+
"proposal",
|
|
49
|
+
"critique",
|
|
50
|
+
"decision",
|
|
51
|
+
"directive",
|
|
52
|
+
"progress",
|
|
53
|
+
];
|
|
54
|
+
export const SWARM_BASE_CHANNELS = ["plan", "findings", "questions", "decisions"];
|
|
55
|
+
export const SWARM_HYPOTHESIS_STATUSES = [
|
|
56
|
+
"proposed",
|
|
57
|
+
"promoted",
|
|
58
|
+
"rejected",
|
|
59
|
+
];
|
|
60
|
+
export const SWARM_CRITIQUE_VERDICTS = ["support", "refute", "needs_evidence"];
|
|
61
|
+
export const SWARM_ARTIFACT_KINDS = [
|
|
62
|
+
"report",
|
|
63
|
+
"design",
|
|
64
|
+
"notes",
|
|
65
|
+
"benchmark",
|
|
66
|
+
"data",
|
|
67
|
+
"code_ref",
|
|
68
|
+
"other",
|
|
69
|
+
];
|
|
70
|
+
export const DEFAULT_SWARM_BUDGET_USD = 25;
|
|
71
|
+
export const MAX_SWARM_BUDGET_USD = 2000;
|
|
72
|
+
export const DEFAULT_SWARM_DEADLINE_HOURS = 24;
|
|
73
|
+
export const MAX_SWARM_DEADLINE_HOURS = 24 * 14;
|
|
74
|
+
export const DEFAULT_SWARM_MAX_WORKERS = 5;
|
|
75
|
+
export const MAX_SWARM_WORKERS = 8;
|
|
76
|
+
export const DEFAULT_SWARM_MAX_RUNNING = 3;
|
|
77
|
+
export const MAX_SWARM_RUNNING = 6;
|
|
78
|
+
export const DEFAULT_SWARM_MAX_CYCLES = 200;
|
|
79
|
+
export const MAX_SWARM_CYCLES = 2000;
|
|
80
|
+
export const SWARM_STALL_LIMIT = 2;
|
|
81
|
+
export const SWARM_IDLE_ORCHESTRATOR_LIMIT = 3;
|
|
82
|
+
export const SWARM_MIN_CRITIQUED_HYPOTHESES = 4;
|
|
83
|
+
export const SWARM_BUDGET_WARN_RATIO = 0.8;
|
|
84
|
+
export const MAX_SWARM_TITLE = 160;
|
|
85
|
+
export const MAX_SWARM_GOAL = 12_000;
|
|
86
|
+
export const MAX_SWARM_POST = 8_000;
|
|
87
|
+
export const MAX_SWARM_NOTES = 12_000;
|
|
88
|
+
export const MAX_SWARM_TASK_SPEC = 4_000;
|
|
89
|
+
export const MAX_SWARM_LEDGER_FIELD = 12_000;
|
|
90
|
+
export const MAX_SWARM_ARTIFACT = 200_000;
|
|
91
|
+
export const MAX_SWARM_BRIEF = 3_000;
|
|
92
|
+
export const SWARM_ELO_START = 1200;
|
|
93
|
+
export const SWARM_ELO_K = 32;
|
|
94
|
+
function includes(list, value) {
|
|
95
|
+
return typeof value === "string" && list.includes(value);
|
|
96
|
+
}
|
|
97
|
+
export const isSwarmStatus = (v) => includes(SWARM_STATUSES, v);
|
|
98
|
+
export const isSwarmRole = (v) => includes(SWARM_ROLES, v);
|
|
99
|
+
export const isSwarmTaskKind = (v) => includes(SWARM_TASK_KINDS, v);
|
|
100
|
+
export const isSwarmTaskStatus = (v) => includes(SWARM_TASK_STATUSES, v);
|
|
101
|
+
export const isSwarmPostKind = (v) => includes(SWARM_POST_KINDS, v);
|
|
102
|
+
export const isSwarmArtifactKind = (v) => includes(SWARM_ARTIFACT_KINDS, v);
|
|
103
|
+
export const isSwarmCritiqueVerdict = (v) => includes(SWARM_CRITIQUE_VERDICTS, v);
|
|
104
|
+
export const isSwarmHypothesisStatus = (v) => includes(SWARM_HYPOTHESIS_STATUSES, v);
|
|
105
|
+
export function isActiveSwarmStatus(status) {
|
|
106
|
+
return ACTIVE_SWARM_STATUSES.includes(status);
|
|
107
|
+
}
|
|
108
|
+
export function isTerminalSwarmStatus(status) {
|
|
109
|
+
return TERMINAL_SWARM_STATUSES.includes(status);
|
|
110
|
+
}
|
|
111
|
+
export function canStartSwarm(status) {
|
|
112
|
+
return status === "draft";
|
|
113
|
+
}
|
|
114
|
+
export function canPauseSwarm(status) {
|
|
115
|
+
return isActiveSwarmStatus(status) || status === "awaiting_approval";
|
|
116
|
+
}
|
|
117
|
+
export function canResumeSwarm(status) {
|
|
118
|
+
return status === "paused";
|
|
119
|
+
}
|
|
120
|
+
export function canStopSwarm(status) {
|
|
121
|
+
return !isTerminalSwarmStatus(status);
|
|
122
|
+
}
|
|
123
|
+
export function isSwarmChannel(value) {
|
|
124
|
+
if (typeof value !== "string")
|
|
125
|
+
return false;
|
|
126
|
+
if (SWARM_BASE_CHANNELS.includes(value))
|
|
127
|
+
return true;
|
|
128
|
+
return /^task:[A-Za-z0-9_-]{4,40}$/.test(value);
|
|
129
|
+
}
|
|
130
|
+
export function roleNeedsRepo(role) {
|
|
131
|
+
return REPO_ROLES.includes(role);
|
|
132
|
+
}
|
|
133
|
+
/** When the human attached a repo, every agent gets a checkout so they can read it. */
|
|
134
|
+
export function swarmAgentUsesRepo(swarm, _role) {
|
|
135
|
+
return Boolean(swarm.repoUrl);
|
|
136
|
+
}
|
|
137
|
+
/** Engineers / integrators only exist once the research report is approved. */
|
|
138
|
+
export function roleAllowedInPhase(role, phase) {
|
|
139
|
+
if (role === "orchestrator")
|
|
140
|
+
return false;
|
|
141
|
+
if (roleNeedsRepo(role))
|
|
142
|
+
return phase === "build";
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
export function canEditSwarm(swarm, actor) {
|
|
146
|
+
if (swarm.creatorId === actor.userId)
|
|
147
|
+
return true;
|
|
148
|
+
return actor.orgRole === "owner" || actor.orgRole === "admin";
|
|
149
|
+
}
|
|
150
|
+
function clampNumber(raw, min, max, fallback) {
|
|
151
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
152
|
+
if (!Number.isFinite(n))
|
|
153
|
+
return fallback;
|
|
154
|
+
return Math.min(max, Math.max(min, n));
|
|
155
|
+
}
|
|
156
|
+
export function clampSwarmBudgetUsd(raw) {
|
|
157
|
+
return Math.round(clampNumber(raw, 1, MAX_SWARM_BUDGET_USD, DEFAULT_SWARM_BUDGET_USD) * 100) / 100;
|
|
158
|
+
}
|
|
159
|
+
export function clampSwarmDeadlineHours(raw) {
|
|
160
|
+
return clampNumber(raw, 1, MAX_SWARM_DEADLINE_HOURS, DEFAULT_SWARM_DEADLINE_HOURS);
|
|
161
|
+
}
|
|
162
|
+
export function clampSwarmMaxWorkers(raw) {
|
|
163
|
+
return Math.round(clampNumber(raw, 1, MAX_SWARM_WORKERS, DEFAULT_SWARM_MAX_WORKERS));
|
|
164
|
+
}
|
|
165
|
+
export function clampSwarmMaxRunning(raw) {
|
|
166
|
+
return Math.round(clampNumber(raw, 1, MAX_SWARM_RUNNING, DEFAULT_SWARM_MAX_RUNNING));
|
|
167
|
+
}
|
|
168
|
+
export function clampSwarmMaxCycles(raw) {
|
|
169
|
+
return Math.round(clampNumber(raw, 5, MAX_SWARM_CYCLES, DEFAULT_SWARM_MAX_CYCLES));
|
|
170
|
+
}
|
|
171
|
+
export function nextEloRatings(ratingA, ratingB, outcome, k = SWARM_ELO_K) {
|
|
172
|
+
const expectedA = 1 / (1 + 10 ** ((ratingB - ratingA) / 400));
|
|
173
|
+
const scoreA = outcome === "a" ? 1 : outcome === "b" ? 0 : 0.5;
|
|
174
|
+
const delta = k * (scoreA - expectedA);
|
|
175
|
+
const round = (n) => Math.round(n * 10) / 10;
|
|
176
|
+
return { a: round(ratingA + delta), b: round(ratingB - delta) };
|
|
177
|
+
}
|
|
178
|
+
/** Hard limits the runner enforces before starting any cycle. */
|
|
179
|
+
export function evaluateSwarmLimits(state, now) {
|
|
180
|
+
if (state.spentUsd >= state.budgetUsd)
|
|
181
|
+
return { action: "pause", reason: "budget" };
|
|
182
|
+
if (state.deadlineAt !== null && now >= state.deadlineAt) {
|
|
183
|
+
return { action: "stop", reason: "deadline" };
|
|
184
|
+
}
|
|
185
|
+
if (state.cyclesDone >= state.maxCycles)
|
|
186
|
+
return { action: "stop", reason: "max_cycles" };
|
|
187
|
+
return { action: "continue" };
|
|
188
|
+
}
|
|
189
|
+
export function shouldWarnBudget(spentUsd, budgetUsd) {
|
|
190
|
+
return budgetUsd > 0 && spentUsd >= budgetUsd * SWARM_BUDGET_WARN_RATIO;
|
|
191
|
+
}
|
|
192
|
+
/** A swarm may only finish with a report, enough vetted ideas, and a passing verifier. */
|
|
193
|
+
export function evaluateDoneGate(input) {
|
|
194
|
+
const missing = [];
|
|
195
|
+
const minCritiqued = input.minCritiquedHypotheses ?? SWARM_MIN_CRITIQUED_HYPOTHESES;
|
|
196
|
+
if (!input.hasReport)
|
|
197
|
+
missing.push("a `report` artifact from the synthesizer");
|
|
198
|
+
if (input.critiquedHypotheses < minCritiqued) {
|
|
199
|
+
missing.push(`at least ${minCritiqued} critiqued hypotheses (have ${input.critiquedHypotheses})`);
|
|
200
|
+
}
|
|
201
|
+
if (input.latestVerificationPassed !== true) {
|
|
202
|
+
missing.push("a passing verify_result from the verifier after the latest report");
|
|
203
|
+
}
|
|
204
|
+
if (input.phase === "build" && input.openBuildTasks > 0) {
|
|
205
|
+
missing.push(`${input.openBuildTasks} build task(s) still open`);
|
|
206
|
+
}
|
|
207
|
+
return { ok: missing.length === 0, missing };
|
|
208
|
+
}
|
|
209
|
+
export function taskDependenciesMet(task, byId) {
|
|
210
|
+
return task.blockedBy.every((id) => byId.get(id)?.status === "done");
|
|
211
|
+
}
|
|
212
|
+
export function taskMatchesRole(task, role) {
|
|
213
|
+
if (task.roleHint)
|
|
214
|
+
return task.roleHint === role;
|
|
215
|
+
switch (task.kind) {
|
|
216
|
+
case "research":
|
|
217
|
+
return role === "researcher";
|
|
218
|
+
case "critique":
|
|
219
|
+
return role === "critic";
|
|
220
|
+
case "rank":
|
|
221
|
+
return role === "ranker";
|
|
222
|
+
case "synthesize":
|
|
223
|
+
return role === "synthesizer";
|
|
224
|
+
case "verify":
|
|
225
|
+
return role === "verifier";
|
|
226
|
+
case "build":
|
|
227
|
+
return role === "engineer";
|
|
228
|
+
case "integrate":
|
|
229
|
+
return role === "integrator";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export function extractMentions(body) {
|
|
233
|
+
const out = new Set();
|
|
234
|
+
for (const match of body.matchAll(/(^|\s)@([A-Za-z0-9][A-Za-z0-9_-]{0,39})/g)) {
|
|
235
|
+
out.add(match[2].toLowerCase());
|
|
236
|
+
}
|
|
237
|
+
return [...out];
|
|
238
|
+
}
|
|
239
|
+
export function slugifySwarmLabel(raw, fallback = "agent") {
|
|
240
|
+
const slug = raw
|
|
241
|
+
.toLowerCase()
|
|
242
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
243
|
+
.replace(/^-+|-+$/g, "")
|
|
244
|
+
.slice(0, 32);
|
|
245
|
+
return slug || fallback;
|
|
246
|
+
}
|
|
247
|
+
export function swarmTitleFromGoal(goal) {
|
|
248
|
+
const firstLine = goal.trim().split(/\n/)[0] || "Swarm";
|
|
249
|
+
const clean = firstLine.replace(/\s+/g, " ").trim();
|
|
250
|
+
return clean.length > 80 ? `${clean.slice(0, 77).trimEnd()}…` : clean;
|
|
251
|
+
}
|
|
252
|
+
/** Compact label for a swarm's stored Cursor model id (no catalog required). */
|
|
253
|
+
export function formatSwarmModelLabel(modelId) {
|
|
254
|
+
const id = (modelId ?? "").trim();
|
|
255
|
+
if (!id || id === "auto")
|
|
256
|
+
return "Auto";
|
|
257
|
+
const leaf = id.includes("/") ? id.split("/").pop() || id : id;
|
|
258
|
+
return leaf.length > 28 ? `${leaf.slice(0, 26).trimEnd()}…` : leaf;
|
|
259
|
+
}
|
|
260
|
+
function clip(text, max) {
|
|
261
|
+
const t = text.trim();
|
|
262
|
+
return t.length > max ? `${t.slice(0, max - 1).trimEnd()}…` : t;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Deterministic board digest injected into every cycle prompt. Agents get the
|
|
266
|
+
* decisions, directives, and anything addressed to them — not the whole board.
|
|
267
|
+
*/
|
|
268
|
+
export function buildBoardDigest(input) {
|
|
269
|
+
const maxChars = input.maxChars ?? 9_000;
|
|
270
|
+
const labelLc = input.agentLabel.toLowerCase();
|
|
271
|
+
const sorted = [...input.posts].sort((a, b) => a.createdAt - b.createdAt);
|
|
272
|
+
const directives = sorted
|
|
273
|
+
.filter((p) => p.kind === "directive" || p.channel === "plan")
|
|
274
|
+
.slice(-6);
|
|
275
|
+
const decisions = sorted.filter((p) => p.kind === "decision").slice(-5);
|
|
276
|
+
const forMe = sorted
|
|
277
|
+
.filter((p) => p.createdAt > input.since &&
|
|
278
|
+
(p.mentions.includes(input.agentId) || p.mentions.includes(labelLc)))
|
|
279
|
+
.slice(-8);
|
|
280
|
+
const fresh = sorted
|
|
281
|
+
.filter((p) => p.createdAt > input.since &&
|
|
282
|
+
(p.kind === "finding" || p.kind === "critique" || p.kind === "proposal"))
|
|
283
|
+
.slice(-10);
|
|
284
|
+
const openQuestions = sorted
|
|
285
|
+
.filter((p) => p.kind === "question" && p.createdAt > input.since)
|
|
286
|
+
.slice(-5);
|
|
287
|
+
const line = (p, max = 420) => `- [${p.kind} · #${p.channel} · ${p.authorLabel}] ${clip(p.bodyMd.replace(/\s+/g, " "), max)} (post ${p.id})`;
|
|
288
|
+
const sections = [];
|
|
289
|
+
sections.push(`### Team\n${input.agents
|
|
290
|
+
.filter((a) => a.status !== "retired")
|
|
291
|
+
.map((a) => `- @${a.label} — ${a.role}${a.id === input.agentId ? " (you)" : ""} · ${a.status}`)
|
|
292
|
+
.join("\n")}`);
|
|
293
|
+
if (directives.length) {
|
|
294
|
+
sections.push(`### Current directives\n${directives.map((p) => line(p, 600)).join("\n")}`);
|
|
295
|
+
}
|
|
296
|
+
if (decisions.length) {
|
|
297
|
+
sections.push(`### Decisions\n${decisions.map((p) => line(p)).join("\n")}`);
|
|
298
|
+
}
|
|
299
|
+
if (forMe.length) {
|
|
300
|
+
sections.push(`### Addressed to you\n${forMe.map((p) => line(p, 600)).join("\n")}`);
|
|
301
|
+
}
|
|
302
|
+
if (fresh.length) {
|
|
303
|
+
sections.push(`### New since your last cycle\n${fresh.map((p) => line(p)).join("\n")}`);
|
|
304
|
+
}
|
|
305
|
+
if (openQuestions.length) {
|
|
306
|
+
sections.push(`### Open questions\n${openQuestions.map((p) => line(p)).join("\n")}`);
|
|
307
|
+
}
|
|
308
|
+
const top = [...input.hypotheses]
|
|
309
|
+
.filter((h) => h.status !== "rejected")
|
|
310
|
+
.sort((a, b) => b.elo - a.elo)
|
|
311
|
+
.slice(0, 8);
|
|
312
|
+
if (top.length) {
|
|
313
|
+
sections.push(`### Hypothesis leaderboard\n${top
|
|
314
|
+
.map((h, i) => `${i + 1}. ${clip(h.title, 140)} — Elo ${Math.round(h.elo)}, ${h.critiques} critique(s) (${h.id})`)
|
|
315
|
+
.join("\n")}`);
|
|
316
|
+
}
|
|
317
|
+
const openTasks = input.tasks.filter((t) => t.status === "pending" || t.status === "claimed");
|
|
318
|
+
if (openTasks.length) {
|
|
319
|
+
sections.push(`### Open tasks\n${openTasks
|
|
320
|
+
.slice(0, 14)
|
|
321
|
+
.map((t) => `- ${t.id} [${t.kind} · ${t.status}${t.ownerAgentId === input.agentId ? " · yours" : ""}] ${clip(t.title, 140)}`)
|
|
322
|
+
.join("\n")}`);
|
|
323
|
+
}
|
|
324
|
+
let out = sections.join("\n\n");
|
|
325
|
+
if (out.length > maxChars)
|
|
326
|
+
out = `${out.slice(0, maxChars - 40).trimEnd()}\n…(digest truncated — call board_read)`;
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type SwarmAgentInfo, type SwarmAgentStatus, type SwarmRole, type SwarmTaskInfo, type SwarmTaskKind, type SwarmTaskStatus } from "./swarm";
|
|
2
|
+
export type SwarmVisualStatus = "idle" | "working" | "dead" | "error";
|
|
3
|
+
export declare const ROLE_HEX: Record<SwarmRole, string>;
|
|
4
|
+
/** Deeper role colors for the light theme (the pastels above wash out on white). */
|
|
5
|
+
export declare const ROLE_HEX_LIGHT: Record<SwarmRole, string>;
|
|
6
|
+
export type SwarmSceneTheme = "light" | "dark";
|
|
7
|
+
export declare function roleHex(role: SwarmRole, theme?: SwarmSceneTheme): string;
|
|
8
|
+
export declare const ROLE_FLOORS: Record<SwarmRole, {
|
|
9
|
+
y: number;
|
|
10
|
+
z: number;
|
|
11
|
+
xBias: number;
|
|
12
|
+
label: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const WORK_BOARD: {
|
|
15
|
+
y: number;
|
|
16
|
+
z: number;
|
|
17
|
+
x: number;
|
|
18
|
+
};
|
|
19
|
+
export type Vec3 = [number, number, number];
|
|
20
|
+
export declare const ZERO_VEC: Vec3;
|
|
21
|
+
export declare function addVec(a: Vec3, b: Vec3): Vec3;
|
|
22
|
+
export interface SwarmVisualAgentNode {
|
|
23
|
+
id: string;
|
|
24
|
+
kind: "agent";
|
|
25
|
+
role: SwarmRole;
|
|
26
|
+
label: string;
|
|
27
|
+
brief: string;
|
|
28
|
+
status: SwarmAgentStatus;
|
|
29
|
+
visualStatus: SwarmVisualStatus;
|
|
30
|
+
spawnedBy: string | null;
|
|
31
|
+
currentTaskId: string | null;
|
|
32
|
+
taskTitle: string | null;
|
|
33
|
+
position: Vec3;
|
|
34
|
+
scale: number;
|
|
35
|
+
}
|
|
36
|
+
export interface SwarmVisualWorkNode {
|
|
37
|
+
id: string;
|
|
38
|
+
kind: "work";
|
|
39
|
+
title: string;
|
|
40
|
+
taskKind: SwarmTaskKind;
|
|
41
|
+
status: SwarmTaskStatus;
|
|
42
|
+
ownerAgentId: string | null;
|
|
43
|
+
position: Vec3;
|
|
44
|
+
}
|
|
45
|
+
export interface SwarmVisualEdge {
|
|
46
|
+
id: string;
|
|
47
|
+
type: "spawn" | "work";
|
|
48
|
+
from: string;
|
|
49
|
+
to: string;
|
|
50
|
+
}
|
|
51
|
+
export interface SwarmVisualFloor {
|
|
52
|
+
role: SwarmRole;
|
|
53
|
+
label: string;
|
|
54
|
+
center: Vec3;
|
|
55
|
+
width: number;
|
|
56
|
+
depth: number;
|
|
57
|
+
}
|
|
58
|
+
export interface SwarmVisualGraph {
|
|
59
|
+
agents: SwarmVisualAgentNode[];
|
|
60
|
+
work: SwarmVisualWorkNode[];
|
|
61
|
+
edges: SwarmVisualEdge[];
|
|
62
|
+
floors: SwarmVisualFloor[];
|
|
63
|
+
}
|
|
64
|
+
export declare function visualStatusFor(status: SwarmAgentStatus): SwarmVisualStatus;
|
|
65
|
+
export declare function agentVisibleInGraph(agent: Pick<SwarmAgentInfo, "status" | "createdAt" | "role">, opts?: {
|
|
66
|
+
includeRetired?: boolean;
|
|
67
|
+
asOf?: number | null;
|
|
68
|
+
}): boolean;
|
|
69
|
+
export declare function replayCast(agents: SwarmAgentInfo[], opts?: {
|
|
70
|
+
includeRetired?: boolean;
|
|
71
|
+
}): SwarmAgentInfo[];
|
|
72
|
+
/** Perlin smootherstep — slow in and out, no linear snap. */
|
|
73
|
+
export declare function smootherstep(t: number): number;
|
|
74
|
+
export declare function buildSwarmVisualGraph(input: {
|
|
75
|
+
agents: SwarmAgentInfo[];
|
|
76
|
+
tasks: SwarmTaskInfo[];
|
|
77
|
+
includeRetired?: boolean;
|
|
78
|
+
asOf?: number | null;
|
|
79
|
+
}): SwarmVisualGraph;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { SWARM_ROLES, } from "./swarm";
|
|
2
|
+
export const ROLE_HEX = {
|
|
3
|
+
orchestrator: "#f0c674",
|
|
4
|
+
researcher: "#8ec5ff",
|
|
5
|
+
critic: "#f59e9e",
|
|
6
|
+
ranker: "#c4a7ff",
|
|
7
|
+
synthesizer: "#7ee2c4",
|
|
8
|
+
verifier: "#3ecf8e",
|
|
9
|
+
engineer: "#f7a86b",
|
|
10
|
+
integrator: "#f7d56b",
|
|
11
|
+
};
|
|
12
|
+
/** Deeper role colors for the light theme (the pastels above wash out on white). */
|
|
13
|
+
export const ROLE_HEX_LIGHT = {
|
|
14
|
+
orchestrator: "#c48a12",
|
|
15
|
+
researcher: "#2f7fd1",
|
|
16
|
+
critic: "#d4525a",
|
|
17
|
+
ranker: "#7c5cd6",
|
|
18
|
+
synthesizer: "#159b77",
|
|
19
|
+
verifier: "#17a34a",
|
|
20
|
+
engineer: "#dd6f24",
|
|
21
|
+
integrator: "#b8930a",
|
|
22
|
+
};
|
|
23
|
+
export function roleHex(role, theme = "dark") {
|
|
24
|
+
return (theme === "light" ? ROLE_HEX_LIGHT : ROLE_HEX)[role];
|
|
25
|
+
}
|
|
26
|
+
export const ROLE_FLOORS = {
|
|
27
|
+
orchestrator: { y: 5.2, z: -3.1, xBias: 0, label: "Orchestrator" },
|
|
28
|
+
researcher: { y: 3.35, z: -0.7, xBias: -3.6, label: "Research" },
|
|
29
|
+
critic: { y: 2.15, z: 0.55, xBias: 0.15, label: "Critique" },
|
|
30
|
+
ranker: { y: 1.35, z: 0.15, xBias: 3.35, label: "Rank" },
|
|
31
|
+
synthesizer: { y: 0.35, z: 2.05, xBias: -1.35, label: "Synthesize" },
|
|
32
|
+
verifier: { y: -0.25, z: 2.25, xBias: 2.15, label: "Verify" },
|
|
33
|
+
engineer: { y: -1.45, z: 1.35, xBias: 5.15, label: "Build" },
|
|
34
|
+
integrator: { y: -1.45, z: 2.45, xBias: 6.45, label: "Integrate" },
|
|
35
|
+
};
|
|
36
|
+
export const WORK_BOARD = { y: -2.35, z: 4.35, x: 0 };
|
|
37
|
+
export const ZERO_VEC = [0, 0, 0];
|
|
38
|
+
export function addVec(a, b) {
|
|
39
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
40
|
+
}
|
|
41
|
+
export function visualStatusFor(status) {
|
|
42
|
+
if (status === "running")
|
|
43
|
+
return "working";
|
|
44
|
+
if (status === "retired")
|
|
45
|
+
return "dead";
|
|
46
|
+
if (status === "error")
|
|
47
|
+
return "error";
|
|
48
|
+
return "idle";
|
|
49
|
+
}
|
|
50
|
+
export function agentVisibleInGraph(agent, opts = {}) {
|
|
51
|
+
if (opts.asOf != null && agent.createdAt > opts.asOf)
|
|
52
|
+
return false;
|
|
53
|
+
if (agent.role === "orchestrator")
|
|
54
|
+
return true;
|
|
55
|
+
if (!opts.includeRetired && agent.status === "retired")
|
|
56
|
+
return false;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
export function replayCast(agents, opts = {}) {
|
|
60
|
+
return agents
|
|
61
|
+
.filter((agent) => agentVisibleInGraph(agent, { includeRetired: opts.includeRetired, asOf: null }))
|
|
62
|
+
.slice()
|
|
63
|
+
.sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
|
64
|
+
}
|
|
65
|
+
/** Perlin smootherstep — slow in and out, no linear snap. */
|
|
66
|
+
export function smootherstep(t) {
|
|
67
|
+
const x = Math.min(1, Math.max(0, t));
|
|
68
|
+
return x * x * x * (x * (x * 6 - 15) + 10);
|
|
69
|
+
}
|
|
70
|
+
function layoutAgents(agents) {
|
|
71
|
+
const byRole = new Map();
|
|
72
|
+
for (const role of SWARM_ROLES)
|
|
73
|
+
byRole.set(role, []);
|
|
74
|
+
for (const agent of agents) {
|
|
75
|
+
(byRole.get(agent.role) ?? []).push(agent);
|
|
76
|
+
}
|
|
77
|
+
const positions = new Map();
|
|
78
|
+
for (const role of SWARM_ROLES) {
|
|
79
|
+
const lane = ROLE_FLOORS[role];
|
|
80
|
+
const list = (byRole.get(role) ?? [])
|
|
81
|
+
.slice()
|
|
82
|
+
.sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
|
83
|
+
const n = list.length;
|
|
84
|
+
list.forEach((agent, i) => {
|
|
85
|
+
const x = lane.xBias + (i - (n - 1) / 2) * 1.55;
|
|
86
|
+
const z = lane.z + (i % 2 === 0 ? 0 : 0.32);
|
|
87
|
+
positions.set(agent.id, [x, lane.y, z]);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return positions;
|
|
91
|
+
}
|
|
92
|
+
function floorsFor(agents) {
|
|
93
|
+
const floors = [];
|
|
94
|
+
for (const role of SWARM_ROLES) {
|
|
95
|
+
const nodes = agents.filter((a) => a.role === role);
|
|
96
|
+
if (!nodes.length)
|
|
97
|
+
continue;
|
|
98
|
+
const xs = nodes.map((n) => n.position[0]);
|
|
99
|
+
const zs = nodes.map((n) => n.position[2]);
|
|
100
|
+
const minX = Math.min(...xs);
|
|
101
|
+
const maxX = Math.max(...xs);
|
|
102
|
+
const minZ = Math.min(...zs);
|
|
103
|
+
const maxZ = Math.max(...zs);
|
|
104
|
+
const padX = role === "orchestrator" ? 1.6 : 1.25;
|
|
105
|
+
const padZ = role === "orchestrator" ? 1.3 : 1.05;
|
|
106
|
+
floors.push({
|
|
107
|
+
role,
|
|
108
|
+
label: ROLE_FLOORS[role].label,
|
|
109
|
+
center: [(minX + maxX) / 2, ROLE_FLOORS[role].y - 0.02, (minZ + maxZ) / 2],
|
|
110
|
+
width: Math.max(2.2, maxX - minX + padX * 2),
|
|
111
|
+
depth: Math.max(1.6, maxZ - minZ + padZ * 2),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return floors;
|
|
115
|
+
}
|
|
116
|
+
function workPositions(count) {
|
|
117
|
+
const cols = Math.min(4, Math.max(1, count));
|
|
118
|
+
const rows = Math.ceil(count / cols);
|
|
119
|
+
return Array.from({ length: count }, (_, i) => {
|
|
120
|
+
const col = i % cols;
|
|
121
|
+
const row = Math.floor(i / cols);
|
|
122
|
+
const x = WORK_BOARD.x + (col - (cols - 1) / 2) * 1.85;
|
|
123
|
+
const z = WORK_BOARD.z + (row - (rows - 1) / 2) * 1.15;
|
|
124
|
+
return [x, WORK_BOARD.y, z];
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
export function buildSwarmVisualGraph(input) {
|
|
128
|
+
const layoutPool = replayCast(input.agents, { includeRetired: input.includeRetired });
|
|
129
|
+
const visible = input.agents
|
|
130
|
+
.filter((agent) => agentVisibleInGraph(agent, input))
|
|
131
|
+
.slice()
|
|
132
|
+
.sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
|
133
|
+
const visibleIds = new Set(visible.map((a) => a.id));
|
|
134
|
+
const positions = layoutAgents(layoutPool);
|
|
135
|
+
const taskById = new Map(input.tasks.map((t) => [t.id, t]));
|
|
136
|
+
const agents = visible.map((agent) => {
|
|
137
|
+
const current = agent.currentTaskId ? taskById.get(agent.currentTaskId) : undefined;
|
|
138
|
+
const owned = input.tasks.find((t) => t.ownerAgentId === agent.id && t.status !== "cancelled");
|
|
139
|
+
return {
|
|
140
|
+
id: agent.id,
|
|
141
|
+
kind: "agent",
|
|
142
|
+
role: agent.role,
|
|
143
|
+
label: agent.label,
|
|
144
|
+
brief: agent.brief,
|
|
145
|
+
status: agent.status,
|
|
146
|
+
visualStatus: visualStatusFor(agent.status),
|
|
147
|
+
spawnedBy: agent.spawnedBy,
|
|
148
|
+
currentTaskId: agent.currentTaskId,
|
|
149
|
+
taskTitle: current?.title ?? owned?.title ?? null,
|
|
150
|
+
position: positions.get(agent.id) ?? [0, 0, 0],
|
|
151
|
+
scale: agent.role === "orchestrator" ? 1.45 : 1,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
const workSource = input.tasks.filter((task) => {
|
|
155
|
+
if (task.status === "cancelled")
|
|
156
|
+
return false;
|
|
157
|
+
if (task.ownerAgentId && visibleIds.has(task.ownerAgentId))
|
|
158
|
+
return true;
|
|
159
|
+
return visible.some((a) => a.currentTaskId === task.id);
|
|
160
|
+
});
|
|
161
|
+
const ranked = workSource
|
|
162
|
+
.slice()
|
|
163
|
+
.sort((a, b) => {
|
|
164
|
+
const rank = (t) => t.status === "claimed" ? 0 : visible.some((agent) => agent.currentTaskId === t.id) ? 1 : 2;
|
|
165
|
+
return rank(a) - rank(b) || b.updatedAt - a.updatedAt;
|
|
166
|
+
})
|
|
167
|
+
.slice(0, 12);
|
|
168
|
+
const spots = workPositions(ranked.length);
|
|
169
|
+
const work = ranked.map((task, i) => ({
|
|
170
|
+
id: task.id,
|
|
171
|
+
kind: "work",
|
|
172
|
+
title: task.title,
|
|
173
|
+
taskKind: task.kind,
|
|
174
|
+
status: task.status,
|
|
175
|
+
ownerAgentId: task.ownerAgentId,
|
|
176
|
+
position: spots[i] ?? [WORK_BOARD.x, WORK_BOARD.y, WORK_BOARD.z],
|
|
177
|
+
}));
|
|
178
|
+
const workIds = new Set(work.map((node) => node.id));
|
|
179
|
+
const orch = agents.find((a) => a.role === "orchestrator");
|
|
180
|
+
const edges = [];
|
|
181
|
+
for (const agent of agents) {
|
|
182
|
+
if (agent.role === "orchestrator")
|
|
183
|
+
continue;
|
|
184
|
+
const parent = (agent.spawnedBy && visibleIds.has(agent.spawnedBy) && agent.spawnedBy) || orch?.id || null;
|
|
185
|
+
if (!parent)
|
|
186
|
+
continue;
|
|
187
|
+
edges.push({
|
|
188
|
+
id: `spawn:${parent}:${agent.id}`,
|
|
189
|
+
type: "spawn",
|
|
190
|
+
from: parent,
|
|
191
|
+
to: agent.id,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const seenWork = new Set();
|
|
195
|
+
const addWork = (from, to) => {
|
|
196
|
+
const id = `work:${from}:${to}`;
|
|
197
|
+
if (seenWork.has(id) || !workIds.has(to) || !visibleIds.has(from))
|
|
198
|
+
return;
|
|
199
|
+
seenWork.add(id);
|
|
200
|
+
edges.push({ id, type: "work", from, to });
|
|
201
|
+
};
|
|
202
|
+
for (const agent of agents) {
|
|
203
|
+
if (agent.currentTaskId)
|
|
204
|
+
addWork(agent.id, agent.currentTaskId);
|
|
205
|
+
}
|
|
206
|
+
for (const node of work) {
|
|
207
|
+
if (node.ownerAgentId && (node.status === "claimed" || node.status === "done")) {
|
|
208
|
+
addWork(node.ownerAgentId, node.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { agents, work, edges, floors: floorsFor(agents) };
|
|
212
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter terminal noise that pollutes shared Cursor Agent sessions.
|
|
3
|
+
*
|
|
4
|
+
* xterm.js answers Device Attribute / cursor-position queries via onData.
|
|
5
|
+
* Those replies must not be forwarded to the PTY — but real input
|
|
6
|
+
* (arrows, PageUp/Down, mouse wheel, typed text) must pass through.
|
|
7
|
+
*/
|
|
8
|
+
/** DA / CPR / focus replies xterm emits — never forward these to the PTY. */
|
|
9
|
+
export declare function isTerminalAutoReply(data: string): boolean;
|
|
10
|
+
/** SGR / legacy mouse reports (including wheel buttons 64/65). */
|
|
11
|
+
export declare function isMouseSequence(data: string): boolean;
|
|
12
|
+
/** Strip leaked DA gibberish from output shown in the browser. */
|
|
13
|
+
export declare function sanitizeTerminalOutput(data: string): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter terminal noise that pollutes shared Cursor Agent sessions.
|
|
3
|
+
*
|
|
4
|
+
* xterm.js answers Device Attribute / cursor-position queries via onData.
|
|
5
|
+
* Those replies must not be forwarded to the PTY — but real input
|
|
6
|
+
* (arrows, PageUp/Down, mouse wheel, typed text) must pass through.
|
|
7
|
+
*/
|
|
8
|
+
/** DA / CPR / focus replies xterm emits — never forward these to the PTY. */
|
|
9
|
+
export function isTerminalAutoReply(data) {
|
|
10
|
+
if (!data)
|
|
11
|
+
return true;
|
|
12
|
+
// Primary / secondary DA: ESC [ ? … c | ESC [ > … c | ESC [ … c
|
|
13
|
+
if (/^\x1b\[\??[\d;]*c$/.test(data))
|
|
14
|
+
return true;
|
|
15
|
+
if (/^\x1b\[>[\d;]*c$/.test(data))
|
|
16
|
+
return true;
|
|
17
|
+
// Cursor Position Report: ESC [ row ; col R
|
|
18
|
+
if (/^\x1b\[\d+;\d+R$/.test(data))
|
|
19
|
+
return true;
|
|
20
|
+
// Device Status Report reply
|
|
21
|
+
if (/^\x1b\[\d*n$/.test(data))
|
|
22
|
+
return true;
|
|
23
|
+
// Focus in / out
|
|
24
|
+
if (data === "\x1b[I" || data === "\x1b[O")
|
|
25
|
+
return true;
|
|
26
|
+
// Bare non-printable controls (keep TAB/LF/CR — those are real input)
|
|
27
|
+
if (/^[\x00-\x08\x0b\x0c\x0e-\x1a\x1c-\x1f]+$/.test(data))
|
|
28
|
+
return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/** SGR / legacy mouse reports (including wheel buttons 64/65). */
|
|
32
|
+
export function isMouseSequence(data) {
|
|
33
|
+
return (/^\x1b\[<[\d;]*[Mm]$/.test(data) ||
|
|
34
|
+
/^\x1b\[M.{3}$/.test(data));
|
|
35
|
+
}
|
|
36
|
+
/** Visible DA/DA2 garbage that already leaked into scrollback. */
|
|
37
|
+
const VISIBLE_DA_GARBAGE = /(?:\x1b)?(?:\[>?[\d;]*c|\[\?[\d;]*c|\[>[\d;]+c)+/g;
|
|
38
|
+
/** OSC / CSI fragments that sometimes render literally. */
|
|
39
|
+
const LITERAL_CSI_NOISE = /(?:direct)?(?:\[>?[\d;]*c)+/gi;
|
|
40
|
+
/** Strip leaked DA gibberish from output shown in the browser. */
|
|
41
|
+
export function sanitizeTerminalOutput(data) {
|
|
42
|
+
return data
|
|
43
|
+
.replace(VISIBLE_DA_GARBAGE, "")
|
|
44
|
+
.replace(LITERAL_CSI_NOISE, "")
|
|
45
|
+
.replace(/(?:\[>?[\d;]*c){2,}/g, "");
|
|
46
|
+
}
|