whipped 0.2.1 → 0.4.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 CHANGED
@@ -3529,11 +3529,12 @@ function isResumableSessionState(state) {
3529
3529
  function normalizeTag(raw2) {
3530
3530
  return raw2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3531
3531
  }
3532
- var runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
3532
+ var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
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
+ ASSISTANT_AGENT_PREFIX = "__assistant__:";
3537
+ runtimeAgentIdSchema = z.enum(["claude", "codex", "opencode", "cursor", "mimo"]);
3537
3538
  effortLevelSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
3538
3539
  agentModelChoiceSchema = z.object({
3539
3540
  agentId: runtimeAgentIdSchema.default("claude"),
@@ -4266,21 +4267,26 @@ function getDb() {
4266
4267
  function runMigrations(db) {
4267
4268
  const currentVersion = db.pragma("user_version", { simple: true });
4268
4269
  const files = readdirSync(MIGRATIONS_DIR).filter((f2) => /^\d+_.+\.sql$/.test(f2)).sort();
4269
- for (const file of files) {
4270
- const version = Number.parseInt(file.split("_")[0] ?? "0", 10);
4271
- if (!Number.isFinite(version) || version <= currentVersion) continue;
4272
- const sql = readFileSync2(join4(MIGRATIONS_DIR, file), "utf-8");
4273
- logger.info({ file, version }, "Running migration");
4274
- const tx = db.transaction(() => {
4275
- db.exec(sql);
4276
- db.pragma(`user_version = ${version}`);
4277
- });
4278
- try {
4279
- tx();
4280
- } catch (err) {
4281
- logger.error({ err, file }, "Migration failed; rolled back");
4282
- throw new Error(`Migration ${file} failed: ${err.message}`);
4270
+ db.pragma("foreign_keys = OFF");
4271
+ try {
4272
+ for (const file of files) {
4273
+ const version = Number.parseInt(file.split("_")[0] ?? "0", 10);
4274
+ if (!Number.isFinite(version) || version <= currentVersion) continue;
4275
+ const sql = readFileSync2(join4(MIGRATIONS_DIR, file), "utf-8");
4276
+ logger.info({ file, version }, "Running migration");
4277
+ const tx = db.transaction(() => {
4278
+ db.exec(sql);
4279
+ db.pragma(`user_version = ${version}`);
4280
+ });
4281
+ try {
4282
+ tx();
4283
+ } catch (err) {
4284
+ logger.error({ err, file }, "Migration failed; rolled back");
4285
+ throw new Error(`Migration ${file} failed: ${err.message}`);
4286
+ }
4283
4287
  }
4288
+ } finally {
4289
+ db.pragma("foreign_keys = ON");
4284
4290
  }
4285
4291
  }
4286
4292
  var MIGRATIONS_DIR, DEFAULT_DB_PATH, cachedDb;
@@ -13639,13 +13645,40 @@ function buildTaskHookEnv(taskId, workspaceId) {
13639
13645
  [MACHINE_TOKEN_ENV]: getMachineToken() ?? ""
13640
13646
  };
13641
13647
  }
13642
- var OPENCODE_CONFIG_DIR_ENV = "OPENCODE_CONFIG_DIR";
13643
- function getOpencodeConfigDir(id) {
13648
+ var PLUGIN_AGENT_SPECS = {
13649
+ opencode: {
13650
+ configDirEnv: "OPENCODE_CONFIG_DIR",
13651
+ configFile: "opencode.json",
13652
+ dirPrefix: "opencode",
13653
+ pluginHeader: 'import { tool } from "@opencode-ai/plugin"\nimport type { Plugin } from "@opencode-ai/plugin"\n\n',
13654
+ pluginExportType: ": Plugin",
13655
+ toolWrapperOpen: "tool(",
13656
+ toolWrapperClose: ")"
13657
+ },
13658
+ mimo: {
13659
+ configDirEnv: "MIMOCODE_CONFIG_DIR",
13660
+ configFile: "mimocode.json",
13661
+ dirPrefix: "mimo",
13662
+ pluginHeader: "",
13663
+ pluginExportType: "",
13664
+ toolWrapperOpen: "",
13665
+ toolWrapperClose: ""
13666
+ }
13667
+ };
13668
+ function isPluginConfigAgent(agentId) {
13669
+ return agentId in PLUGIN_AGENT_SPECS;
13670
+ }
13671
+ function getPluginAgentConfigDir(agentId, id) {
13644
13672
  const safe = id.replace(/[^a-zA-Z0-9_-]/g, "_");
13645
- return join9(HOOKS_DIR, `opencode-${safe}`);
13673
+ return join9(HOOKS_DIR, `${PLUGIN_AGENT_SPECS[agentId].dirPrefix}-${safe}`);
13674
+ }
13675
+ function pluginAgentConfigDirEnv(agentId, id) {
13676
+ if (!isPluginConfigAgent(agentId)) return {};
13677
+ return { [PLUGIN_AGENT_SPECS[agentId].configDirEnv]: getPluginAgentConfigDir(agentId, id) };
13646
13678
  }
13647
- async function writeOpencodeFiles(id, serverPort, mcpServer, opts = {}) {
13648
- const dir = getOpencodeConfigDir(id);
13679
+ async function writePluginAgentFiles(agentId, id, serverPort, mcpServer, opts = {}) {
13680
+ const spec = PLUGIN_AGENT_SPECS[agentId];
13681
+ const dir = getPluginAgentConfigDir(agentId, id);
13649
13682
  await mkdir(join9(dir, "plugin"), { recursive: true });
13650
13683
  const systemParts = [];
13651
13684
  if (opts.appendSystemPrompt) systemParts.push(opts.appendSystemPrompt);
@@ -13656,15 +13689,12 @@ async function writeOpencodeFiles(id, serverPort, mcpServer, opts = {}) {
13656
13689
  "experimental.chat.system.transform": async (_input, output) => {
13657
13690
  ${systemParts.map((p2) => `output.system.push(${JSON.stringify(p2)})`).join("\n ")}
13658
13691
  },`;
13659
- const plugin = `import { tool } from "@opencode-ai/plugin"
13660
- import type { Plugin } from "@opencode-ai/plugin"
13661
-
13662
- export const WhippedPlugin: Plugin = async () => {
13692
+ const plugin = `${spec.pluginHeader}export const WhippedPlugin${spec.pluginExportType} = async () => {
13663
13693
  const port = ${serverPort}
13664
13694
 
13665
13695
  return {${systemTransformHook}
13666
13696
  tool: {
13667
- task_complete: tool({
13697
+ task_complete: ${spec.toolWrapperOpen}{
13668
13698
  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
13699
  args: {},
13670
13700
  execute: async (_args, _ctx) => {
@@ -13677,15 +13707,15 @@ export const WhippedPlugin: Plugin = async () => {
13677
13707
  }
13678
13708
  return "Task marked as complete."
13679
13709
  }
13680
- })
13710
+ }${spec.toolWrapperClose}
13681
13711
  },
13682
13712
  }
13683
13713
  }
13684
13714
  `;
13685
13715
  const extraMcp = Object.fromEntries(
13686
- Object.entries(opts.extraMcp ?? {}).map(([name, spec]) => [
13716
+ Object.entries(opts.extraMcp ?? {}).map(([name, spec2]) => [
13687
13717
  name,
13688
- { type: "local", command: [spec.command, ...spec.args] }
13718
+ { type: "local", command: [spec2.command, ...spec2.args] }
13689
13719
  ])
13690
13720
  );
13691
13721
  const config = {
@@ -13706,11 +13736,11 @@ export const WhippedPlugin: Plugin = async () => {
13706
13736
  };
13707
13737
  await Promise.all([
13708
13738
  writeFile(join9(dir, "plugin", "whipped.ts"), plugin),
13709
- writeFile(join9(dir, "opencode.json"), JSON.stringify(config, null, 2))
13739
+ writeFile(join9(dir, spec.configFile), JSON.stringify(config, null, 2))
13710
13740
  ]);
13711
13741
  }
13712
- async function cleanupOpencodeFiles(id) {
13713
- await rm(getOpencodeConfigDir(id), { recursive: true, force: true });
13742
+ async function cleanupPluginAgentFiles(agentId, id) {
13743
+ await rm(getPluginAgentConfigDir(agentId, id), { recursive: true, force: true });
13714
13744
  }
13715
13745
  var CURSOR_CONFIG_DIR_ENV = "CURSOR_CONFIG_DIR";
13716
13746
  function getCursorConfigDir(id) {
@@ -14278,6 +14308,7 @@ init_logger();
14278
14308
  init_task_id();
14279
14309
 
14280
14310
  // src/daemon/poller.ts
14311
+ init_runtime_config();
14281
14312
  init_logger();
14282
14313
  init_task_id();
14283
14314
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -18626,6 +18657,8 @@ var BoardPoller = class {
18626
18657
  async poll() {
18627
18658
  const { workspaceId, repoPath, scheduler, stateHub, onCardReadyForReview } = this.options;
18628
18659
  const state = await loadWorkspaceState(workspaceId, repoPath);
18660
+ const effectiveLimit = state.projectConfig.maxParallelTasks ?? (await loadGlobalConfig()).maxParallelTasks;
18661
+ scheduler.setMaxParallelTasks(effectiveLimit);
18629
18662
  const board = state.board;
18630
18663
  const pendingCards = [];
18631
18664
  const todoColumn = board.columns.find((c) => c.id === "todo");
@@ -18902,9 +18935,9 @@ function buildBrowserMcpServer(cardId) {
18902
18935
  }
18903
18936
 
18904
18937
  // src/agents/agent-registry.ts
18905
- function getOpencodeModels() {
18938
+ function getProviderModels(binary) {
18906
18939
  try {
18907
- const result = spawnSync4("opencode", ["models"], {
18940
+ const result = spawnSync4(binary, ["models"], {
18908
18941
  stdio: ["ignore", "pipe", "ignore"],
18909
18942
  timeout: 1e4,
18910
18943
  encoding: "utf-8"
@@ -18916,6 +18949,12 @@ function getOpencodeModels() {
18916
18949
  }
18917
18950
  return [];
18918
18951
  }
18952
+ function getOpencodeModels() {
18953
+ return getProviderModels("opencode");
18954
+ }
18955
+ function getMimoModels() {
18956
+ return getProviderModels("mimo");
18957
+ }
18919
18958
  function getCursorModels() {
18920
18959
  try {
18921
18960
  const result = spawnSync4("agent", ["models"], {
@@ -18959,6 +18998,12 @@ var AGENT_DEFINITIONS = [
18959
18998
  label: "Cursor Agent",
18960
18999
  command: "agent",
18961
19000
  checkCommand: ["agent", "--version"]
19001
+ },
19002
+ {
19003
+ id: "mimo",
19004
+ label: "MiMo Code",
19005
+ command: "mimo",
19006
+ checkCommand: ["mimo", "--version"]
18962
19007
  }
18963
19008
  ];
18964
19009
  function isCommandAvailable(args) {
@@ -19027,24 +19072,29 @@ function buildAgentArgs(agentId, prompt, ctx = {}) {
19027
19072
  }
19028
19073
  return args;
19029
19074
  }
19030
- case "opencode": {
19075
+ // mimo (mimocode) is an opencode fork with an identical CLI surface
19076
+ // (`run`/`--agent build`/`-m`/`--prompt`/`--variant`), so it shares this branch.
19077
+ case "opencode":
19078
+ case "mimo": {
19031
19079
  if (mode === "print") {
19032
19080
  const args2 = ["run", "--agent", "build"];
19081
+ if (agentId === "mimo") args2.push("--never-ask", "--trust");
19033
19082
  if (ctx.model) args2.push("-m", ctx.model);
19034
19083
  if (ctx.effort) {
19035
- const effortMap = {
19084
+ const OPENCODE_EFFORT_VARIANT = {
19036
19085
  low: "minimal",
19037
19086
  medium: "low",
19038
19087
  high: "medium",
19039
19088
  xhigh: "high",
19040
19089
  max: "max"
19041
19090
  };
19042
- args2.push("--variant", effortMap[ctx.effort]);
19091
+ args2.push("--variant", agentId === "mimo" ? ctx.effort : OPENCODE_EFFORT_VARIANT[ctx.effort]);
19043
19092
  }
19044
19093
  if (prompt.trim()) args2.push(prompt);
19045
19094
  return args2;
19046
19095
  }
19047
19096
  const args = ["--agent", "build"];
19097
+ if (agentId === "mimo") args.push("--never-ask", "--trust");
19048
19098
  if (ctx.model) args.push("-m", ctx.model);
19049
19099
  if (prompt.trim()) args.push("--prompt", prompt);
19050
19100
  return args;
@@ -19966,38 +20016,6 @@ function markRecurringRan(recurringAgentId) {
19966
20016
  getDb().prepare("UPDATE recurring_agents SET last_run_at = ?, next_run_at = ? WHERE id = ?").run(now, nextRunAt, recurringAgentId);
19967
20017
  }
19968
20018
 
19969
- // src/daemon/recurring-agent-scheduler.ts
19970
- init_workspace_state();
19971
- init_workspace_state();
19972
-
19973
- // src/daemon/scheduler.ts
19974
- import { spawn as spawn5 } from "node:child_process";
19975
- import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync9 } from "node:fs";
19976
- import { unlink as unlink2 } from "node:fs/promises";
19977
- import { dirname as dirname5, join as join16, resolve as resolve2 } from "node:path";
19978
- import { fileURLToPath as fileURLToPath3 } from "node:url";
19979
- init_api_contract();
19980
- init_logger();
19981
-
19982
- // src/core/prompt-resolver.ts
19983
- init_logger();
19984
- import { readFileSync as readFileSync5 } from "node:fs";
19985
- import { isAbsolute, join as join14 } from "node:path";
19986
- function resolvePromptText(prompt, repoPath) {
19987
- if (!prompt) return "";
19988
- if (prompt.source === "inline") return prompt.text;
19989
- const path2 = isAbsolute(prompt.path) ? prompt.path : join14(repoPath, prompt.path);
19990
- try {
19991
- return readFileSync5(path2, "utf-8");
19992
- } catch (err) {
19993
- logger.warn({ err: err.message, path: path2 }, "Slot prompt file unreadable \u2014 falling back to empty prompt");
19994
- return "";
19995
- }
19996
- }
19997
-
19998
- // src/daemon/scheduler.ts
19999
- init_task_id();
20000
-
20001
20019
  // src/state/memory-store.ts
20002
20020
  init_api_contract();
20003
20021
  init_task_id();
@@ -20115,6 +20133,7 @@ function createMemory(input) {
20115
20133
  throw new Error("global-scoped memory requires at least one tag");
20116
20134
  }
20117
20135
  const originWorkspaceId = input.originWorkspaceId ?? workspaceId;
20136
+ const originCardId = input.originCardId && db.prepare("SELECT 1 FROM cards WHERE id = ?").get(input.originCardId) ? input.originCardId : null;
20118
20137
  const tx = db.transaction(() => {
20119
20138
  db.prepare(
20120
20139
  `INSERT INTO memories (
@@ -20131,7 +20150,7 @@ function createMemory(input) {
20131
20150
  input.content,
20132
20151
  input.sourceType,
20133
20152
  input.importance ?? 1,
20134
- input.originCardId ?? null,
20153
+ originCardId,
20135
20154
  input.originAgent ? JSON.stringify(input.originAgent) : null,
20136
20155
  input.status ?? "approved",
20137
20156
  now,
@@ -20255,7 +20274,7 @@ function searchMemories(query, workspaceId, limit = 20) {
20255
20274
  ).all({ q: ftsQuery, ws: workspaceId, limit });
20256
20275
  return hydrate(rows.map(rowToMemory));
20257
20276
  }
20258
- function buildMemoryContext(workspaceId, memoryLimit = 40) {
20277
+ function buildMemoryContext(workspaceId, memoryLimit = 40, opts) {
20259
20278
  const sections = [];
20260
20279
  const fmt = (m2) => {
20261
20280
  const tagSuffix = m2.tags.length > 0 ? ` _(tags: ${m2.tags.join(", ")})_` : "";
@@ -20273,18 +20292,50 @@ ${projectMem.map(fmt).join("\n")}`);
20273
20292
  ${globalMem.map(fmt).join("\n")}`);
20274
20293
  }
20275
20294
  if (sections.length === 0) return "";
20295
+ const readOnly = opts?.readOnly ?? false;
20276
20296
  const knownTags = listTags();
20277
- const tagLine = knownTags.length > 0 ? `
20297
+ const tagLine = !readOnly && knownTags.length > 0 ? `
20278
20298
 
20279
20299
  Existing tags (reuse before inventing new ones): ${knownTags.join(", ")}.` : "";
20300
+ const recallLine = readOnly ? "Use `whipped_search_memory` / `whipped_get_memory` to recall more." : "Use `whipped_search_memory` / `whipped_get_memory` to recall more, and `whipped_update_memory` to correct an entry that's now wrong.";
20280
20301
  return [
20281
20302
  "## Memory",
20282
- `This is whipped's persistent project memory \u2014 durable knowledge from past work. Each entry is prefixed with its id. Use \`whipped_search_memory\` / \`whipped_get_memory\` to recall more, and \`whipped_update_memory\` to correct an entry that's now wrong. Treat these as hints, not gospel: if a memory references a file, symbol, or rule, verify it still holds before relying on it.${tagLine}`,
20303
+ `This is whipped's persistent project memory \u2014 durable knowledge from past work. Each entry is prefixed with its id. ${recallLine} Treat these as hints, not gospel: if a memory references a file, symbol, or rule, verify it still holds before relying on it.${tagLine}`,
20283
20304
  ...sections
20284
20305
  ].join("\n\n");
20285
20306
  }
20286
20307
 
20308
+ // src/daemon/recurring-agent-scheduler.ts
20309
+ init_workspace_state();
20310
+ init_workspace_state();
20311
+
20287
20312
  // src/daemon/scheduler.ts
20313
+ import { spawn as spawn5 } from "node:child_process";
20314
+ import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync9 } from "node:fs";
20315
+ import { unlink as unlink2 } from "node:fs/promises";
20316
+ import { dirname as dirname5, join as join16, resolve as resolve2 } from "node:path";
20317
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
20318
+ init_api_contract();
20319
+ init_logger();
20320
+
20321
+ // src/core/prompt-resolver.ts
20322
+ init_logger();
20323
+ import { readFileSync as readFileSync5 } from "node:fs";
20324
+ import { isAbsolute, join as join14 } from "node:path";
20325
+ function resolvePromptText(prompt, repoPath) {
20326
+ if (!prompt) return "";
20327
+ if (prompt.source === "inline") return prompt.text;
20328
+ const path2 = isAbsolute(prompt.path) ? prompt.path : join14(repoPath, prompt.path);
20329
+ try {
20330
+ return readFileSync5(path2, "utf-8");
20331
+ } catch (err) {
20332
+ logger.warn({ err: err.message, path: path2 }, "Slot prompt file unreadable \u2014 falling back to empty prompt");
20333
+ return "";
20334
+ }
20335
+ }
20336
+
20337
+ // src/daemon/scheduler.ts
20338
+ init_task_id();
20288
20339
  init_workspace_state();
20289
20340
 
20290
20341
  // src/daemon/review-pipeline.ts
@@ -20478,9 +20529,9 @@ ${rawSystemPrompt}` : rawSystemPrompt;
20478
20529
 
20479
20530
  After calling \`kanban_add_comment\`, call the \`task_complete\` MCP tool to signal that you are done.` : withMemory;
20480
20531
  const triggerWord = getSlotTriggerWord(slot.type);
20481
- const mcpConfigPath = agentBinary !== "opencode" && agentBinary !== "cursor" ? getMcpConfigPath(streamId) : void 0;
20482
- const hookServerPort = agentBinary === "codex" || agentBinary === "opencode" || agentBinary === "cursor" ? getServerPort(options.serverUrl) : void 0;
20483
- const mcpServer = agentBinary === "codex" || agentBinary === "opencode" || agentBinary === "cursor" ? buildWhippedMcpServerSpec(options.mcpBinary, options.serverUrl, workspaceId, agentBinary) : void 0;
20532
+ const mcpConfigPath = !isPluginConfigAgent(agentBinary) && agentBinary !== "cursor" ? getMcpConfigPath(streamId) : void 0;
20533
+ const hookServerPort = agentBinary === "codex" || isPluginConfigAgent(agentBinary) || agentBinary === "cursor" ? getServerPort(options.serverUrl) : void 0;
20534
+ const mcpServer = agentBinary === "codex" || isPluginConfigAgent(agentBinary) || agentBinary === "cursor" ? buildWhippedMcpServerSpec(options.mcpBinary, options.serverUrl, workspaceId, agentBinary) : void 0;
20484
20535
  const browserMcp = browserEnabled ? buildBrowserMcpServer(card.id) : void 0;
20485
20536
  const browserMcpSpec = browserMcp ? { command: browserMcp.command, args: browserMcp.args } : void 0;
20486
20537
  const extraMcpServers = browserMcpSpec ? { [PLAYWRIGHT_MCP_SERVER_NAME]: browserMcpSpec } : void 0;
@@ -20789,9 +20840,11 @@ async function handleReviewSuccess(card, options) {
20789
20840
  }
20790
20841
  function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, registerStopCallback, registerLiveProcess, mcpConfigPath, appendSystemPrompt, files, secretsEnv, effort, hookServerPort, mcpServer, model, slotType, browserMcpServer) {
20791
20842
  const extraMcp = browserMcpServer ? { [PLAYWRIGHT_MCP_SERVER_NAME]: browserMcpServer } : void 0;
20792
- if (agentId === "opencode" && hookServerPort != null && mcpServer) {
20793
- void writeOpencodeFiles(streamId, hookServerPort, mcpServer, { appendSystemPrompt, extraMcp }).catch(() => {
20794
- });
20843
+ if (isPluginConfigAgent(agentId) && hookServerPort != null && mcpServer) {
20844
+ void writePluginAgentFiles(agentId, streamId, hookServerPort, mcpServer, { appendSystemPrompt, extraMcp }).catch(
20845
+ () => {
20846
+ }
20847
+ );
20795
20848
  }
20796
20849
  if (agentId === "cursor" && hookServerPort != null && mcpServer) {
20797
20850
  void writeCursorConfigFiles(streamId, hookServerPort, mcpServer, extraMcp).catch(() => {
@@ -20806,7 +20859,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
20806
20859
  void saveTerminalBuffer(workspaceId, streamId, output);
20807
20860
  if (mcpConfigPath) unlink(mcpConfigPath).catch(() => {
20808
20861
  });
20809
- if (agentId === "opencode") void cleanupOpencodeFiles(streamId);
20862
+ if (isPluginConfigAgent(agentId)) void cleanupPluginAgentFiles(agentId, streamId);
20810
20863
  if (agentId === "cursor") void cleanupCursorConfigDir(streamId);
20811
20864
  resolve5(output);
20812
20865
  });
@@ -20820,7 +20873,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
20820
20873
  ...buildTaskHookEnv(streamId, workspaceId),
20821
20874
  ...secretsEnv,
20822
20875
  ...slotType ? { WHIPPED_SLOT: slotType } : {},
20823
- ...agentId === "opencode" ? { [OPENCODE_CONFIG_DIR_ENV]: getOpencodeConfigDir(streamId) } : {},
20876
+ ...pluginAgentConfigDirEnv(agentId, streamId),
20824
20877
  ...agentId === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(streamId) } : {}
20825
20878
  },
20826
20879
  hookSettingsPath: agentId === "claude" ? CLAUDE_TASK_SETTINGS_PATH : void 0,
@@ -20828,7 +20881,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
20828
20881
  mcpConfigPath: agentId === "claude" ? mcpConfigPath : void 0,
20829
20882
  mcpServer: agentId === "codex" ? mcpServer : void 0,
20830
20883
  browserMcpServer: agentId === "codex" ? browserMcpServer : void 0,
20831
- appendSystemPrompt: agentId !== "opencode" ? appendSystemPrompt : void 0,
20884
+ appendSystemPrompt: isPluginConfigAgent(agentId) ? void 0 : appendSystemPrompt,
20832
20885
  files: agentId === "claude" ? files : void 0,
20833
20886
  effort,
20834
20887
  model,
@@ -20843,7 +20896,7 @@ function runAgentOnce(agentId, prompt, cwd, workspaceId, streamId, stateHub, reg
20843
20896
  void saveTerminalBuffer(workspaceId, streamId, output);
20844
20897
  if (mcpConfigPath) unlink(mcpConfigPath).catch(() => {
20845
20898
  });
20846
- if (agentId === "opencode") void cleanupOpencodeFiles(streamId);
20899
+ if (isPluginConfigAgent(agentId)) void cleanupPluginAgentFiles(agentId, streamId);
20847
20900
  if (agentId === "cursor") void cleanupCursorConfigDir(streamId);
20848
20901
  resolve5(output);
20849
20902
  }
@@ -21455,9 +21508,9 @@ ${rawSystemPrompt}` : rawSystemPrompt;
21455
21508
  const systemPrompt = agentId === "cursor" ? `${withMemory}
21456
21509
 
21457
21510
  After calling \`kanban_set_plan\`, call the \`task_complete\` MCP tool to signal that you are done.` : withMemory;
21458
- const mcpConfigPath = agentId !== "opencode" && agentId !== "cursor" ? getMcpConfigPath(streamId) : void 0;
21459
- const hookServerPort = agentId === "codex" || agentId === "opencode" || agentId === "cursor" ? getServerPort(options.serverUrl) : void 0;
21460
- const mcpServer = agentId === "codex" || agentId === "opencode" || agentId === "cursor" ? buildWhippedMcpServerSpec(options.mcpBinary, options.serverUrl, workspaceId, agentId) : void 0;
21511
+ const mcpConfigPath = !isPluginConfigAgent(agentId) && agentId !== "cursor" ? getMcpConfigPath(streamId) : void 0;
21512
+ const hookServerPort = agentId === "codex" || isPluginConfigAgent(agentId) || agentId === "cursor" ? getServerPort(options.serverUrl) : void 0;
21513
+ const mcpServer = agentId === "codex" || isPluginConfigAgent(agentId) || agentId === "cursor" ? buildWhippedMcpServerSpec(options.mcpBinary, options.serverUrl, workspaceId, agentId) : void 0;
21461
21514
  if (agentId === "claude" && mcpConfigPath) {
21462
21515
  await writeClaudeMcpConfig(options.mcpBinary, options.serverUrl, workspaceId, agentId, mcpConfigPath).catch(
21463
21516
  () => {
@@ -21658,7 +21711,6 @@ After handling all children, call \`kanban_add_comment\` on the PARENT card (${p
21658
21711
  // src/daemon/scheduler.ts
21659
21712
  var FAST_EXIT_THRESHOLD_MS = 8e3;
21660
21713
  var MAX_RECENT_BUFFERS = 100;
21661
- var ASSISTANT_AGENT_PREFIX = "__assistant__:";
21662
21714
  var TaskScheduler = class {
21663
21715
  constructor(options) {
21664
21716
  this.options = options;
@@ -21742,7 +21794,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
21742
21794
  await writeClaudeAssistantSettings(getMcpServerPath(), serverUrl, workspaceId).catch((err) => {
21743
21795
  logger.warn({ err }, "[scheduler] Failed to write assistant agent MCP settings");
21744
21796
  });
21745
- } else if (agentId === "opencode") {
21797
+ } else if (isPluginConfigAgent(agentId)) {
21746
21798
  const mcpSpec = buildWhippedMcpServerSpec(
21747
21799
  getMcpServerPath(),
21748
21800
  serverUrl,
@@ -21750,9 +21802,11 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
21750
21802
  void 0,
21751
21803
  buildMcpRoleArgs("assistant")
21752
21804
  );
21753
- await writeOpencodeFiles(taskId, getServerPort(serverUrl), mcpSpec, { appendSystemPrompt }).catch((err) => {
21754
- logger.warn({ err }, "[scheduler] Failed to write opencode assistant agent files");
21755
- });
21805
+ await writePluginAgentFiles(agentId, taskId, getServerPort(serverUrl), mcpSpec, { appendSystemPrompt }).catch(
21806
+ (err) => {
21807
+ logger.warn({ err }, `[scheduler] Failed to write ${agentId} assistant agent files`);
21808
+ }
21809
+ );
21756
21810
  } else if (agentId === "cursor") {
21757
21811
  const mcpSpec = buildWhippedMcpServerSpec(
21758
21812
  getMcpServerPath(),
@@ -21778,7 +21832,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
21778
21832
  ...secretsEnv,
21779
21833
  ...buildTaskHookEnv(taskId, workspaceId),
21780
21834
  WHIPPED_SLOT: "assistant",
21781
- ...agentId === "opencode" ? { [OPENCODE_CONFIG_DIR_ENV]: getOpencodeConfigDir(taskId) } : {},
21835
+ ...pluginAgentConfigDirEnv(agentId, taskId),
21782
21836
  ...agentId === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(taskId) } : {}
21783
21837
  },
21784
21838
  mcpConfigPath: agentId === "claude" ? CLAUDE_ASSISTANT_MCP_CONFIG_PATH : void 0,
@@ -21791,7 +21845,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
21791
21845
  ) : void 0,
21792
21846
  model: assistantModel?.model ?? null,
21793
21847
  effort: assistantModel?.effort ?? null,
21794
- appendSystemPrompt: agentId !== "opencode" ? appendSystemPrompt : void 0,
21848
+ appendSystemPrompt: isPluginConfigAgent(agentId) ? void 0 : appendSystemPrompt,
21795
21849
  onOutput: (data) => {
21796
21850
  assistantTask.outputBuffer += data;
21797
21851
  stateHub.broadcastTerminalOutput(workspaceId, taskId, data);
@@ -21824,6 +21878,11 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
21824
21878
  get maxParallelTasks() {
21825
21879
  return this.options.maxParallelTasks;
21826
21880
  }
21881
+ // Config can change at runtime; the poller re-syncs this each tick so a
21882
+ // changed "Max Parallel Tasks" takes effect without restarting the daemon.
21883
+ setMaxParallelTasks(limit) {
21884
+ this.options.maxParallelTasks = limit;
21885
+ }
21827
21886
  canAcceptTask(inFlightCount) {
21828
21887
  const count = inFlightCount ?? this.running.size;
21829
21888
  return count < this.options.maxParallelTasks;
@@ -21959,7 +22018,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
21959
22018
  this.options.onTaskCompleted(taskId);
21960
22019
  return;
21961
22020
  }
21962
- const mcpConfigPath = agentId !== "opencode" && agentId !== "cursor" ? getMcpConfigPath(taskId) : void 0;
22021
+ const mcpConfigPath = !isPluginConfigAgent(agentId) && agentId !== "cursor" ? getMcpConfigPath(taskId) : void 0;
21963
22022
  let sharedBranchName;
21964
22023
  if (hasSharedWorktree) {
21965
22024
  const ownerCard = board.cards[effectiveWorktreeId];
@@ -22094,9 +22153,9 @@ ${devSystemPromptResult.text}`;
22094
22153
  mcpConfigPath
22095
22154
  ).catch(() => {
22096
22155
  });
22097
- } else if (agentId === "opencode") {
22156
+ } else if (isPluginConfigAgent(agentId)) {
22098
22157
  const mcpSpec = buildWhippedMcpServerSpec(getMcpServerPath(), this.options.serverUrl, workspaceId, agentId);
22099
- await writeOpencodeFiles(taskId, getServerPort(this.options.serverUrl), mcpSpec, {
22158
+ await writePluginAgentFiles(agentId, taskId, getServerPort(this.options.serverUrl), mcpSpec, {
22100
22159
  appendSystemPrompt: devSystemPromptResult.text
22101
22160
  }).catch(() => {
22102
22161
  });
@@ -22130,14 +22189,14 @@ ${devSystemPromptResult.text}`;
22130
22189
  ...secretsEnv,
22131
22190
  WHIPPED_SLOT: "dev",
22132
22191
  ...devPair.model ? { WHIPPED_MODEL: devPair.model } : {},
22133
- ...agentId === "opencode" ? { [OPENCODE_CONFIG_DIR_ENV]: getOpencodeConfigDir(taskId) } : {},
22192
+ ...pluginAgentConfigDirEnv(agentId, taskId),
22134
22193
  ...agentId === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(taskId) } : {}
22135
22194
  },
22136
22195
  hookSettingsPath: agentId === "claude" ? CLAUDE_TASK_SETTINGS_PATH : void 0,
22137
22196
  hookServerPort: agentId === "codex" ? getServerPort(this.options.serverUrl) : void 0,
22138
22197
  mcpConfigPath: agentId === "claude" ? mcpConfigPath : void 0,
22139
22198
  mcpServer: agentId === "codex" ? buildWhippedMcpServerSpec(getMcpServerPath(), this.options.serverUrl, workspaceId, agentId) : void 0,
22140
- appendSystemPrompt: agentId !== "opencode" ? agentId === "cursor" ? devSystemPromptResult.text + "\n\n4. Call the `task_complete` MCP tool to signal that the task is complete." : devSystemPromptResult.text : void 0,
22199
+ 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
22200
  files: agentId === "claude" ? devSystemPromptResult.files : void 0,
22142
22201
  effort: devPair.effort ?? void 0,
22143
22202
  model: devPair.model ?? void 0,
@@ -22155,7 +22214,7 @@ ${devSystemPromptResult.text}`;
22155
22214
  if (runningTask.worktreeOwnerId) this.runningSharedWorktrees.delete(runningTask.worktreeOwnerId);
22156
22215
  if (mcpConfigPath) unlink2(mcpConfigPath).catch(() => {
22157
22216
  });
22158
- if (agentId === "opencode") void cleanupOpencodeFiles(taskId);
22217
+ if (isPluginConfigAgent(agentId)) void cleanupPluginAgentFiles(agentId, taskId);
22159
22218
  if (agentId === "cursor") void cleanupCursorConfigDir(taskId);
22160
22219
  if (this.isShuttingDown) return;
22161
22220
  if (this.manuallyStoppedTasks.has(taskId)) {
@@ -22532,9 +22591,9 @@ ${devSystemPromptResult.text}`;
22532
22591
  let outputBuffer = "";
22533
22592
  let hookHandled = false;
22534
22593
  const conflictSystemPrompt = buildConflictResolutionSystemPrompt(card, conflictedFiles, conflictGitInstructions);
22535
- if (defaultAgent === "opencode") {
22594
+ if (isPluginConfigAgent(defaultAgent)) {
22536
22595
  const mcpSpec = buildWhippedMcpServerSpec(getMcpServerPath(), this.options.serverUrl, workspaceId);
22537
- await writeOpencodeFiles(streamId, getServerPort(this.options.serverUrl), mcpSpec, {
22596
+ await writePluginAgentFiles(defaultAgent, streamId, getServerPort(this.options.serverUrl), mcpSpec, {
22538
22597
  appendSystemPrompt: conflictSystemPrompt
22539
22598
  }).catch(() => {
22540
22599
  });
@@ -22551,10 +22610,10 @@ ${devSystemPromptResult.text}`;
22551
22610
  hookServerPort: defaultAgent === "codex" ? getServerPort(this.options.serverUrl) : void 0,
22552
22611
  env: {
22553
22612
  ...buildTaskHookEnv(streamId, workspaceId),
22554
- ...defaultAgent === "opencode" ? { [OPENCODE_CONFIG_DIR_ENV]: getOpencodeConfigDir(streamId) } : {},
22613
+ ...pluginAgentConfigDirEnv(defaultAgent, streamId),
22555
22614
  ...defaultAgent === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(streamId) } : {}
22556
22615
  },
22557
- appendSystemPrompt: defaultAgent !== "opencode" ? conflictSystemPrompt : void 0,
22616
+ appendSystemPrompt: isPluginConfigAgent(defaultAgent) ? void 0 : conflictSystemPrompt,
22558
22617
  onOutput: (data) => {
22559
22618
  outputBuffer += data;
22560
22619
  stateHub.broadcastTerminalOutput(workspaceId, streamId, data);
@@ -22575,7 +22634,7 @@ ${devSystemPromptResult.text}`;
22575
22634
  this.setRecentBuffer(streamId, outputBuffer);
22576
22635
  void saveTerminalBuffer(workspaceId, streamId, outputBuffer);
22577
22636
  void endTerminalSession(workspaceId, card.id, streamId, Date.now(), "completed");
22578
- if (defaultAgent === "opencode") void cleanupOpencodeFiles(streamId);
22637
+ if (isPluginConfigAgent(defaultAgent)) void cleanupPluginAgentFiles(defaultAgent, streamId);
22579
22638
  if (defaultAgent === "cursor") void cleanupCursorConfigDir(streamId);
22580
22639
  proc.kill();
22581
22640
  onComplete(true).catch((err) => logger.error({ err }, `[scheduler] conflict onComplete failed for ${card.id}:`));
@@ -22820,7 +22879,7 @@ var RecurringAgentScheduler = class {
22820
22879
  } catch (err) {
22821
22880
  logger.warn({ err, agentId: agent.id }, "[recurring] failed to load project config");
22822
22881
  }
22823
- const appendSystemPrompt = buildRecurringSystemPrompt(
22882
+ const baseSystemPrompt = buildRecurringSystemPrompt(
22824
22883
  repoPath,
22825
22884
  agent.name,
22826
22885
  agent.instructions,
@@ -22828,6 +22887,10 @@ var RecurringAgentScheduler = class {
22828
22887
  secrets,
22829
22888
  projectSystemPrompt
22830
22889
  );
22890
+ const memContext = buildMemoryContext(workspaceId, 40, { readOnly: true });
22891
+ const appendSystemPrompt = memContext ? `${memContext}
22892
+
22893
+ ${baseSystemPrompt}` : baseSystemPrompt;
22831
22894
  const prompt = buildRecurringPrompt();
22832
22895
  if (agentBinary === "claude" && mcpConfigPath) {
22833
22896
  await writeClaudeMcpConfig(
@@ -22839,10 +22902,11 @@ var RecurringAgentScheduler = class {
22839
22902
  void 0,
22840
22903
  roleArgs
22841
22904
  ).catch((err) => logger.warn({ err }, "[recurring] failed to write claude MCP config"));
22842
- } else if (agentBinary === "opencode") {
22843
- await writeOpencodeFiles(streamId, hookServerPort, mcpServer, { appendSystemPrompt, readOnly: true }).catch(
22844
- (err) => logger.warn({ err }, "[recurring] failed to write opencode files")
22845
- );
22905
+ } else if (isPluginConfigAgent(agentBinary)) {
22906
+ await writePluginAgentFiles(agentBinary, streamId, hookServerPort, mcpServer, {
22907
+ appendSystemPrompt,
22908
+ readOnly: true
22909
+ }).catch((err) => logger.warn({ err }, `[recurring] failed to write ${agentBinary} files`));
22846
22910
  } else if (agentBinary === "cursor") {
22847
22911
  await writeCursorConfigFiles(streamId, hookServerPort, mcpServer).catch(
22848
22912
  (err) => logger.warn({ err }, "[recurring] failed to write cursor config")
@@ -22856,7 +22920,7 @@ var RecurringAgentScheduler = class {
22856
22920
  const cleanup = () => {
22857
22921
  if (mcpConfigPath) unlink3(mcpConfigPath).catch(() => {
22858
22922
  });
22859
- if (agentBinary === "opencode") void cleanupOpencodeFiles(streamId);
22923
+ if (isPluginConfigAgent(agentBinary)) void cleanupPluginAgentFiles(agentBinary, streamId);
22860
22924
  if (agentBinary === "cursor") void cleanupCursorConfigDir(streamId);
22861
22925
  };
22862
22926
  const finish = (status) => {
@@ -22890,18 +22954,18 @@ var RecurringAgentScheduler = class {
22890
22954
  ...buildTaskHookEnv(streamId, workspaceId),
22891
22955
  WHIPPED_SLOT: "recurring",
22892
22956
  ...agent.model.model ? { WHIPPED_MODEL: agent.model.model } : {},
22893
- ...agentBinary === "opencode" ? { [OPENCODE_CONFIG_DIR_ENV]: getOpencodeConfigDir(streamId) } : {},
22957
+ ...pluginAgentConfigDirEnv(agentBinary, streamId),
22894
22958
  ...agentBinary === "cursor" ? { [CURSOR_CONFIG_DIR_ENV]: getCursorConfigDir(streamId) } : {}
22895
22959
  },
22896
22960
  hookSettingsPath: agentBinary === "claude" ? CLAUDE_TASK_SETTINGS_PATH : void 0,
22897
22961
  hookServerPort: agentBinary === "codex" ? hookServerPort : void 0,
22898
22962
  mcpConfigPath,
22899
22963
  mcpServer: agentBinary === "codex" ? mcpServer : void 0,
22900
- appendSystemPrompt: agentBinary !== "opencode" ? appendSystemPrompt : void 0,
22964
+ appendSystemPrompt: isPluginConfigAgent(agentBinary) ? void 0 : appendSystemPrompt,
22901
22965
  model: agent.model.model,
22902
22966
  effort: agent.model.effort,
22903
22967
  // Observer agents are read-only: claude blocks file/shell tools via
22904
- // --disallowedTools, opencode disables them in its config. codex/cursor
22968
+ // --disallowedTools, opencode/mimo disable them in their config. codex/cursor
22905
22969
  // fall back to prompt-only enforcement.
22906
22970
  readOnly: true,
22907
22971
  onOutput: (data) => {
@@ -22957,8 +23021,10 @@ ${journalBlock}
22957
23021
 
22958
23022
  When you finish, call \`update_journal\` with the full updated notes to keep for next time (what you checked, what you filed, what you're watching). It REPLACES the journal, so include everything still relevant.
22959
23023
 
23024
+ If your task is a one-off that is now complete, or the condition you were watching for is resolved and there is nothing left to do on future runs, call \`disable_self\` to stop running on schedule. This only pauses future runs (the user can re-enable you); the current run still finishes normally. Update your journal before disabling.
23025
+
22960
23026
  ## Available tools
22961
- \`kanban_get_board\`, \`kanban_create_card\`, \`kanban_add_comment\`, \`kanban_get_workflows\`, \`whipped_search_memory\`, \`whipped_get_memory\`, \`update_journal\`, plus read-only repo tools (Read, Grep, Glob).`;
23027
+ \`kanban_get_board\`, \`kanban_create_card\`, \`kanban_add_comment\`, \`kanban_get_workflows\`, \`whipped_search_memory\`, \`whipped_get_memory\`, \`update_journal\`, \`disable_self\`, plus read-only repo tools (Read, Grep, Glob).`;
22962
23028
  const secretsSection = buildSecretsSection(secrets);
22963
23029
  if (secretsSection) prompt += `
22964
23030
 
@@ -25531,12 +25597,16 @@ import { z as z4 } from "zod";
25531
25597
 
25532
25598
  // src/api/services/agents-service.ts
25533
25599
  var listAvailableAgents = async () => getAvailableAgents();
25534
- var listModels = async (agent) => agent === "opencode" ? getOpencodeModels().map((m2) => ({ value: m2, label: m2 })) : getCursorModels();
25600
+ var listModels = async (agent) => {
25601
+ if (agent === "cursor") return getCursorModels();
25602
+ const models = agent === "mimo" ? getMimoModels() : getOpencodeModels();
25603
+ return models.map((m2) => ({ value: m2, label: m2 }));
25604
+ };
25535
25605
 
25536
25606
  // src/api/routes/agents.ts
25537
25607
  var agentsController = new Hono2().get("/available", async (c) => {
25538
25608
  return c.json(await listAvailableAgents());
25539
- }).get("/models", zv("query", z4.object({ agent: z4.enum(["opencode", "cursor"]) })), async (c) => {
25609
+ }).get("/models", zv("query", z4.object({ agent: z4.enum(["opencode", "cursor", "mimo"]) })), async (c) => {
25540
25610
  return c.json(await listModels(c.req.valid("query").agent));
25541
25611
  });
25542
25612
 
@@ -25973,21 +26043,22 @@ var deleteReviewCommentService = async (input) => {
25973
26043
  await updateCard(input.workspaceId, input.cardId, { reviewComments: next });
25974
26044
  return { ok: true };
25975
26045
  };
25976
- var submitHumanFeedbackService = async (workspaceId, cardId, comment, attachments) => {
26046
+ var submitHumanFeedbackService = async (workspaceId, cardId, comment, attachments, type, metadata) => {
25977
26047
  const board = await loadBoard(workspaceId);
25978
26048
  const card = board.cards[cardId];
25979
26049
  if (!card) throw NotFoundError("Card");
25980
26050
  const trimmed = comment?.trim();
25981
- const hasContent = trimmed || (attachments?.length ?? 0) > 0;
26051
+ const hasContent = trimmed || (attachments?.length ?? 0) > 0 || metadata != null;
25982
26052
  const updatedComments = hasContent ? [
25983
26053
  ...card.reviewComments ?? [],
25984
26054
  {
25985
26055
  id: generateTaskId(),
25986
- type: "human",
26056
+ type: type ?? "human",
25987
26057
  actor: { type: "human", id: "human" },
25988
26058
  createdAt: Date.now(),
25989
26059
  summary: trimmed ?? "Feedback with attachments",
25990
- attachments: attachments?.length ? attachments : void 0
26060
+ attachments: attachments?.length ? attachments : void 0,
26061
+ ...metadata ? { metadata } : {}
25991
26062
  }
25992
26063
  ] : card.reviewComments ?? [];
25993
26064
  await updateCard(workspaceId, cardId, { reviewComments: updatedComments, autoFixAttempts: 0 });
@@ -26300,13 +26371,15 @@ var cardsController = new Hono2().post("/", zv("json", runtimeCardCreateRequestS
26300
26371
  workspaceId: z5.string(),
26301
26372
  cardId: z5.string(),
26302
26373
  comment: z5.string().optional(),
26303
- attachments: z5.array(reviewAttachmentSchema).optional()
26374
+ attachments: z5.array(reviewAttachmentSchema).optional(),
26375
+ type: z5.string().optional(),
26376
+ metadata: z5.record(z5.string(), z5.unknown()).optional()
26304
26377
  })
26305
26378
  ),
26306
26379
  async (c) => {
26307
26380
  const ctx = c.var.ctx;
26308
- const { workspaceId, cardId, comment, attachments } = c.req.valid("json");
26309
- const result = await submitHumanFeedbackService(workspaceId, cardId, comment, attachments);
26381
+ const { workspaceId, cardId, comment, attachments, type, metadata } = c.req.valid("json");
26382
+ const result = await submitHumanFeedbackService(workspaceId, cardId, comment, attachments, type, metadata);
26310
26383
  ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26311
26384
  if (result.reopenCascade) {
26312
26385
  const scheduler = ctx.getScheduler(workspaceId);
@@ -28565,7 +28638,7 @@ process.on("uncaughtException", (err) => {
28565
28638
  if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
28566
28639
  throw err;
28567
28640
  });
28568
- var VERSION9 = true ? "0.2.1" : "0.0.0-dev";
28641
+ var VERSION9 = true ? "0.4.0" : "0.0.0-dev";
28569
28642
  async function isPortAvailable(port, host) {
28570
28643
  return new Promise((resolve5) => {
28571
28644
  const probe = createServer2();
@@ -33,11 +33,12 @@ function highestWorkflowLevel(workflow) {
33
33
  }
34
34
  return LEVEL_ORDER[bestIdx] ?? "medium";
35
35
  }
36
- var runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
36
+ var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
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
+ ASSISTANT_AGENT_PREFIX = "__assistant__:";
41
+ runtimeAgentIdSchema = z.enum(["claude", "codex", "opencode", "cursor", "mimo"]);
41
42
  effortLevelSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
42
43
  agentModelChoiceSchema = z.object({
43
44
  agentId: runtimeAgentIdSchema.default("claude"),
@@ -1661,7 +1662,8 @@ var RECURRING_OBSERVER_TOOLS = /* @__PURE__ */ new Set([
1661
1662
  "kanban_get_system_prompt",
1662
1663
  "whipped_search_memory",
1663
1664
  "whipped_get_memory",
1664
- "update_journal"
1665
+ "update_journal",
1666
+ "disable_self"
1665
1667
  ]);
1666
1668
  var baseRegisterTool = server.registerTool;
1667
1669
  var registerTool = ((name, ...rest) => {
@@ -2134,7 +2136,7 @@ registerTool(
2134
2136
  id: z2.string().describe("Unique pair ID within this slot"),
2135
2137
  level: z2.enum(["minimal", "low", "medium", "high", "max"]).describe("Capability level. The card's active level selects which pair runs."),
2136
2138
  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."),
2139
+ binary: z2.enum(["claude", "codex", "opencode", "cursor", "mimo"]).describe("Agent binary: claude / codex / opencode / cursor / mimo."),
2138
2140
  model: z2.string().nullable().optional().describe("Model override, or null for the agent default (e.g. 'claude-opus-4-6', 'gpt-5.5')."),
2139
2141
  effort: z2.enum(["low", "medium", "high", "xhigh", "max"]).nullable().optional().describe("Reasoning effort override, or null for the agent default.")
2140
2142
  })
@@ -2322,7 +2324,8 @@ registerTool(
2322
2324
  }
2323
2325
  );
2324
2326
  var agentSlot = process.env.WHIPPED_SLOT ?? "";
2325
- var memoryCardId = process.env.WHIPPED_HOOK_TASK_ID ?? "";
2327
+ var hookTaskId = process.env.WHIPPED_HOOK_TASK_ID ?? "";
2328
+ var memoryCardId = hookTaskId.startsWith(ASSISTANT_AGENT_PREFIX) ? "" : hookTaskId;
2326
2329
  var memoryModel = process.env.WHIPPED_MODEL ?? "";
2327
2330
  registerTool(
2328
2331
  "whipped_search_memory",
@@ -2441,7 +2444,7 @@ var scheduleShape = {
2441
2444
  intervalSeconds: z2.number().int().positive().optional().describe("Required when scheduleKind=interval (e.g. 3600 = hourly)"),
2442
2445
  cronExpr: z2.string().optional().describe("Required when scheduleKind=calendar, e.g. '0 9 * * 1' = every Monday 09:00"),
2443
2446
  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"),
2447
+ agentBinary: z2.enum(["claude", "codex", "opencode", "cursor", "mimo"]).optional().describe("Which agent runs this; defaults to claude"),
2445
2448
  model: z2.string().optional().describe("Model id, e.g. 'claude-opus-4-8' or 'gpt-5.5'"),
2446
2449
  effort: z2.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
2447
2450
  enabled: z2.boolean().optional()
@@ -2547,6 +2550,21 @@ if (mcpRole === "recurring" && recurringAgentId) {
2547
2550
  }
2548
2551
  }
2549
2552
  );
2553
+ registerTool(
2554
+ "disable_self",
2555
+ {
2556
+ description: "Disable yourself so you stop running on schedule. Use this once your assigned task is complete and there is nothing left to watch for \u2014 e.g. a one-off job that has finished, or a condition you were waiting on that is now resolved. This only pauses future runs; the user can re-enable you later. It does not affect the current run, which finishes normally.",
2557
+ inputSchema: {}
2558
+ },
2559
+ async () => {
2560
+ try {
2561
+ await apiMutate("recurring.update", { id: recurringAgentId, enabled: false });
2562
+ return { content: [{ type: "text", text: "Disabled. No further scheduled runs until re-enabled." }] };
2563
+ } catch (err) {
2564
+ return { content: [{ type: "text", text: `Disable failed: ${err.message}` }] };
2565
+ }
2566
+ }
2567
+ );
2550
2568
  }
2551
2569
  var transport = new StdioServerTransport();
2552
2570
  await server.connect(transport);
@@ -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) {
@@ -50987,7 +50999,13 @@ function CommentComposer({ card, workspaceId, onRefresh }) {
50987
50999
  const atts = uploaded.length > 0 ? uploaded : void 0;
50988
51000
  if (requestChanges) {
50989
51001
  await submitHumanFeedbackTrigger({
50990
- body: { workspaceId, cardId: card.id, comment: summaryText || void 0, attachments: atts }
51002
+ body: {
51003
+ workspaceId,
51004
+ cardId: card.id,
51005
+ comment: summaryText || void 0,
51006
+ attachments: atts,
51007
+ ...visualComment ? { type: "visual-comment", metadata: { visualComment } } : {}
51008
+ }
50991
51009
  });
50992
51010
  } else {
50993
51011
  await addReviewCommentTrigger({
@@ -75916,7 +75934,8 @@ const AGENT_COLORS = {
75916
75934
  claude: { dot: "bg-[#7c6aff]", text: "text-[#7c6aff]", bg: "bg-[#7c6aff]/10" },
75917
75935
  codex: { dot: "bg-[#22c55e]", text: "text-[#22c55e]", bg: "bg-[#22c55e]/10" },
75918
75936
  cursor: { dot: "bg-[#3b82f6]", text: "text-[#3b82f6]", bg: "bg-[#3b82f6]/10" },
75919
- opencode: { dot: "bg-[#f97316]", text: "text-[#f97316]", bg: "bg-[#f97316]/10" }
75937
+ opencode: { dot: "bg-[#f97316]", text: "text-[#f97316]", bg: "bg-[#f97316]/10" },
75938
+ mimo: { dot: "bg-[#fb8147]", text: "text-[#fb8147]", bg: "bg-[#fb8147]/10" }
75920
75939
  };
75921
75940
  const PRIORITY_STYLES = {
75922
75941
  urgent: "text-red-400 bg-red-400/10",
@@ -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-Ce4HDOh7.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-Dgu7Q26n.css">
8
+ <script type="module" crossorigin src="/assets/index-C-dcfzmG.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.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Autonomous AI agent kanban board for Claude, Codex, Opencode, and Cursor.",
5
5
  "type": "module",
6
6
  "files": [