whipped 0.2.0 → 0.3.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/dist/cli.js +134 -81
- package/dist/mcp-server.js +5 -5
- package/dist/migrations/013_agent_id_add_mimo.sql +93 -0
- package/dist/web-ui/assets/{index-Dgu7Q26n.css → index-C-IzbCaE.css} +16 -0
- package/dist/web-ui/assets/{index-Ce4HDOh7.js → index-DG3DMON0.js} +21 -8
- package/dist/web-ui/index.html +2 -2
- package/package.json +14 -25
package/dist/cli.js
CHANGED
|
@@ -3533,7 +3533,7 @@ var runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlo
|
|
|
3533
3533
|
var init_api_contract = __esm({
|
|
3534
3534
|
"src/core/api-contract.ts"() {
|
|
3535
3535
|
"use strict";
|
|
3536
|
-
runtimeAgentIdSchema = z.enum(["claude", "codex", "opencode", "cursor"]);
|
|
3536
|
+
runtimeAgentIdSchema = z.enum(["claude", "codex", "opencode", "cursor", "mimo"]);
|
|
3537
3537
|
effortLevelSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
3538
3538
|
agentModelChoiceSchema = z.object({
|
|
3539
3539
|
agentId: runtimeAgentIdSchema.default("claude"),
|
|
@@ -4266,21 +4266,26 @@ function getDb() {
|
|
|
4266
4266
|
function runMigrations(db) {
|
|
4267
4267
|
const currentVersion = db.pragma("user_version", { simple: true });
|
|
4268
4268
|
const files = readdirSync(MIGRATIONS_DIR).filter((f2) => /^\d+_.+\.sql$/.test(f2)).sort();
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
db.
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4269
|
+
db.pragma("foreign_keys = OFF");
|
|
4270
|
+
try {
|
|
4271
|
+
for (const file of files) {
|
|
4272
|
+
const version = Number.parseInt(file.split("_")[0] ?? "0", 10);
|
|
4273
|
+
if (!Number.isFinite(version) || version <= currentVersion) continue;
|
|
4274
|
+
const sql = readFileSync2(join4(MIGRATIONS_DIR, file), "utf-8");
|
|
4275
|
+
logger.info({ file, version }, "Running migration");
|
|
4276
|
+
const tx = db.transaction(() => {
|
|
4277
|
+
db.exec(sql);
|
|
4278
|
+
db.pragma(`user_version = ${version}`);
|
|
4279
|
+
});
|
|
4280
|
+
try {
|
|
4281
|
+
tx();
|
|
4282
|
+
} catch (err) {
|
|
4283
|
+
logger.error({ err, file }, "Migration failed; rolled back");
|
|
4284
|
+
throw new Error(`Migration ${file} failed: ${err.message}`);
|
|
4285
|
+
}
|
|
4283
4286
|
}
|
|
4287
|
+
} finally {
|
|
4288
|
+
db.pragma("foreign_keys = ON");
|
|
4284
4289
|
}
|
|
4285
4290
|
}
|
|
4286
4291
|
var MIGRATIONS_DIR, DEFAULT_DB_PATH, cachedDb;
|
|
@@ -13639,13 +13644,40 @@ function buildTaskHookEnv(taskId, workspaceId) {
|
|
|
13639
13644
|
[MACHINE_TOKEN_ENV]: getMachineToken() ?? ""
|
|
13640
13645
|
};
|
|
13641
13646
|
}
|
|
13642
|
-
var
|
|
13643
|
-
|
|
13647
|
+
var PLUGIN_AGENT_SPECS = {
|
|
13648
|
+
opencode: {
|
|
13649
|
+
configDirEnv: "OPENCODE_CONFIG_DIR",
|
|
13650
|
+
configFile: "opencode.json",
|
|
13651
|
+
dirPrefix: "opencode",
|
|
13652
|
+
pluginHeader: 'import { tool } from "@opencode-ai/plugin"\nimport type { Plugin } from "@opencode-ai/plugin"\n\n',
|
|
13653
|
+
pluginExportType: ": Plugin",
|
|
13654
|
+
toolWrapperOpen: "tool(",
|
|
13655
|
+
toolWrapperClose: ")"
|
|
13656
|
+
},
|
|
13657
|
+
mimo: {
|
|
13658
|
+
configDirEnv: "MIMOCODE_CONFIG_DIR",
|
|
13659
|
+
configFile: "mimocode.json",
|
|
13660
|
+
dirPrefix: "mimo",
|
|
13661
|
+
pluginHeader: "",
|
|
13662
|
+
pluginExportType: "",
|
|
13663
|
+
toolWrapperOpen: "",
|
|
13664
|
+
toolWrapperClose: ""
|
|
13665
|
+
}
|
|
13666
|
+
};
|
|
13667
|
+
function isPluginConfigAgent(agentId) {
|
|
13668
|
+
return agentId in PLUGIN_AGENT_SPECS;
|
|
13669
|
+
}
|
|
13670
|
+
function getPluginAgentConfigDir(agentId, id) {
|
|
13644
13671
|
const safe = id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
13645
|
-
return join9(HOOKS_DIR,
|
|
13672
|
+
return join9(HOOKS_DIR, `${PLUGIN_AGENT_SPECS[agentId].dirPrefix}-${safe}`);
|
|
13646
13673
|
}
|
|
13647
|
-
|
|
13648
|
-
|
|
13674
|
+
function pluginAgentConfigDirEnv(agentId, id) {
|
|
13675
|
+
if (!isPluginConfigAgent(agentId)) return {};
|
|
13676
|
+
return { [PLUGIN_AGENT_SPECS[agentId].configDirEnv]: getPluginAgentConfigDir(agentId, id) };
|
|
13677
|
+
}
|
|
13678
|
+
async function writePluginAgentFiles(agentId, id, serverPort, mcpServer, opts = {}) {
|
|
13679
|
+
const spec = PLUGIN_AGENT_SPECS[agentId];
|
|
13680
|
+
const dir = getPluginAgentConfigDir(agentId, id);
|
|
13649
13681
|
await mkdir(join9(dir, "plugin"), { recursive: true });
|
|
13650
13682
|
const systemParts = [];
|
|
13651
13683
|
if (opts.appendSystemPrompt) systemParts.push(opts.appendSystemPrompt);
|
|
@@ -13656,15 +13688,12 @@ async function writeOpencodeFiles(id, serverPort, mcpServer, opts = {}) {
|
|
|
13656
13688
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
13657
13689
|
${systemParts.map((p2) => `output.system.push(${JSON.stringify(p2)})`).join("\n ")}
|
|
13658
13690
|
},`;
|
|
13659
|
-
const plugin =
|
|
13660
|
-
import type { Plugin } from "@opencode-ai/plugin"
|
|
13661
|
-
|
|
13662
|
-
export const WhippedPlugin: Plugin = async () => {
|
|
13691
|
+
const plugin = `${spec.pluginHeader}export const WhippedPlugin${spec.pluginExportType} = async () => {
|
|
13663
13692
|
const port = ${serverPort}
|
|
13664
13693
|
|
|
13665
13694
|
return {${systemTransformHook}
|
|
13666
13695
|
tool: {
|
|
13667
|
-
task_complete:
|
|
13696
|
+
task_complete: ${spec.toolWrapperOpen}{
|
|
13668
13697
|
description: "Signal that you have finished all work on this task. Call this after completing all code changes, setting PR metadata with kanban_set_pr_meta, and adding your summary with kanban_add_comment.",
|
|
13669
13698
|
args: {},
|
|
13670
13699
|
execute: async (_args, _ctx) => {
|
|
@@ -13677,15 +13706,15 @@ export const WhippedPlugin: Plugin = async () => {
|
|
|
13677
13706
|
}
|
|
13678
13707
|
return "Task marked as complete."
|
|
13679
13708
|
}
|
|
13680
|
-
}
|
|
13709
|
+
}${spec.toolWrapperClose}
|
|
13681
13710
|
},
|
|
13682
13711
|
}
|
|
13683
13712
|
}
|
|
13684
13713
|
`;
|
|
13685
13714
|
const extraMcp = Object.fromEntries(
|
|
13686
|
-
Object.entries(opts.extraMcp ?? {}).map(([name,
|
|
13715
|
+
Object.entries(opts.extraMcp ?? {}).map(([name, spec2]) => [
|
|
13687
13716
|
name,
|
|
13688
|
-
{ type: "local", command: [
|
|
13717
|
+
{ type: "local", command: [spec2.command, ...spec2.args] }
|
|
13689
13718
|
])
|
|
13690
13719
|
);
|
|
13691
13720
|
const config = {
|
|
@@ -13706,11 +13735,11 @@ export const WhippedPlugin: Plugin = async () => {
|
|
|
13706
13735
|
};
|
|
13707
13736
|
await Promise.all([
|
|
13708
13737
|
writeFile(join9(dir, "plugin", "whipped.ts"), plugin),
|
|
13709
|
-
writeFile(join9(dir,
|
|
13738
|
+
writeFile(join9(dir, spec.configFile), JSON.stringify(config, null, 2))
|
|
13710
13739
|
]);
|
|
13711
13740
|
}
|
|
13712
|
-
async function
|
|
13713
|
-
await rm(
|
|
13741
|
+
async function cleanupPluginAgentFiles(agentId, id) {
|
|
13742
|
+
await rm(getPluginAgentConfigDir(agentId, id), { recursive: true, force: true });
|
|
13714
13743
|
}
|
|
13715
13744
|
var CURSOR_CONFIG_DIR_ENV = "CURSOR_CONFIG_DIR";
|
|
13716
13745
|
function getCursorConfigDir(id) {
|
|
@@ -18902,9 +18931,9 @@ function buildBrowserMcpServer(cardId) {
|
|
|
18902
18931
|
}
|
|
18903
18932
|
|
|
18904
18933
|
// src/agents/agent-registry.ts
|
|
18905
|
-
function
|
|
18934
|
+
function getProviderModels(binary) {
|
|
18906
18935
|
try {
|
|
18907
|
-
const result = spawnSync4(
|
|
18936
|
+
const result = spawnSync4(binary, ["models"], {
|
|
18908
18937
|
stdio: ["ignore", "pipe", "ignore"],
|
|
18909
18938
|
timeout: 1e4,
|
|
18910
18939
|
encoding: "utf-8"
|
|
@@ -18916,6 +18945,12 @@ function getOpencodeModels() {
|
|
|
18916
18945
|
}
|
|
18917
18946
|
return [];
|
|
18918
18947
|
}
|
|
18948
|
+
function getOpencodeModels() {
|
|
18949
|
+
return getProviderModels("opencode");
|
|
18950
|
+
}
|
|
18951
|
+
function getMimoModels() {
|
|
18952
|
+
return getProviderModels("mimo");
|
|
18953
|
+
}
|
|
18919
18954
|
function getCursorModels() {
|
|
18920
18955
|
try {
|
|
18921
18956
|
const result = spawnSync4("agent", ["models"], {
|
|
@@ -18959,6 +18994,12 @@ var AGENT_DEFINITIONS = [
|
|
|
18959
18994
|
label: "Cursor Agent",
|
|
18960
18995
|
command: "agent",
|
|
18961
18996
|
checkCommand: ["agent", "--version"]
|
|
18997
|
+
},
|
|
18998
|
+
{
|
|
18999
|
+
id: "mimo",
|
|
19000
|
+
label: "MiMo Code",
|
|
19001
|
+
command: "mimo",
|
|
19002
|
+
checkCommand: ["mimo", "--version"]
|
|
18962
19003
|
}
|
|
18963
19004
|
];
|
|
18964
19005
|
function isCommandAvailable(args) {
|
|
@@ -19027,19 +19068,22 @@ function buildAgentArgs(agentId, prompt, ctx = {}) {
|
|
|
19027
19068
|
}
|
|
19028
19069
|
return args;
|
|
19029
19070
|
}
|
|
19030
|
-
|
|
19071
|
+
// mimo (mimocode) is an opencode fork with an identical CLI surface
|
|
19072
|
+
// (`run`/`--agent build`/`-m`/`--prompt`/`--variant`), so it shares this branch.
|
|
19073
|
+
case "opencode":
|
|
19074
|
+
case "mimo": {
|
|
19031
19075
|
if (mode === "print") {
|
|
19032
19076
|
const args2 = ["run", "--agent", "build"];
|
|
19033
19077
|
if (ctx.model) args2.push("-m", ctx.model);
|
|
19034
19078
|
if (ctx.effort) {
|
|
19035
|
-
const
|
|
19079
|
+
const OPENCODE_EFFORT_VARIANT = {
|
|
19036
19080
|
low: "minimal",
|
|
19037
19081
|
medium: "low",
|
|
19038
19082
|
high: "medium",
|
|
19039
19083
|
xhigh: "high",
|
|
19040
19084
|
max: "max"
|
|
19041
19085
|
};
|
|
19042
|
-
args2.push("--variant",
|
|
19086
|
+
args2.push("--variant", agentId === "mimo" ? ctx.effort : OPENCODE_EFFORT_VARIANT[ctx.effort]);
|
|
19043
19087
|
}
|
|
19044
19088
|
if (prompt.trim()) args2.push(prompt);
|
|
19045
19089
|
return args2;
|
|
@@ -20290,10 +20334,10 @@ init_workspace_state();
|
|
|
20290
20334
|
// src/daemon/review-pipeline.ts
|
|
20291
20335
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
20292
20336
|
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
20293
|
-
import { readFile as readFile2,
|
|
20337
|
+
import { readdir, readFile as readFile2, stat, unlink } from "node:fs/promises";
|
|
20294
20338
|
import { join as join15 } from "node:path";
|
|
20295
|
-
init_api_contract();
|
|
20296
20339
|
init_runtime_config();
|
|
20340
|
+
init_api_contract();
|
|
20297
20341
|
init_logger();
|
|
20298
20342
|
init_task_id();
|
|
20299
20343
|
|
|
@@ -20478,9 +20522,9 @@ ${rawSystemPrompt}` : rawSystemPrompt;
|
|
|
20478
20522
|
|
|
20479
20523
|
After calling \`kanban_add_comment\`, call the \`task_complete\` MCP tool to signal that you are done.` : withMemory;
|
|
20480
20524
|
const triggerWord = getSlotTriggerWord(slot.type);
|
|
20481
|
-
const mcpConfigPath = agentBinary
|
|
20482
|
-
const hookServerPort = agentBinary === "codex" || agentBinary
|
|
20483
|
-
const mcpServer = agentBinary === "codex" || agentBinary
|
|
20525
|
+
const mcpConfigPath = !isPluginConfigAgent(agentBinary) && agentBinary !== "cursor" ? getMcpConfigPath(streamId) : void 0;
|
|
20526
|
+
const hookServerPort = agentBinary === "codex" || isPluginConfigAgent(agentBinary) || agentBinary === "cursor" ? getServerPort(options.serverUrl) : void 0;
|
|
20527
|
+
const mcpServer = agentBinary === "codex" || isPluginConfigAgent(agentBinary) || agentBinary === "cursor" ? buildWhippedMcpServerSpec(options.mcpBinary, options.serverUrl, workspaceId, agentBinary) : void 0;
|
|
20484
20528
|
const browserMcp = browserEnabled ? buildBrowserMcpServer(card.id) : void 0;
|
|
20485
20529
|
const browserMcpSpec = browserMcp ? { command: browserMcp.command, args: browserMcp.args } : void 0;
|
|
20486
20530
|
const extraMcpServers = browserMcpSpec ? { [PLAYWRIGHT_MCP_SERVER_NAME]: browserMcpSpec } : void 0;
|
|
@@ -20789,9 +20833,11 @@ async function handleReviewSuccess(card, options) {
|
|
|
20789
20833
|
}
|
|
20790
20834
|
function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, registerStopCallback, registerLiveProcess, mcpConfigPath, appendSystemPrompt, files, secretsEnv, effort, hookServerPort, mcpServer, model, slotType, browserMcpServer) {
|
|
20791
20835
|
const extraMcp = browserMcpServer ? { [PLAYWRIGHT_MCP_SERVER_NAME]: browserMcpServer } : void 0;
|
|
20792
|
-
if (agentId
|
|
20793
|
-
void
|
|
20794
|
-
|
|
20836
|
+
if (isPluginConfigAgent(agentId) && hookServerPort != null && mcpServer) {
|
|
20837
|
+
void writePluginAgentFiles(agentId, streamId, hookServerPort, mcpServer, { appendSystemPrompt, extraMcp }).catch(
|
|
20838
|
+
() => {
|
|
20839
|
+
}
|
|
20840
|
+
);
|
|
20795
20841
|
}
|
|
20796
20842
|
if (agentId === "cursor" && hookServerPort != null && mcpServer) {
|
|
20797
20843
|
void writeCursorConfigFiles(streamId, hookServerPort, mcpServer, extraMcp).catch(() => {
|
|
@@ -20806,7 +20852,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
|
|
|
20806
20852
|
void saveTerminalBuffer(workspaceId, streamId, output);
|
|
20807
20853
|
if (mcpConfigPath) unlink(mcpConfigPath).catch(() => {
|
|
20808
20854
|
});
|
|
20809
|
-
if (agentId
|
|
20855
|
+
if (isPluginConfigAgent(agentId)) void cleanupPluginAgentFiles(agentId, streamId);
|
|
20810
20856
|
if (agentId === "cursor") void cleanupCursorConfigDir(streamId);
|
|
20811
20857
|
resolve5(output);
|
|
20812
20858
|
});
|
|
@@ -20820,7 +20866,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
|
|
|
20820
20866
|
...buildTaskHookEnv(streamId, workspaceId),
|
|
20821
20867
|
...secretsEnv,
|
|
20822
20868
|
...slotType ? { WHIPPED_SLOT: slotType } : {},
|
|
20823
|
-
...agentId
|
|
20869
|
+
...pluginAgentConfigDirEnv(agentId, streamId),
|
|
20824
20870
|
...agentId === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(streamId) } : {}
|
|
20825
20871
|
},
|
|
20826
20872
|
hookSettingsPath: agentId === "claude" ? CLAUDE_TASK_SETTINGS_PATH : void 0,
|
|
@@ -20828,7 +20874,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
|
|
|
20828
20874
|
mcpConfigPath: agentId === "claude" ? mcpConfigPath : void 0,
|
|
20829
20875
|
mcpServer: agentId === "codex" ? mcpServer : void 0,
|
|
20830
20876
|
browserMcpServer: agentId === "codex" ? browserMcpServer : void 0,
|
|
20831
|
-
appendSystemPrompt: agentId
|
|
20877
|
+
appendSystemPrompt: isPluginConfigAgent(agentId) ? void 0 : appendSystemPrompt,
|
|
20832
20878
|
files: agentId === "claude" ? files : void 0,
|
|
20833
20879
|
effort,
|
|
20834
20880
|
model,
|
|
@@ -20843,7 +20889,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
|
|
|
20843
20889
|
void saveTerminalBuffer(workspaceId, streamId, output);
|
|
20844
20890
|
if (mcpConfigPath) unlink(mcpConfigPath).catch(() => {
|
|
20845
20891
|
});
|
|
20846
|
-
if (agentId
|
|
20892
|
+
if (isPluginConfigAgent(agentId)) void cleanupPluginAgentFiles(agentId, streamId);
|
|
20847
20893
|
if (agentId === "cursor") void cleanupCursorConfigDir(streamId);
|
|
20848
20894
|
resolve5(output);
|
|
20849
20895
|
}
|
|
@@ -21192,12 +21238,12 @@ When you finish your work:
|
|
|
21192
21238
|
|
|
21193
21239
|
${commitInstruction.replace(/^1\. /, "3. ")}
|
|
21194
21240
|
|
|
21195
|
-
4. Reconcile memory. Review this task's description, the user's comments, and any decision or correction that came up while you worked. If something **changed, contradicted, reversed, or superseded** an entry in the Memory list above, call \`whipped_update_memory\` on that entry's id so future tasks don't act on stale knowledge (e.g. the user says "stop using short ids, use full kebab-case" \u2192 update the id-convention memory, don't leave the old one).
|
|
21241
|
+
4. Reconcile memory. Review this task's description, the user's comments, and any decision or correction that came up while you worked. If something **changed, contradicted, reversed, or superseded** an entry in the Memory list above, call \`whipped_update_memory\` on that entry's id so future tasks don't act on stale knowledge (e.g. the user says "stop using short ids, use full kebab-case" \u2192 update the id-convention memory, don't leave the old one). Most tasks need NO new memory \u2014 skip this step by default. Only \`whipped_save_memory\` if a cross-cutting rule or non-obvious trap came up that you'd have wanted *before* starting and that isn't already recoverable by reading the code, schema, or a controller \u2014 endpoint shapes, column lists, field names, and per-page layout are code, not memory. Do NOT create a second memory that conflicts with an existing one \u2014 update the existing one instead.`);
|
|
21196
21242
|
parts.push(`## Memory
|
|
21197
21243
|
|
|
21198
21244
|
This project has its own persistent memory. The \`whipped_save_memory\` / \`whipped_update_memory\` MCP tools ARE this project's memory \u2014 do NOT use your own notes, scratch files, CLAUDE.md, or any other memory system for durable facts.
|
|
21199
21245
|
|
|
21200
|
-
When you are asked to "remember", "save to memory", "note for next time" \u2014 or you
|
|
21246
|
+
When you are asked to "remember", "save to memory", "note for next time" \u2014 or you hit a cross-cutting convention, an architecture decision, a non-obvious repo-wide gotcha, or a correction the user made \u2014 record it in memory. Do NOT record what is already in the code or schema (endpoint request/response shapes, query params, column lists, field names, colour classes, per-page layout): if your note would cite the file where the truth lives, the file is the memory \u2014 skip it. Keep each entry to one focused fact in 1-3 sentences.
|
|
21201
21247
|
|
|
21202
21248
|
Before recording, check the memory list injected above (each entry shows its \`[id]\`) and \`whipped_search_memory\`. If what you're recording **contradicts, reverses, supersedes, corrects, or is a near-duplicate of** an existing memory, call \`whipped_update_memory\` with that memory's id and overwrite it \u2014 do NOT create a second, conflicting entry. Only \`whipped_save_memory\` when there is genuinely no existing memory about the same thing. Use \`whipped_get_memory\` to read one in full.
|
|
21203
21249
|
|
|
@@ -21455,9 +21501,9 @@ ${rawSystemPrompt}` : rawSystemPrompt;
|
|
|
21455
21501
|
const systemPrompt = agentId === "cursor" ? `${withMemory}
|
|
21456
21502
|
|
|
21457
21503
|
After calling \`kanban_set_plan\`, call the \`task_complete\` MCP tool to signal that you are done.` : withMemory;
|
|
21458
|
-
const mcpConfigPath = agentId
|
|
21459
|
-
const hookServerPort = agentId === "codex" || agentId
|
|
21460
|
-
const mcpServer = agentId === "codex" || agentId
|
|
21504
|
+
const mcpConfigPath = !isPluginConfigAgent(agentId) && agentId !== "cursor" ? getMcpConfigPath(streamId) : void 0;
|
|
21505
|
+
const hookServerPort = agentId === "codex" || isPluginConfigAgent(agentId) || agentId === "cursor" ? getServerPort(options.serverUrl) : void 0;
|
|
21506
|
+
const mcpServer = agentId === "codex" || isPluginConfigAgent(agentId) || agentId === "cursor" ? buildWhippedMcpServerSpec(options.mcpBinary, options.serverUrl, workspaceId, agentId) : void 0;
|
|
21461
21507
|
if (agentId === "claude" && mcpConfigPath) {
|
|
21462
21508
|
await writeClaudeMcpConfig(options.mcpBinary, options.serverUrl, workspaceId, agentId, mcpConfigPath).catch(
|
|
21463
21509
|
() => {
|
|
@@ -21742,7 +21788,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
21742
21788
|
await writeClaudeAssistantSettings(getMcpServerPath(), serverUrl, workspaceId).catch((err) => {
|
|
21743
21789
|
logger.warn({ err }, "[scheduler] Failed to write assistant agent MCP settings");
|
|
21744
21790
|
});
|
|
21745
|
-
} else if (agentId
|
|
21791
|
+
} else if (isPluginConfigAgent(agentId)) {
|
|
21746
21792
|
const mcpSpec = buildWhippedMcpServerSpec(
|
|
21747
21793
|
getMcpServerPath(),
|
|
21748
21794
|
serverUrl,
|
|
@@ -21750,9 +21796,11 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
21750
21796
|
void 0,
|
|
21751
21797
|
buildMcpRoleArgs("assistant")
|
|
21752
21798
|
);
|
|
21753
|
-
await
|
|
21754
|
-
|
|
21755
|
-
|
|
21799
|
+
await writePluginAgentFiles(agentId, taskId, getServerPort(serverUrl), mcpSpec, { appendSystemPrompt }).catch(
|
|
21800
|
+
(err) => {
|
|
21801
|
+
logger.warn({ err }, `[scheduler] Failed to write ${agentId} assistant agent files`);
|
|
21802
|
+
}
|
|
21803
|
+
);
|
|
21756
21804
|
} else if (agentId === "cursor") {
|
|
21757
21805
|
const mcpSpec = buildWhippedMcpServerSpec(
|
|
21758
21806
|
getMcpServerPath(),
|
|
@@ -21778,7 +21826,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
21778
21826
|
...secretsEnv,
|
|
21779
21827
|
...buildTaskHookEnv(taskId, workspaceId),
|
|
21780
21828
|
WHIPPED_SLOT: "assistant",
|
|
21781
|
-
...agentId
|
|
21829
|
+
...pluginAgentConfigDirEnv(agentId, taskId),
|
|
21782
21830
|
...agentId === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(taskId) } : {}
|
|
21783
21831
|
},
|
|
21784
21832
|
mcpConfigPath: agentId === "claude" ? CLAUDE_ASSISTANT_MCP_CONFIG_PATH : void 0,
|
|
@@ -21791,7 +21839,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
21791
21839
|
) : void 0,
|
|
21792
21840
|
model: assistantModel?.model ?? null,
|
|
21793
21841
|
effort: assistantModel?.effort ?? null,
|
|
21794
|
-
appendSystemPrompt: agentId
|
|
21842
|
+
appendSystemPrompt: isPluginConfigAgent(agentId) ? void 0 : appendSystemPrompt,
|
|
21795
21843
|
onOutput: (data) => {
|
|
21796
21844
|
assistantTask.outputBuffer += data;
|
|
21797
21845
|
stateHub.broadcastTerminalOutput(workspaceId, taskId, data);
|
|
@@ -21959,7 +22007,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
|
|
|
21959
22007
|
this.options.onTaskCompleted(taskId);
|
|
21960
22008
|
return;
|
|
21961
22009
|
}
|
|
21962
|
-
const mcpConfigPath = agentId
|
|
22010
|
+
const mcpConfigPath = !isPluginConfigAgent(agentId) && agentId !== "cursor" ? getMcpConfigPath(taskId) : void 0;
|
|
21963
22011
|
let sharedBranchName;
|
|
21964
22012
|
if (hasSharedWorktree) {
|
|
21965
22013
|
const ownerCard = board.cards[effectiveWorktreeId];
|
|
@@ -22094,9 +22142,9 @@ ${devSystemPromptResult.text}`;
|
|
|
22094
22142
|
mcpConfigPath
|
|
22095
22143
|
).catch(() => {
|
|
22096
22144
|
});
|
|
22097
|
-
} else if (agentId
|
|
22145
|
+
} else if (isPluginConfigAgent(agentId)) {
|
|
22098
22146
|
const mcpSpec = buildWhippedMcpServerSpec(getMcpServerPath(), this.options.serverUrl, workspaceId, agentId);
|
|
22099
|
-
await
|
|
22147
|
+
await writePluginAgentFiles(agentId, taskId, getServerPort(this.options.serverUrl), mcpSpec, {
|
|
22100
22148
|
appendSystemPrompt: devSystemPromptResult.text
|
|
22101
22149
|
}).catch(() => {
|
|
22102
22150
|
});
|
|
@@ -22130,14 +22178,14 @@ ${devSystemPromptResult.text}`;
|
|
|
22130
22178
|
...secretsEnv,
|
|
22131
22179
|
WHIPPED_SLOT: "dev",
|
|
22132
22180
|
...devPair.model ? { WHIPPED_MODEL: devPair.model } : {},
|
|
22133
|
-
...agentId
|
|
22181
|
+
...pluginAgentConfigDirEnv(agentId, taskId),
|
|
22134
22182
|
...agentId === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(taskId) } : {}
|
|
22135
22183
|
},
|
|
22136
22184
|
hookSettingsPath: agentId === "claude" ? CLAUDE_TASK_SETTINGS_PATH : void 0,
|
|
22137
22185
|
hookServerPort: agentId === "codex" ? getServerPort(this.options.serverUrl) : void 0,
|
|
22138
22186
|
mcpConfigPath: agentId === "claude" ? mcpConfigPath : void 0,
|
|
22139
22187
|
mcpServer: agentId === "codex" ? buildWhippedMcpServerSpec(getMcpServerPath(), this.options.serverUrl, workspaceId, agentId) : void 0,
|
|
22140
|
-
appendSystemPrompt: agentId
|
|
22188
|
+
appendSystemPrompt: isPluginConfigAgent(agentId) ? void 0 : agentId === "cursor" ? devSystemPromptResult.text + "\n\n4. Call the `task_complete` MCP tool to signal that the task is complete." : devSystemPromptResult.text,
|
|
22141
22189
|
files: agentId === "claude" ? devSystemPromptResult.files : void 0,
|
|
22142
22190
|
effort: devPair.effort ?? void 0,
|
|
22143
22191
|
model: devPair.model ?? void 0,
|
|
@@ -22155,7 +22203,7 @@ ${devSystemPromptResult.text}`;
|
|
|
22155
22203
|
if (runningTask.worktreeOwnerId) this.runningSharedWorktrees.delete(runningTask.worktreeOwnerId);
|
|
22156
22204
|
if (mcpConfigPath) unlink2(mcpConfigPath).catch(() => {
|
|
22157
22205
|
});
|
|
22158
|
-
if (agentId
|
|
22206
|
+
if (isPluginConfigAgent(agentId)) void cleanupPluginAgentFiles(agentId, taskId);
|
|
22159
22207
|
if (agentId === "cursor") void cleanupCursorConfigDir(taskId);
|
|
22160
22208
|
if (this.isShuttingDown) return;
|
|
22161
22209
|
if (this.manuallyStoppedTasks.has(taskId)) {
|
|
@@ -22532,9 +22580,9 @@ ${devSystemPromptResult.text}`;
|
|
|
22532
22580
|
let outputBuffer = "";
|
|
22533
22581
|
let hookHandled = false;
|
|
22534
22582
|
const conflictSystemPrompt = buildConflictResolutionSystemPrompt(card, conflictedFiles, conflictGitInstructions);
|
|
22535
|
-
if (defaultAgent
|
|
22583
|
+
if (isPluginConfigAgent(defaultAgent)) {
|
|
22536
22584
|
const mcpSpec = buildWhippedMcpServerSpec(getMcpServerPath(), this.options.serverUrl, workspaceId);
|
|
22537
|
-
await
|
|
22585
|
+
await writePluginAgentFiles(defaultAgent, streamId, getServerPort(this.options.serverUrl), mcpSpec, {
|
|
22538
22586
|
appendSystemPrompt: conflictSystemPrompt
|
|
22539
22587
|
}).catch(() => {
|
|
22540
22588
|
});
|
|
@@ -22551,10 +22599,10 @@ ${devSystemPromptResult.text}`;
|
|
|
22551
22599
|
hookServerPort: defaultAgent === "codex" ? getServerPort(this.options.serverUrl) : void 0,
|
|
22552
22600
|
env: {
|
|
22553
22601
|
...buildTaskHookEnv(streamId, workspaceId),
|
|
22554
|
-
...defaultAgent
|
|
22602
|
+
...pluginAgentConfigDirEnv(defaultAgent, streamId),
|
|
22555
22603
|
...defaultAgent === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(streamId) } : {}
|
|
22556
22604
|
},
|
|
22557
|
-
appendSystemPrompt: defaultAgent
|
|
22605
|
+
appendSystemPrompt: isPluginConfigAgent(defaultAgent) ? void 0 : conflictSystemPrompt,
|
|
22558
22606
|
onOutput: (data) => {
|
|
22559
22607
|
outputBuffer += data;
|
|
22560
22608
|
stateHub.broadcastTerminalOutput(workspaceId, streamId, data);
|
|
@@ -22575,7 +22623,7 @@ ${devSystemPromptResult.text}`;
|
|
|
22575
22623
|
this.setRecentBuffer(streamId, outputBuffer);
|
|
22576
22624
|
void saveTerminalBuffer(workspaceId, streamId, outputBuffer);
|
|
22577
22625
|
void endTerminalSession(workspaceId, card.id, streamId, Date.now(), "completed");
|
|
22578
|
-
if (defaultAgent
|
|
22626
|
+
if (isPluginConfigAgent(defaultAgent)) void cleanupPluginAgentFiles(defaultAgent, streamId);
|
|
22579
22627
|
if (defaultAgent === "cursor") void cleanupCursorConfigDir(streamId);
|
|
22580
22628
|
proc.kill();
|
|
22581
22629
|
onComplete(true).catch((err) => logger.error({ err }, `[scheduler] conflict onComplete failed for ${card.id}:`));
|
|
@@ -22839,10 +22887,11 @@ var RecurringAgentScheduler = class {
|
|
|
22839
22887
|
void 0,
|
|
22840
22888
|
roleArgs
|
|
22841
22889
|
).catch((err) => logger.warn({ err }, "[recurring] failed to write claude MCP config"));
|
|
22842
|
-
} else if (agentBinary
|
|
22843
|
-
await
|
|
22844
|
-
|
|
22845
|
-
|
|
22890
|
+
} else if (isPluginConfigAgent(agentBinary)) {
|
|
22891
|
+
await writePluginAgentFiles(agentBinary, streamId, hookServerPort, mcpServer, {
|
|
22892
|
+
appendSystemPrompt,
|
|
22893
|
+
readOnly: true
|
|
22894
|
+
}).catch((err) => logger.warn({ err }, `[recurring] failed to write ${agentBinary} files`));
|
|
22846
22895
|
} else if (agentBinary === "cursor") {
|
|
22847
22896
|
await writeCursorConfigFiles(streamId, hookServerPort, mcpServer).catch(
|
|
22848
22897
|
(err) => logger.warn({ err }, "[recurring] failed to write cursor config")
|
|
@@ -22856,7 +22905,7 @@ var RecurringAgentScheduler = class {
|
|
|
22856
22905
|
const cleanup = () => {
|
|
22857
22906
|
if (mcpConfigPath) unlink3(mcpConfigPath).catch(() => {
|
|
22858
22907
|
});
|
|
22859
|
-
if (agentBinary
|
|
22908
|
+
if (isPluginConfigAgent(agentBinary)) void cleanupPluginAgentFiles(agentBinary, streamId);
|
|
22860
22909
|
if (agentBinary === "cursor") void cleanupCursorConfigDir(streamId);
|
|
22861
22910
|
};
|
|
22862
22911
|
const finish = (status) => {
|
|
@@ -22890,18 +22939,18 @@ var RecurringAgentScheduler = class {
|
|
|
22890
22939
|
...buildTaskHookEnv(streamId, workspaceId),
|
|
22891
22940
|
WHIPPED_SLOT: "recurring",
|
|
22892
22941
|
...agent.model.model ? { WHIPPED_MODEL: agent.model.model } : {},
|
|
22893
|
-
...agentBinary
|
|
22942
|
+
...pluginAgentConfigDirEnv(agentBinary, streamId),
|
|
22894
22943
|
...agentBinary === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(streamId) } : {}
|
|
22895
22944
|
},
|
|
22896
22945
|
hookSettingsPath: agentBinary === "claude" ? CLAUDE_TASK_SETTINGS_PATH : void 0,
|
|
22897
22946
|
hookServerPort: agentBinary === "codex" ? hookServerPort : void 0,
|
|
22898
22947
|
mcpConfigPath,
|
|
22899
22948
|
mcpServer: agentBinary === "codex" ? mcpServer : void 0,
|
|
22900
|
-
appendSystemPrompt: agentBinary
|
|
22949
|
+
appendSystemPrompt: isPluginConfigAgent(agentBinary) ? void 0 : appendSystemPrompt,
|
|
22901
22950
|
model: agent.model.model,
|
|
22902
22951
|
effort: agent.model.effort,
|
|
22903
22952
|
// Observer agents are read-only: claude blocks file/shell tools via
|
|
22904
|
-
// --disallowedTools, opencode
|
|
22953
|
+
// --disallowedTools, opencode/mimo disable them in their config. codex/cursor
|
|
22905
22954
|
// fall back to prompt-only enforcement.
|
|
22906
22955
|
readOnly: true,
|
|
22907
22956
|
onOutput: (data) => {
|
|
@@ -25531,12 +25580,16 @@ import { z as z4 } from "zod";
|
|
|
25531
25580
|
|
|
25532
25581
|
// src/api/services/agents-service.ts
|
|
25533
25582
|
var listAvailableAgents = async () => getAvailableAgents();
|
|
25534
|
-
var listModels = async (agent) =>
|
|
25583
|
+
var listModels = async (agent) => {
|
|
25584
|
+
if (agent === "cursor") return getCursorModels();
|
|
25585
|
+
const models = agent === "mimo" ? getMimoModels() : getOpencodeModels();
|
|
25586
|
+
return models.map((m2) => ({ value: m2, label: m2 }));
|
|
25587
|
+
};
|
|
25535
25588
|
|
|
25536
25589
|
// src/api/routes/agents.ts
|
|
25537
25590
|
var agentsController = new Hono2().get("/available", async (c) => {
|
|
25538
25591
|
return c.json(await listAvailableAgents());
|
|
25539
|
-
}).get("/models", zv("query", z4.object({ agent: z4.enum(["opencode", "cursor"]) })), async (c) => {
|
|
25592
|
+
}).get("/models", zv("query", z4.object({ agent: z4.enum(["opencode", "cursor", "mimo"]) })), async (c) => {
|
|
25540
25593
|
return c.json(await listModels(c.req.valid("query").agent));
|
|
25541
25594
|
});
|
|
25542
25595
|
|
|
@@ -28565,7 +28618,7 @@ process.on("uncaughtException", (err) => {
|
|
|
28565
28618
|
if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
|
|
28566
28619
|
throw err;
|
|
28567
28620
|
});
|
|
28568
|
-
var VERSION9 = true ? "0.
|
|
28621
|
+
var VERSION9 = true ? "0.3.0" : "0.0.0-dev";
|
|
28569
28622
|
async function isPortAvailable(port, host) {
|
|
28570
28623
|
return new Promise((resolve5) => {
|
|
28571
28624
|
const probe = createServer2();
|
package/dist/mcp-server.js
CHANGED
|
@@ -37,7 +37,7 @@ var runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlo
|
|
|
37
37
|
var init_api_contract = __esm({
|
|
38
38
|
"src/core/api-contract.ts"() {
|
|
39
39
|
"use strict";
|
|
40
|
-
runtimeAgentIdSchema = z.enum(["claude", "codex", "opencode", "cursor"]);
|
|
40
|
+
runtimeAgentIdSchema = z.enum(["claude", "codex", "opencode", "cursor", "mimo"]);
|
|
41
41
|
effortLevelSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
42
42
|
agentModelChoiceSchema = z.object({
|
|
43
43
|
agentId: runtimeAgentIdSchema.default("claude"),
|
|
@@ -2134,7 +2134,7 @@ registerTool(
|
|
|
2134
2134
|
id: z2.string().describe("Unique pair ID within this slot"),
|
|
2135
2135
|
level: z2.enum(["minimal", "low", "medium", "high", "max"]).describe("Capability level. The card's active level selects which pair runs."),
|
|
2136
2136
|
isFree: z2.boolean().describe("Whether this pair uses a zero-cost model"),
|
|
2137
|
-
binary: z2.enum(["claude", "codex", "opencode", "cursor"]).describe("Agent binary: claude / codex / opencode / cursor."),
|
|
2137
|
+
binary: z2.enum(["claude", "codex", "opencode", "cursor", "mimo"]).describe("Agent binary: claude / codex / opencode / cursor / mimo."),
|
|
2138
2138
|
model: z2.string().nullable().optional().describe("Model override, or null for the agent default (e.g. 'claude-opus-4-6', 'gpt-5.5')."),
|
|
2139
2139
|
effort: z2.enum(["low", "medium", "high", "xhigh", "max"]).nullable().optional().describe("Reasoning effort override, or null for the agent default.")
|
|
2140
2140
|
})
|
|
@@ -2370,12 +2370,12 @@ if (agentSlot === "dev" || agentSlot === "assistant") {
|
|
|
2370
2370
|
registerTool(
|
|
2371
2371
|
"whipped_save_memory",
|
|
2372
2372
|
{
|
|
2373
|
-
description: "Save a durable memory
|
|
2373
|
+
description: "Save a durable memory: a cross-cutting rule or a non-obvious trap that a careful reader of the code would still get wrong. Non-derivability is the bar \u2014 NOT how long it took to find. If the fact lives in one file, the API schema, or a controller (endpoint request/response shapes, query params, column lists, field names, CSS/colour classes, per-page layout), do NOT save it \u2014 that is code, read it when you need it. The test: if your note has to cite the file where the truth lives, the file IS the memory; skip it. Save for conventions, architecture decisions, repo-wide gotchas, or user corrections. Most tasks produce nothing memory-worthy \u2014 that is the expected outcome, not a gap. Keep each memory to ONE focused fact in 1-3 sentences (~60 words max); a multi-paragraph dump is a sign it belongs in the code, not here. Scope 'project' = specific to this repo; 'global' = a fact shareable across projects (e.g. a framework/library convention). The user may review project task-lessons; global lessons go to a review queue.\n\nGlobal memory REQUIRES tags \u2014 it only reaches another project that subscribes to one of its tags. Tag with the framework-qualified form when the knowledge is ecosystem-specific, and the bare form only when the fact is truly tool-level and framework-agnostic: Spoosh used via its React bindings \u2192 'spoosh-react'; a fact about Spoosh itself \u2192 'spoosh'; React hooks \u2192 'react-hook' (not bare 'hook'). Reuse an existing tag from the injected memory's tag list before inventing a near-duplicate.",
|
|
2374
2374
|
inputSchema: {
|
|
2375
2375
|
scope: z2.enum(["project", "global"]).describe("'project' (this repo) or 'global' (shareable across projects)"),
|
|
2376
2376
|
type: z2.enum(["fact", "convention", "decision", "preference", "rule", "lesson", "sharp_edge"]).describe("Kind of memory"),
|
|
2377
2377
|
title: z2.string().describe("Short one-line summary"),
|
|
2378
|
-
content: z2.string().describe("The durable fact, in 1-3 sentences"),
|
|
2378
|
+
content: z2.string().describe("The durable fact, in 1-3 sentences (~60 words). One fact only \u2014 not a page or endpoint spec."),
|
|
2379
2379
|
sourceType: z2.enum(["user_correction", "explicit_save", "task_lesson"]).default("task_lesson").describe("Why this is being saved \u2014 'user_correction' if the user explicitly told you, else 'task_lesson'"),
|
|
2380
2380
|
importance: z2.number().int().min(1).max(3).optional().describe("1 normal, 2 high, 3 critical"),
|
|
2381
2381
|
tags: z2.array(z2.string()).optional().describe(
|
|
@@ -2441,7 +2441,7 @@ var scheduleShape = {
|
|
|
2441
2441
|
intervalSeconds: z2.number().int().positive().optional().describe("Required when scheduleKind=interval (e.g. 3600 = hourly)"),
|
|
2442
2442
|
cronExpr: z2.string().optional().describe("Required when scheduleKind=calendar, e.g. '0 9 * * 1' = every Monday 09:00"),
|
|
2443
2443
|
timezone: z2.string().optional().describe("IANA timezone, required when scheduleKind=calendar (e.g. 'Asia/Yangon')"),
|
|
2444
|
-
agentBinary: z2.enum(["claude", "codex", "opencode", "cursor"]).optional().describe("Which agent runs this; defaults to claude"),
|
|
2444
|
+
agentBinary: z2.enum(["claude", "codex", "opencode", "cursor", "mimo"]).optional().describe("Which agent runs this; defaults to claude"),
|
|
2445
2445
|
model: z2.string().optional().describe("Model id, e.g. 'claude-opus-4-8' or 'gpt-5.5'"),
|
|
2446
2446
|
effort: z2.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
|
|
2447
2447
|
enabled: z2.boolean().optional()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 013 add 'mimo' to the agent_id CHECK constraint
|
|
3
|
+
--
|
|
4
|
+
-- mimo (mimocode) is a new supported agent binary. The agent_id columns on
|
|
5
|
+
-- `cards` and `terminal_sessions` carry a CHECK constraint that only allowed
|
|
6
|
+
-- claude/codex/opencode/cursor, so persisting a mimo card or terminal session
|
|
7
|
+
-- failed with "CHECK constraint failed: agent_id IN (...)".
|
|
8
|
+
--
|
|
9
|
+
-- SQLite can't alter a CHECK constraint in place, so each table is rebuilt with
|
|
10
|
+
-- the widened constraint (the standard recreate-and-copy procedure, as in 003).
|
|
11
|
+
-- The migration runner disables foreign_keys for the duration of migrations, so
|
|
12
|
+
-- dropping the `cards` parent here does NOT cascade-delete its many child tables
|
|
13
|
+
-- (activity_log, review_comments, terminal_sessions, card_subtasks, …).
|
|
14
|
+
-- =====================================================================
|
|
15
|
+
|
|
16
|
+
-- ── cards: rebuild with widened agent_id CHECK ──────────────────────────────
|
|
17
|
+
CREATE TABLE cards_new (
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
workspace_id TEXT NOT NULL,
|
|
20
|
+
description TEXT NOT NULL,
|
|
21
|
+
description_attachments_json TEXT NOT NULL DEFAULT '[]',
|
|
22
|
+
column_id TEXT NOT NULL CHECK (column_id IN (
|
|
23
|
+
'todo','in_progress','reopened',
|
|
24
|
+
'ready_for_review','blocked','done'
|
|
25
|
+
)),
|
|
26
|
+
column_position INTEGER NOT NULL,
|
|
27
|
+
type TEXT NOT NULL DEFAULT 'task' CHECK (type IN ('task','story','subtask')),
|
|
28
|
+
ready_for_dev INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
agent_id TEXT CHECK (agent_id IN ('claude','codex','opencode','cursor','mimo')),
|
|
30
|
+
priority TEXT CHECK (priority IN ('urgent','high','medium','low')),
|
|
31
|
+
auto_fix_attempts INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
base_ref TEXT NOT NULL,
|
|
33
|
+
workflow_id TEXT,
|
|
34
|
+
github_issue_url TEXT,
|
|
35
|
+
pr_json TEXT,
|
|
36
|
+
jira_key TEXT,
|
|
37
|
+
jira_url TEXT,
|
|
38
|
+
github_comment_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
39
|
+
worktree_path TEXT,
|
|
40
|
+
branch_name TEXT,
|
|
41
|
+
slack_message_ts TEXT,
|
|
42
|
+
slack_channel_id TEXT,
|
|
43
|
+
created_at INTEGER NOT NULL,
|
|
44
|
+
updated_at INTEGER NOT NULL,
|
|
45
|
+
depends_on_id TEXT REFERENCES cards(id) ON DELETE SET NULL,
|
|
46
|
+
plan TEXT,
|
|
47
|
+
active_level TEXT NOT NULL DEFAULT 'medium',
|
|
48
|
+
model_config_json TEXT,
|
|
49
|
+
FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
INSERT INTO cards_new (
|
|
53
|
+
id, workspace_id, description, description_attachments_json, column_id, column_position,
|
|
54
|
+
type, ready_for_dev, agent_id, priority, auto_fix_attempts, base_ref, workflow_id,
|
|
55
|
+
github_issue_url, pr_json, jira_key, jira_url, github_comment_ids_json, worktree_path,
|
|
56
|
+
branch_name, slack_message_ts, slack_channel_id, created_at, updated_at, depends_on_id,
|
|
57
|
+
plan, active_level, model_config_json
|
|
58
|
+
)
|
|
59
|
+
SELECT
|
|
60
|
+
id, workspace_id, description, description_attachments_json, column_id, column_position,
|
|
61
|
+
type, ready_for_dev, agent_id, priority, auto_fix_attempts, base_ref, workflow_id,
|
|
62
|
+
github_issue_url, pr_json, jira_key, jira_url, github_comment_ids_json, worktree_path,
|
|
63
|
+
branch_name, slack_message_ts, slack_channel_id, created_at, updated_at, depends_on_id,
|
|
64
|
+
plan, active_level, model_config_json
|
|
65
|
+
FROM cards;
|
|
66
|
+
|
|
67
|
+
DROP INDEX IF EXISTS idx_cards_workspace_column;
|
|
68
|
+
DROP INDEX IF EXISTS idx_cards_workflow;
|
|
69
|
+
DROP TABLE cards;
|
|
70
|
+
ALTER TABLE cards_new RENAME TO cards;
|
|
71
|
+
CREATE INDEX idx_cards_workspace_column ON cards(workspace_id, column_id, column_position);
|
|
72
|
+
CREATE INDEX idx_cards_workflow ON cards(workflow_id);
|
|
73
|
+
|
|
74
|
+
-- ── terminal_sessions: rebuild with widened agent_id CHECK ──────────────────
|
|
75
|
+
CREATE TABLE terminal_sessions_new (
|
|
76
|
+
card_id TEXT NOT NULL,
|
|
77
|
+
stream_id TEXT NOT NULL,
|
|
78
|
+
type TEXT NOT NULL,
|
|
79
|
+
started_at INTEGER NOT NULL,
|
|
80
|
+
ended_at INTEGER,
|
|
81
|
+
agent_id TEXT CHECK (agent_id IN ('claude','codex','opencode','cursor','mimo')),
|
|
82
|
+
state TEXT CHECK (state IN ('running','stopped','completed','failed','killed')),
|
|
83
|
+
PRIMARY KEY (card_id, stream_id),
|
|
84
|
+
FOREIGN KEY (card_id) REFERENCES cards(id) ON DELETE CASCADE
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
INSERT INTO terminal_sessions_new (card_id, stream_id, type, started_at, ended_at, agent_id, state)
|
|
88
|
+
SELECT card_id, stream_id, type, started_at, ended_at, agent_id, state FROM terminal_sessions;
|
|
89
|
+
|
|
90
|
+
DROP INDEX IF EXISTS idx_terminal_sessions_card;
|
|
91
|
+
DROP TABLE terminal_sessions;
|
|
92
|
+
ALTER TABLE terminal_sessions_new RENAME TO terminal_sessions;
|
|
93
|
+
CREATE INDEX idx_terminal_sessions_card ON terminal_sessions(card_id);
|
|
@@ -5115,6 +5115,10 @@
|
|
|
5115
5115
|
border-color: oklab(70.4871% .125896 .137895 / .25);
|
|
5116
5116
|
}
|
|
5117
5117
|
|
|
5118
|
+
.border-\[\#fb8147\]\/25 {
|
|
5119
|
+
border-color: oklab(73.1647% .118121 .115767 / .25);
|
|
5120
|
+
}
|
|
5121
|
+
|
|
5118
5122
|
.border-\[var\(--color-border-secondary\)\] {
|
|
5119
5123
|
border-color: var(--color-border-secondary);
|
|
5120
5124
|
}
|
|
@@ -5509,6 +5513,14 @@
|
|
|
5509
5513
|
background-color: oklab(70.4871% .125896 .137895 / .1);
|
|
5510
5514
|
}
|
|
5511
5515
|
|
|
5516
|
+
.bg-\[\#fb8147\] {
|
|
5517
|
+
background-color: #fb8147;
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5520
|
+
.bg-\[\#fb8147\]\/10 {
|
|
5521
|
+
background-color: oklab(73.1647% .118121 .115767 / .1);
|
|
5522
|
+
}
|
|
5523
|
+
|
|
5512
5524
|
.bg-amber-400\/10 {
|
|
5513
5525
|
background-color: #fcbb001a;
|
|
5514
5526
|
}
|
|
@@ -6472,6 +6484,10 @@
|
|
|
6472
6484
|
color: #facc15;
|
|
6473
6485
|
}
|
|
6474
6486
|
|
|
6487
|
+
.text-\[\#fb8147\] {
|
|
6488
|
+
color: #fb8147;
|
|
6489
|
+
}
|
|
6490
|
+
|
|
6475
6491
|
.text-\[\#ffd0d2\] {
|
|
6476
6492
|
color: #ffd0d2;
|
|
6477
6493
|
}
|
|
@@ -27169,12 +27169,13 @@ const ZodIssueCode = {
|
|
|
27169
27169
|
function number$1(params) {
|
|
27170
27170
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
27171
27171
|
}
|
|
27172
|
-
const runtimeAgentIdSchema = _enum(["claude", "codex", "opencode", "cursor"]);
|
|
27172
|
+
const runtimeAgentIdSchema = _enum(["claude", "codex", "opencode", "cursor", "mimo"]);
|
|
27173
27173
|
const AGENT_BINARY_OPTIONS = [
|
|
27174
27174
|
{ value: "claude", label: "claude" },
|
|
27175
27175
|
{ value: "codex", label: "codex" },
|
|
27176
27176
|
{ value: "opencode", label: "opencode" },
|
|
27177
|
-
{ value: "cursor", label: "cursor" }
|
|
27177
|
+
{ value: "cursor", label: "cursor" },
|
|
27178
|
+
{ value: "mimo", label: "mimo" }
|
|
27178
27179
|
];
|
|
27179
27180
|
const effortLevelSchema = _enum(["low", "medium", "high", "xhigh", "max"]);
|
|
27180
27181
|
const EFFORT_OPTIONS = [
|
|
@@ -27206,7 +27207,10 @@ const MODEL_OPTIONS = {
|
|
|
27206
27207
|
opencode: [],
|
|
27207
27208
|
// cursor supports many models — no fixed presets.
|
|
27208
27209
|
// The UI fetches the live list via agents.cursorModels and renders a Select.
|
|
27209
|
-
cursor: []
|
|
27210
|
+
cursor: [],
|
|
27211
|
+
// mimo (mimocode) lists models as provider/model strings, like opencode —
|
|
27212
|
+
// no fixed presets; the UI fetches the live list and renders a Select.
|
|
27213
|
+
mimo: []
|
|
27210
27214
|
};
|
|
27211
27215
|
const agentModelChoiceSchema = object({
|
|
27212
27216
|
agentId: runtimeAgentIdSchema.default("claude"),
|
|
@@ -31758,10 +31762,10 @@ function ModelSelect({
|
|
|
31758
31762
|
menuClassName
|
|
31759
31763
|
}) {
|
|
31760
31764
|
const staticOptions = MODEL_OPTIONS[agentId];
|
|
31761
|
-
const isDynamic = agentId === "opencode" || agentId === "cursor";
|
|
31765
|
+
const isDynamic = agentId === "opencode" || agentId === "cursor" || agentId === "mimo";
|
|
31762
31766
|
const modelsRead = useRead(
|
|
31763
31767
|
(api) => api("agents/models").GET({
|
|
31764
|
-
query: { agent: agentId === "cursor" ? "cursor" : "opencode" }
|
|
31768
|
+
query: { agent: agentId === "cursor" ? "cursor" : agentId === "mimo" ? "mimo" : "opencode" }
|
|
31765
31769
|
}),
|
|
31766
31770
|
{ enabled: isDynamic }
|
|
31767
31771
|
);
|
|
@@ -31829,7 +31833,7 @@ function ModelSelect({
|
|
|
31829
31833
|
{
|
|
31830
31834
|
value,
|
|
31831
31835
|
onChange: (e) => onChange(e.target.value),
|
|
31832
|
-
placeholder: agentId === "opencode" ? "e.g. anthropic/claude-opus-4-7" : agentId === "cursor" ? "e.g. claude-opus-4-7-thinking-max" : agentId === "claude" ? "e.g. claude-opus-4-7" : "e.g. gpt-5-codex"
|
|
31836
|
+
placeholder: agentId === "opencode" || agentId === "mimo" ? "e.g. anthropic/claude-opus-4-7" : agentId === "cursor" ? "e.g. claude-opus-4-7-thinking-max" : agentId === "claude" ? "e.g. claude-opus-4-7" : "e.g. gpt-5-codex"
|
|
31833
31837
|
}
|
|
31834
31838
|
)
|
|
31835
31839
|
] });
|
|
@@ -41299,7 +41303,8 @@ function RunBar({ workspaceId }) {
|
|
|
41299
41303
|
claude: { dot: "bg-[#7c6aff]", text: "text-[#7c6aff]", bg: "bg-[#7c6aff]/10" },
|
|
41300
41304
|
codex: { dot: "bg-[#22c55e]", text: "text-[#22c55e]", bg: "bg-[#22c55e]/10" },
|
|
41301
41305
|
cursor: { dot: "bg-[#3b82f6]", text: "text-[#3b82f6]", bg: "bg-[#3b82f6]/10" },
|
|
41302
|
-
opencode: { dot: "bg-[#f97316]", text: "text-[#f97316]", bg: "bg-[#f97316]/10" }
|
|
41306
|
+
opencode: { dot: "bg-[#f97316]", text: "text-[#f97316]", bg: "bg-[#f97316]/10" },
|
|
41307
|
+
mimo: { dot: "bg-[#fb8147]", text: "text-[#fb8147]", bg: "bg-[#fb8147]/10" }
|
|
41303
41308
|
};
|
|
41304
41309
|
const ac2 = colors[cardAgentId] ?? { dot: "bg-gray-500", text: "text-gray-400", bg: "bg-gray-500/10" };
|
|
41305
41310
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
@@ -50067,6 +50072,13 @@ const AGENT_DISPLAY = {
|
|
|
50067
50072
|
bg: "bg-[#f97316]/10",
|
|
50068
50073
|
border: "border-[#f97316]/25",
|
|
50069
50074
|
dotColor: "bg-[#f97316]"
|
|
50075
|
+
},
|
|
50076
|
+
mimo: {
|
|
50077
|
+
label: "MiMo",
|
|
50078
|
+
color: "text-[#fb8147]",
|
|
50079
|
+
bg: "bg-[#fb8147]/10",
|
|
50080
|
+
border: "border-[#fb8147]/25",
|
|
50081
|
+
dotColor: "bg-[#fb8147]"
|
|
50070
50082
|
}
|
|
50071
50083
|
};
|
|
50072
50084
|
function formatElapsed(sec) {
|
|
@@ -75916,7 +75928,8 @@ const AGENT_COLORS = {
|
|
|
75916
75928
|
claude: { dot: "bg-[#7c6aff]", text: "text-[#7c6aff]", bg: "bg-[#7c6aff]/10" },
|
|
75917
75929
|
codex: { dot: "bg-[#22c55e]", text: "text-[#22c55e]", bg: "bg-[#22c55e]/10" },
|
|
75918
75930
|
cursor: { dot: "bg-[#3b82f6]", text: "text-[#3b82f6]", bg: "bg-[#3b82f6]/10" },
|
|
75919
|
-
opencode: { dot: "bg-[#f97316]", text: "text-[#f97316]", bg: "bg-[#f97316]/10" }
|
|
75931
|
+
opencode: { dot: "bg-[#f97316]", text: "text-[#f97316]", bg: "bg-[#f97316]/10" },
|
|
75932
|
+
mimo: { dot: "bg-[#fb8147]", text: "text-[#fb8147]", bg: "bg-[#fb8147]/10" }
|
|
75920
75933
|
};
|
|
75921
75934
|
const PRIORITY_STYLES = {
|
|
75922
75935
|
urgent: "text-red-400 bg-red-400/10",
|
package/dist/web-ui/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>whipped</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DG3DMON0.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C-IzbCaE.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whipped",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Autonomous AI agent kanban board for Claude, Codex, Opencode, and Cursor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -14,20 +14,6 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=22"
|
|
16
16
|
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"clean": "rm -rf dist",
|
|
19
|
-
"build": "pnpm clean && pnpm web:build && node scripts/build.mjs",
|
|
20
|
-
"prepublishOnly": "pnpm build",
|
|
21
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
22
|
-
"dev": "NODE_ENV=development tsx src/cli.ts",
|
|
23
|
-
"dev:full": "node scripts/dev-full.mjs",
|
|
24
|
-
"web:dev": "pnpm --filter @whipped/web dev",
|
|
25
|
-
"web:build": "pnpm --filter @whipped/web build",
|
|
26
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
27
|
-
"web:typecheck": "pnpm --filter @whipped/web typecheck",
|
|
28
|
-
"lint": "biome lint src web-ui/src",
|
|
29
|
-
"format": "biome format --write ."
|
|
30
|
-
},
|
|
31
17
|
"dependencies": {
|
|
32
18
|
"@hono/zod-validator": "^0.8.0",
|
|
33
19
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -60,14 +46,17 @@
|
|
|
60
46
|
"tsx": "^4.20.3",
|
|
61
47
|
"typescript": "^5.9.3"
|
|
62
48
|
},
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
"scripts": {
|
|
50
|
+
"clean": "rm -rf dist",
|
|
51
|
+
"build": "pnpm clean && pnpm web:build && node scripts/build.mjs",
|
|
52
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
53
|
+
"dev": "NODE_ENV=development tsx src/cli.ts",
|
|
54
|
+
"dev:full": "node scripts/dev-full.mjs",
|
|
55
|
+
"web:dev": "pnpm --filter @whipped/web dev",
|
|
56
|
+
"web:build": "pnpm --filter @whipped/web build",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
58
|
+
"web:typecheck": "pnpm --filter @whipped/web typecheck",
|
|
59
|
+
"lint": "biome lint src web-ui/src",
|
|
60
|
+
"format": "biome format --write ."
|
|
72
61
|
}
|
|
73
|
-
}
|
|
62
|
+
}
|