@couralabs/claudin 0.1.4 → 0.1.6

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.
Files changed (2) hide show
  1. package/claudin.js +744 -132
  2. package/package.json +1 -1
package/claudin.js CHANGED
@@ -5928,6 +5928,12 @@ var backgroundColorSchema = object({
5928
5928
  a: channelSchema
5929
5929
  });
5930
5930
  var TRANSPARENT_BACKGROUND = { r: 0, g: 0, b: 0, a: 0 };
5931
+ // packages/events/src/models/customTools.ts
5932
+ var CUSTOM_SERVER_NAME = "claudin";
5933
+ var BATCH_EDIT_BASE_NAME = "BatchEdit";
5934
+ var BATCH_EDIT_TOOL_NAME = `mcp__${CUSTOM_SERVER_NAME}__${BATCH_EDIT_BASE_NAME}`;
5935
+ var CUSTOM_PREFIX = `mcp__${CUSTOM_SERVER_NAME}__`;
5936
+ var stripCustomPrefix = (toolName) => toolName.startsWith(CUSTOM_PREFIX) ? toolName.slice(CUSTOM_PREFIX.length) : toolName;
5931
5937
  // packages/events/src/models/fileDiff.ts
5932
5938
  var fileDiffSchema = object({
5933
5939
  path: string2(),
@@ -6064,6 +6070,7 @@ function appendEntry(state, entry) {
6064
6070
  };
6065
6071
  }
6066
6072
  var appendNotice = (state, text) => appendEntry(state, { kind: "notice", text });
6073
+ var appendMarkdownNotice = (state, text) => appendEntry(state, { kind: "notice", text, markdown: true });
6067
6074
 
6068
6075
  // packages/events/src/reducers/reduceHistory.ts
6069
6076
  var NO_MATCH_NOTICE = "No earlier prompt matches";
@@ -6111,6 +6118,7 @@ var agentSessionId = string2();
6111
6118
  var content = sdkValue();
6112
6119
  var agentOptions = sdkValue().optional();
6113
6120
  var effortLevelSchema = _enum(["low", "medium", "high", "xhigh", "max"]);
6121
+ var DEFAULT_EFFORT = "high";
6114
6122
  var permissionModeSchema = _enum(["auto", "acceptEdits", "bypassPermissions", "dontAsk"]);
6115
6123
  var DEFAULT_PERMISSION_MODE = "bypassPermissions";
6116
6124
  var PERMISSION_MODE_LABELS = {
@@ -6145,9 +6153,8 @@ var agentRequestSchemas = [
6145
6153
  options: agentOptions
6146
6154
  }),
6147
6155
  object({ type: literal("agent.prompt.requested"), agentSessionId, content }),
6148
- object({ type: literal("agent.prompt.queue.requested"), agentSessionId, content }),
6149
- object({ type: literal("agent.prompt.steer.requested"), agentSessionId, content }),
6150
6156
  object({ type: literal("agent.prompt.cancel.requested"), agentSessionId }),
6157
+ object({ type: literal("agent.task.stop.requested"), agentSessionId, taskId: string2() }),
6151
6158
  object({
6152
6159
  type: literal("agent.tool.approval.answered"),
6153
6160
  agentSessionId,
@@ -6217,6 +6224,8 @@ var createSessionState = (sessionId) => ({
6217
6224
  models: [],
6218
6225
  sessionList: [],
6219
6226
  queuedTurns: 0,
6227
+ queuedPrompts: [],
6228
+ tasks: [],
6220
6229
  transcript: [],
6221
6230
  entryCounter: 0,
6222
6231
  now: Date.now(),
@@ -6315,10 +6324,11 @@ function describeSystemMessage(message) {
6315
6324
  var OPEN_STATUSES = new Set(["running", "awaiting_approval", "awaiting_answer"]);
6316
6325
  var isToolsEntry = (entry) => entry.kind === "tools";
6317
6326
  var listToolCalls = (state) => state.transcript.filter(isToolsEntry).flatMap((entry) => entry.calls);
6318
- var EDIT_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
6319
- var readEditedPath = ({ input }) => input.file_path ?? input.notebook_path;
6327
+ var EDIT_TOOLS = new Set(["Write", "Edit", "MultiEdit", "mcp__claudin__BatchEdit"]);
6328
+ var listBatchEditPaths = (input) => Array.isArray(input.edits) ? input.edits.map((edit) => edit?.file_path) : [];
6329
+ var readEditedPaths = (call) => call.name === BATCH_EDIT_TOOL_NAME ? listBatchEditPaths(call.input) : EDIT_TOOLS.has(call.name) ? [call.input.file_path ?? call.input.notebook_path] : [];
6320
6330
  var listEditedFiles = (state) => [
6321
- ...new Set(listToolCalls(state).filter((call) => EDIT_TOOLS.has(call.name) && call.status === "completed").map(readEditedPath).filter((path) => typeof path === "string"))
6331
+ ...new Set(listToolCalls(state).filter((call) => call.status === "completed").flatMap(readEditedPaths).filter((path) => typeof path === "string"))
6322
6332
  ];
6323
6333
  function countTurnTools(state) {
6324
6334
  const lastUser = state.transcript.findLastIndex((entry) => entry.kind === "user");
@@ -6517,6 +6527,43 @@ function reduceStreamEvent(state, event) {
6517
6527
  }
6518
6528
  }
6519
6529
 
6530
+ // packages/events/src/reducers/reduceTaskModel.ts
6531
+ var readMessageModel = (message) => {
6532
+ if (message.type === "assistant")
6533
+ return message.message.model;
6534
+ if (message.type !== "stream_event")
6535
+ return;
6536
+ const event = message.event;
6537
+ return event.type === "message_start" ? event.message?.model : undefined;
6538
+ };
6539
+ var readRequestedModel = (state, toolUseId) => {
6540
+ const model = listToolCalls(state).find((call) => call.id === toolUseId)?.input.model;
6541
+ return typeof model === "string" ? model : undefined;
6542
+ };
6543
+ function reduceTaskModel(state, message) {
6544
+ if (message.type !== "assistant" && message.type !== "stream_event")
6545
+ return state;
6546
+ const model = readMessageModel(message);
6547
+ const parentId = message.parent_tool_use_id;
6548
+ const stale = (task) => task.toolUseId === parentId && task.model !== model;
6549
+ if (!model || !parentId || !state.tasks.some(stale))
6550
+ return state;
6551
+ const tasks = state.tasks.map((task) => stale(task) ? { ...task, model } : task);
6552
+ return { ...state, tasks };
6553
+ }
6554
+
6555
+ // packages/events/src/reducers/reduceTaskToolUse.ts
6556
+ function reduceTaskToolUse(state, message) {
6557
+ if (message.type !== "assistant" || !message.parent_tool_use_id)
6558
+ return state;
6559
+ const call = message.message.content.findLast((block) => block.type === "tool_use");
6560
+ if (call?.type !== "tool_use")
6561
+ return state;
6562
+ const lastToolCall = { name: call.name, input: call.input ?? {} };
6563
+ const tasks = state.tasks.map((task) => task.toolUseId === message.parent_tool_use_id ? { ...task, lastToolCall } : task);
6564
+ return { ...state, tasks };
6565
+ }
6566
+
6520
6567
  // packages/events/src/reducers/reduceTaskProgress.ts
6521
6568
  function reduceTaskProgress(state, message) {
6522
6569
  const { tool_use_id: toolUseId, summary } = message;
@@ -6525,6 +6572,58 @@ function reduceTaskProgress(state, message) {
6525
6572
  return updateToolCall(state, toolUseId, (call) => ({ ...call, progress: summary }));
6526
6573
  }
6527
6574
 
6575
+ // packages/events/src/reducers/reduceTasks.ts
6576
+ var FINISHED_STATUSES = ["completed", "failed", "killed"];
6577
+ var isTaskMessage = (message) => message.type === "system" && ["task_started", "task_progress", "task_updated", "task_notification"].includes(message.subtype);
6578
+ var patchTask = (tasks, id, patch) => tasks.map((task) => task.id === id ? patch(task) : task);
6579
+ var startTask = (state, message) => {
6580
+ if (message.ambient || message.skip_transcript)
6581
+ return state.tasks;
6582
+ const task = {
6583
+ id: message.task_id,
6584
+ kind: message.subagent_type ?? message.task_type ?? "task",
6585
+ description: message.description,
6586
+ startedAt: state.now,
6587
+ toolUseId: message.tool_use_id,
6588
+ model: readRequestedModel(state, message.tool_use_id),
6589
+ tokens: 0,
6590
+ paused: false
6591
+ };
6592
+ return [...state.tasks.filter(({ id }) => id !== task.id), task];
6593
+ };
6594
+ var updateTask = (tasks, message) => {
6595
+ const { status, description } = message.patch;
6596
+ if (status && FINISHED_STATUSES.includes(status)) {
6597
+ return tasks.filter(({ id }) => id !== message.task_id);
6598
+ }
6599
+ return patchTask(tasks, message.task_id, (task) => ({
6600
+ ...task,
6601
+ description: description ?? task.description,
6602
+ paused: status ? status === "paused" : task.paused
6603
+ }));
6604
+ };
6605
+ var readTasks = (state, message) => {
6606
+ switch (message.subtype) {
6607
+ case "task_started":
6608
+ return startTask(state, message);
6609
+ case "task_progress":
6610
+ return patchTask(state.tasks, message.task_id, (task) => ({
6611
+ ...task,
6612
+ lastToolName: message.last_tool_name ?? task.lastToolName,
6613
+ summary: message.summary ?? task.summary,
6614
+ tokens: message.usage.total_tokens
6615
+ }));
6616
+ case "task_updated":
6617
+ return updateTask(state.tasks, message);
6618
+ case "task_notification":
6619
+ return state.tasks.filter(({ id }) => id !== message.task_id);
6620
+ }
6621
+ };
6622
+ var reduceTasks = (state, message) => ({
6623
+ ...state,
6624
+ tasks: readTasks(state, message)
6625
+ });
6626
+
6528
6627
  // packages/events/src/reducers/reduceToolResults.ts
6529
6628
  function applyResult(state, block) {
6530
6629
  if (block.type !== "tool_result" || !block.tool_use_id)
@@ -6542,10 +6641,10 @@ function reduceToolResults(state, message) {
6542
6641
 
6543
6642
  // packages/events/src/reducers/reduceAgentMessage.ts
6544
6643
  function reduceAgentMessage(state, message) {
6644
+ if (isTaskMessage(message))
6645
+ return reduceTaskMessage(reduceTasks(state, message), message);
6545
6646
  switch (message.type) {
6546
6647
  case "system":
6547
- if (message.subtype === "task_progress")
6548
- return reduceTaskProgress(state, message);
6549
6648
  if (message.subtype !== "init")
6550
6649
  return reduceNoticeMessage(state, message);
6551
6650
  return {
@@ -6557,9 +6656,12 @@ function reduceAgentMessage(state, message) {
6557
6656
  };
6558
6657
  case "stream_event":
6559
6658
  if (message.parent_tool_use_id)
6560
- return state;
6659
+ return reduceTaskModel(state, message);
6561
6660
  return reduceStreamEvent(state, message.event);
6562
6661
  case "assistant":
6662
+ if (message.parent_tool_use_id) {
6663
+ return reduceTaskToolUse(reduceTaskModel(state, message), message);
6664
+ }
6563
6665
  return reduceAssistant(state, message);
6564
6666
  case "user":
6565
6667
  return isPromptEcho(message) ? reduceCheckpoint(state, message) : reduceToolResults(state, message);
@@ -6571,6 +6673,16 @@ function reduceAgentMessage(state, message) {
6571
6673
  return reduceNoticeMessage(state, message);
6572
6674
  }
6573
6675
  }
6676
+ function reduceTaskMessage(state, message) {
6677
+ if (message.type === "system" && message.subtype === "task_progress") {
6678
+ return reduceTaskProgress(state, message);
6679
+ }
6680
+ if (message.type === "system" && message.subtype === "task_notification") {
6681
+ const text = describeSystemMessage(message);
6682
+ return text ? appendMarkdownNotice(state, text) : state;
6683
+ }
6684
+ return state;
6685
+ }
6574
6686
  function reduceNoticeMessage(state, message) {
6575
6687
  const text = describeSystemMessage(message);
6576
6688
  return text ? appendNotice(state, text) : state;
@@ -6751,6 +6863,23 @@ function changeQuestionCustom(state, text) {
6751
6863
  return question ? withDraft(state, { ...question, custom: text }) : state;
6752
6864
  }
6753
6865
 
6866
+ // packages/events/src/reducers/reduceTheme.ts
6867
+ var applyLoadedTheme = (state, theme = state.theme) => ({
6868
+ ...state,
6869
+ theme
6870
+ });
6871
+ var applySavedTheme = (state, theme) => appendNotice({ ...state, theme }, `Theme set to ${theme}`);
6872
+
6873
+ // packages/events/src/reducers/reducePromptQueue.ts
6874
+ var queuePrompt = (state, content) => ({
6875
+ ...state,
6876
+ queuedPrompts: [...state.queuedPrompts, content]
6877
+ });
6878
+ var dequeuePrompt = (state) => ({
6879
+ ...state,
6880
+ queuedPrompts: state.queuedPrompts.slice(1)
6881
+ });
6882
+
6754
6883
  // packages/events/src/reducers/reduceToast.ts
6755
6884
  var withToast = (state, toast) => ({
6756
6885
  ...state,
@@ -6778,6 +6907,8 @@ var pickerSize = (state, kind) => {
6778
6907
  return state.models.length;
6779
6908
  if (kind === "theme")
6780
6909
  return THEME_NAMES.length;
6910
+ if (kind === "effort")
6911
+ return effortLevelSchema.options.length;
6781
6912
  if (kind === "rewind")
6782
6913
  return listCheckpoints(state).length;
6783
6914
  return state.sessionList.length;
@@ -6796,6 +6927,11 @@ var openThemePicker = (state) => {
6796
6927
  const selected = Math.max(THEME_NAMES.indexOf(state.theme), 0);
6797
6928
  return { ...state, ui: { ...state.ui, picker: { kind: "theme", selected } } };
6798
6929
  };
6930
+ var openEffortPicker = (state) => {
6931
+ const current = state.effort || DEFAULT_EFFORT;
6932
+ const selected = Math.max(effortLevelSchema.options.findIndex((level) => level === current), 0);
6933
+ return { ...state, ui: { ...state.ui, picker: { kind: "effort", selected } } };
6934
+ };
6799
6935
  function openRewindPicker(state) {
6800
6936
  const size = listCheckpoints(state).length;
6801
6937
  if (size === 0)
@@ -6820,8 +6956,12 @@ function reduceUi(state, event) {
6820
6956
  return { ...state, ui: { ...state.ui, picker: null } };
6821
6957
  case "ui.theme.picker.opened":
6822
6958
  return openThemePicker(state);
6823
- case "ui.theme.selected":
6824
- return appendNotice({ ...state, theme: event.theme }, `Theme set to ${event.theme}`);
6959
+ case "ui.effort.picker.opened":
6960
+ return openEffortPicker(state);
6961
+ case "ui.prompt.queued":
6962
+ return queuePrompt(state, event.content);
6963
+ case "ui.prompt.dequeued":
6964
+ return dequeuePrompt(state);
6825
6965
  case "ui.rewind.picker.opened":
6826
6966
  return openRewindPicker(state);
6827
6967
  case "ui.history.moved":
@@ -6855,6 +6995,7 @@ function reduceUi(state, event) {
6855
6995
  case "ui.question.recorded":
6856
6996
  return recordQuestionAnswer(state, event);
6857
6997
  case "ui.exit.requested":
6998
+ case "ui.title.set":
6858
6999
  return state;
6859
7000
  case "ui.notice.posted":
6860
7001
  return appendNotice(state, event.text);
@@ -6899,9 +7040,6 @@ var reduceTimedEvent = (state, event, at) => reduceSessionState({ ...state, now:
6899
7040
  function reduceSessionState(state, event) {
6900
7041
  switch (event.type) {
6901
7042
  case "agent.prompt.requested":
6902
- return reducePrompt(state, event.content, false);
6903
- case "agent.prompt.queue.requested":
6904
- case "agent.prompt.steer.requested":
6905
7043
  return reducePrompt(state, event.content, state.status !== "idle");
6906
7044
  case "agent.message.received":
6907
7045
  return reduceAgentMessage(state, event.message);
@@ -6978,9 +7116,11 @@ function reduceSessionState(state, event) {
6978
7116
  case "workspace.info.received":
6979
7117
  return { ...state, cwd: event.cwd, gitBranch: event.gitBranch, gitStats: event.gitStats };
6980
7118
  case "options.loaded":
6981
- return applyLoadedBackground(state, event.background);
7119
+ return applyLoadedTheme(applyLoadedBackground(state, event.background), event.theme);
6982
7120
  case "options.background.saved":
6983
7121
  return applySavedBackground(state, event.color);
7122
+ case "options.theme.saved":
7123
+ return applySavedTheme(state, event.theme);
6984
7124
  case "workspace.files.received":
6985
7125
  return updateFileMatches(state, event.query, event.matches);
6986
7126
  default:
@@ -6993,13 +7133,22 @@ var optionsRequestSchemas = [
6993
7133
  object({
6994
7134
  type: literal("options.background.save.requested"),
6995
7135
  color: backgroundColorSchema
6996
- })
7136
+ }),
7137
+ object({ type: literal("options.theme.save.requested"), theme: themeNameSchema })
6997
7138
  ];
6998
7139
  var optionsResponseSchemas = [
6999
- object({ type: literal("options.loaded"), background: backgroundColorSchema.optional() }),
7000
- object({ type: literal("options.background.saved"), color: backgroundColorSchema })
7140
+ object({
7141
+ type: literal("options.loaded"),
7142
+ background: backgroundColorSchema.optional(),
7143
+ theme: themeNameSchema.optional()
7144
+ }),
7145
+ object({ type: literal("options.background.saved"), color: backgroundColorSchema }),
7146
+ object({ type: literal("options.theme.saved"), theme: themeNameSchema })
7001
7147
  ];
7002
- var optionsFileSchema = looseObject({ background: backgroundColorSchema.optional() });
7148
+ var optionsFileSchema = looseObject({
7149
+ background: backgroundColorSchema.optional(),
7150
+ theme: themeNameSchema.optional()
7151
+ });
7003
7152
  // packages/events/src/requests/sessionRequestEvents.ts
7004
7153
  var sessionRequestSchemas = [
7005
7154
  object({ type: literal("session.create.requested") }),
@@ -7060,7 +7209,6 @@ var agentResponseSchemas = [
7060
7209
  }),
7061
7210
  object({ type: literal("agent.session.created"), agentSessionId: agentSessionId2 }),
7062
7211
  object({ type: literal("agent.message.received"), message: unknown() }),
7063
- object({ type: literal("agent.prompt.accepted"), agentSessionId: agentSessionId2 }),
7064
7212
  object({ type: literal("agent.prompt.cancelled"), agentSessionId: agentSessionId2 }),
7065
7213
  object({
7066
7214
  type: literal("agent.tool.approval.required"),
@@ -7470,11 +7618,10 @@ var sdkRuntime = {
7470
7618
  };
7471
7619
  // packages/agent/src/runtime/buildUserMessage.ts
7472
7620
  var isSlashCommand = (content) => readPromptText(content).trimStart().startsWith("/");
7473
- var buildUserMessage = (content, priority) => ({
7621
+ var buildUserMessage = (content) => ({
7474
7622
  type: "user",
7475
7623
  message: { role: "user", content },
7476
7624
  parent_tool_use_id: null,
7477
- priority,
7478
7625
  client_composed: isSlashCommand(content) ? undefined : true
7479
7626
  });
7480
7627
  // node_modules/.bun/p-defer@4.0.1/node_modules/p-defer/index.js
@@ -8699,6 +8846,110 @@ class PermissionGate {
8699
8846
  }
8700
8847
  }
8701
8848
 
8849
+ // packages/agent/src/tools/customToolsServer.ts
8850
+ import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
8851
+
8852
+ // packages/agent/src/tools/batchEdit/batchEditTool.ts
8853
+ import { tool } from "@anthropic-ai/claude-agent-sdk";
8854
+
8855
+ // packages/agent/src/tools/batchEdit/batchEditSchema.ts
8856
+ var textEditSchema = object({
8857
+ file_path: string2().describe("Absolute path of the file to modify"),
8858
+ old_string: string2().describe("Exact text to replace; empty only to create a new file with new_string"),
8859
+ new_string: string2().describe("Replacement text; must differ from old_string"),
8860
+ replace_all: boolean2().optional().describe("Replace every occurrence of old_string")
8861
+ });
8862
+ var batchEditShape = {
8863
+ edits: array(textEditSchema).min(1).describe("Edits applied in order; edits to one file see the result of earlier ones")
8864
+ };
8865
+
8866
+ // packages/agent/src/tools/batchEdit/stageEdits.ts
8867
+ import { isAbsolute, resolve } from "path";
8868
+
8869
+ // packages/agent/src/tools/batchEdit/applyTextEdit.ts
8870
+ var failEdit = (reason) => ({ ok: false, reason });
8871
+ function applyTextEdit(content, edit) {
8872
+ const { old_string: oldText, new_string: newText, replace_all: replaceAll } = edit;
8873
+ if (oldText === newText)
8874
+ return failEdit("old_string and new_string are identical");
8875
+ if (oldText === "") {
8876
+ return content === "" ? { ok: true, content: newText, replacements: 1 } : failEdit("old_string is empty but the file already has content");
8877
+ }
8878
+ const parts = content.split(oldText);
8879
+ const matches = parts.length - 1;
8880
+ if (matches === 0)
8881
+ return failEdit("old_string was not found in the file");
8882
+ if (matches > 1 && !replaceAll) {
8883
+ return failEdit(`old_string matches ${matches} times; add context or set replace_all`);
8884
+ }
8885
+ return { ok: true, content: parts.join(newText), replacements: matches };
8886
+ }
8887
+
8888
+ // packages/agent/src/tools/batchEdit/stageEdits.ts
8889
+ var failStage = (index, edit, reason) => ({
8890
+ ok: false,
8891
+ message: `Edit ${index + 1} (${edit.file_path}): ${reason}`
8892
+ });
8893
+ async function stageEdits(edits, readFile) {
8894
+ const files = new Map;
8895
+ for (const [index, edit] of edits.entries()) {
8896
+ if (!isAbsolute(edit.file_path))
8897
+ return failStage(index, edit, "file_path must be absolute");
8898
+ const path = resolve(edit.file_path);
8899
+ const current = files.get(path)?.content ?? await readFile(path);
8900
+ if (current === undefined && edit.old_string !== "") {
8901
+ return failStage(index, edit, "the file does not exist");
8902
+ }
8903
+ const outcome = applyTextEdit(current ?? "", edit);
8904
+ if (!outcome.ok)
8905
+ return failStage(index, edit, outcome.reason);
8906
+ const replacements = (files.get(path)?.replacements ?? 0) + outcome.replacements;
8907
+ files.set(path, { content: outcome.content, replacements });
8908
+ }
8909
+ return { ok: true, files };
8910
+ }
8911
+
8912
+ // packages/agent/src/tools/batchEdit/runBatchEdit.ts
8913
+ var readFileText = async (path) => {
8914
+ const file = Bun.file(path);
8915
+ return await file.exists() ? file.text() : undefined;
8916
+ };
8917
+ var describeFiles = (files) => [...files].map(([path, { replacements }]) => `- ${path}: ${replacements} replacement(s)`).join(`
8918
+ `);
8919
+ async function runBatchEdit(edits, readFile = readFileText) {
8920
+ const staged = await stageEdits(edits, readFile);
8921
+ if (!staged.ok) {
8922
+ return { isError: true, text: `${staged.message}
8923
+ No files were changed.` };
8924
+ }
8925
+ try {
8926
+ await Promise.all([...staged.files].map(([path, { content }]) => Bun.write(path, content)));
8927
+ } catch (error) {
8928
+ return { isError: true, text: `Writing failed, files may be partly changed: ${error}` };
8929
+ }
8930
+ return {
8931
+ isError: false,
8932
+ text: `Applied ${edits.length} edit(s) to ${staged.files.size} file(s):
8933
+ ${describeFiles(staged.files)}`
8934
+ };
8935
+ }
8936
+
8937
+ // packages/agent/src/tools/batchEdit/batchEditTool.ts
8938
+ var DESCRIPTION = `Apply many exact-string edits across one or more files in a single call. Each edit works like Edit (file_path, old_string, new_string, replace_all). Edits run in order, and edits to the same file build on each other. The batch is all-or-nothing: if any edit fails validation, no file is changed.`;
8939
+ var batchEditTool = tool(BATCH_EDIT_BASE_NAME, DESCRIPTION, batchEditShape, async ({ edits }) => {
8940
+ const { isError, text } = await runBatchEdit(edits);
8941
+ return { isError, content: [{ type: "text", text }] };
8942
+ });
8943
+
8944
+ // packages/agent/src/tools/customToolsServer.ts
8945
+ var CUSTOM_MCP_SERVERS = {
8946
+ [CUSTOM_SERVER_NAME]: createSdkMcpServer({
8947
+ name: CUSTOM_SERVER_NAME,
8948
+ tools: [batchEditTool],
8949
+ alwaysLoad: true
8950
+ })
8951
+ };
8952
+
8702
8953
  // packages/agent/src/profile/adhdSystemPrompt.ts
8703
8954
  var ADHD_SYSTEM_PROMPT = `For the rest of this session, shape every response for a reader with ADHD. The output is not just brief; it is shaped so an ADHD brain can act on it.
8704
8955
 
@@ -8813,8 +9064,7 @@ var AGENT_TOOLS = [
8813
9064
  "TodoWrite",
8814
9065
  "AskUserQuestion",
8815
9066
  "Write",
8816
- "Edit",
8817
- "NotebookEdit"
9067
+ "Edit"
8818
9068
  ];
8819
9069
  var AGENT_DISALLOWED_TOOLS = [
8820
9070
  "Agent(Explore)",
@@ -8841,11 +9091,12 @@ var TOOL_USAGE = {
8841
9091
  AskUserQuestion: "ask the user",
8842
9092
  Write: "create new files with content",
8843
9093
  Edit: "update files",
8844
- NotebookEdit: "update notebook cells"
9094
+ mcp__claudin__BatchEdit: "update files mutiples files or multiple replaces in same file"
8845
9095
  };
8846
9096
  var TOOL_GUIDANCE = `Always use the dedicated tool, never a workaround:
8847
9097
  ${AGENT_TOOLS.map((tool) => `- Use *${tool}* to ${TOOL_USAGE[tool]}`).join(`
8848
- `)}`;
9098
+ `)}
9099
+ - Use *${BATCH_EDIT_TOOL_NAME}* instead of repeated *Edit* calls to make several edits, across one or many files, in one call`;
8849
9100
 
8850
9101
  // packages/agent/src/profile/agentSystemPrompt.ts
8851
9102
  var AGENT_SYSTEM_PROMPT = [ADHD_SYSTEM_PROMPT, TOOL_GUIDANCE, SUBAGENT_GUIDANCE].join(`
@@ -8855,6 +9106,8 @@ var AGENT_SYSTEM_PROMPT = [ADHD_SYSTEM_PROMPT, TOOL_GUIDANCE, SUBAGENT_GUIDANCE]
8855
9106
  // packages/agent/src/profile/agentProfile.ts
8856
9107
  var AGENT_PROFILE_OPTIONS = {
8857
9108
  tools: AGENT_TOOLS,
9109
+ mcpServers: CUSTOM_MCP_SERVERS,
9110
+ toolAliases: { [BATCH_EDIT_BASE_NAME]: BATCH_EDIT_TOOL_NAME },
8858
9111
  disallowedTools: AGENT_DISALLOWED_TOOLS,
8859
9112
  systemPrompt: AGENT_SYSTEM_PROMPT,
8860
9113
  agents: AGENT_SUBAGENTS,
@@ -8889,6 +9142,27 @@ var AGENT_PROFILE_OPTIONS = {
8889
9142
  includeHookEvents: true
8890
9143
  };
8891
9144
 
9145
+ // packages/agent/src/session/readLiveTaskIds.ts
9146
+ var FINISHED_STATUSES2 = ["completed", "failed", "killed"];
9147
+ var withTask = (current, id) => new Set([...current, id]);
9148
+ var withoutTask = (current, id) => new Set([...current].filter((taskId) => taskId !== id));
9149
+ function readLiveTaskIds(message, current) {
9150
+ if (message.type !== "system")
9151
+ return current;
9152
+ switch (message.subtype) {
9153
+ case "background_tasks_changed":
9154
+ return new Set(message.tasks.filter((task) => !task.ambient).map((task) => task.task_id));
9155
+ case "task_started":
9156
+ return message.ambient ? current : withTask(current, message.task_id);
9157
+ case "task_updated":
9158
+ return FINISHED_STATUSES2.includes(message.patch.status ?? "") ? withoutTask(current, message.task_id) : current;
9159
+ case "task_notification":
9160
+ return withoutTask(current, message.task_id);
9161
+ default:
9162
+ return current;
9163
+ }
9164
+ }
9165
+
8892
9166
  // packages/agent/src/session/agentSession.ts
8893
9167
  function requireRewind(result) {
8894
9168
  if (!result.canRewind)
@@ -8903,6 +9177,7 @@ class AgentSession {
8903
9177
  elicitations;
8904
9178
  query;
8905
9179
  pendingTurns = 0;
9180
+ liveTaskIds = new Set;
8906
9181
  pumping = false;
8907
9182
  constructor(agentSessionId, baseOptions, runtime) {
8908
9183
  this.baseOptions = baseOptions;
@@ -8918,13 +9193,13 @@ class AgentSession {
8918
9193
  }
8919
9194
  });
8920
9195
  }
8921
- prompt(content, priority) {
8922
- this.send(content, priority);
9196
+ prompt(content) {
9197
+ this.send(content);
8923
9198
  return this.streamTurn();
8924
9199
  }
8925
- send(content, priority) {
9200
+ send(content) {
8926
9201
  this.pendingTurns += 1;
8927
- this.input.push(buildUserMessage(content, priority));
9202
+ this.input.push(buildUserMessage(content));
8928
9203
  }
8929
9204
  async* streamTurn() {
8930
9205
  if (this.pumping)
@@ -8951,6 +9226,9 @@ class AgentSession {
8951
9226
  this.rejectAllPrompts();
8952
9227
  await this.query.interrupt();
8953
9228
  }
9229
+ stopTask(taskId) {
9230
+ return this.query.stopTask(taskId);
9231
+ }
8954
9232
  async setModel(model) {
8955
9233
  await this.query.setModel(model);
8956
9234
  this.baseOptions = { ...this.baseOptions, model };
@@ -9021,10 +9299,11 @@ class AgentSession {
9021
9299
  if (done)
9022
9300
  break;
9023
9301
  sink.push({ type: "agent.message.received", message: value });
9302
+ this.liveTaskIds = readLiveTaskIds(value, this.liveTaskIds);
9024
9303
  if (value.type !== "result")
9025
9304
  continue;
9026
9305
  this.pendingTurns = Math.max(this.pendingTurns - 1, 0);
9027
- if (this.pendingTurns === 0)
9306
+ if (this.pendingTurns === 0 && this.liveTaskIds.size === 0)
9028
9307
  break;
9029
9308
  }
9030
9309
  sink.end();
@@ -9091,19 +9370,21 @@ async function readOptions2(path) {
9091
9370
  const parsed = optionsFileSchema.safeParse(stored);
9092
9371
  return parsed.success ? parsed.data : {};
9093
9372
  }
9094
- async function saveBackground(path, background) {
9095
- await Bun.write(path, JSON.stringify({ ...await readOptions2(path), background }, null, 2));
9096
- }
9373
+ var saveOption = async (path, option) => {
9374
+ await Bun.write(path, JSON.stringify({ ...await readOptions2(path), ...option }, null, 2));
9375
+ };
9376
+ var saveBackground = (path, background) => saveOption(path, { background });
9377
+ var saveTheme = (path, theme) => saveOption(path, { theme });
9097
9378
 
9098
9379
  // packages/daemon/src/workspace/resolvePath.ts
9099
9380
  import { stat } from "fs/promises";
9100
9381
  import { homedir as homedir2 } from "os";
9101
- import { join as join3, resolve } from "path";
9382
+ import { join as join3, resolve as resolve2 } from "path";
9102
9383
  var isDirectory = async (path) => (await stat(path).catch(() => {
9103
9384
  return;
9104
9385
  }))?.isDirectory() ?? false;
9105
9386
  var expandHome = (path) => path === "~" || path.startsWith("~/") ? join3(homedir2(), path.slice(1)) : path;
9106
- var resolveUserPath = (base, path) => resolve(base, expandHome(path.trim()));
9387
+ var resolveUserPath = (base, path) => resolve2(base, expandHome(path.trim()));
9107
9388
 
9108
9389
  // packages/daemon/src/server/daemonContext.ts
9109
9390
  var SESSIONS_DIRECTORY = join4(homedir3(), ".claudin", "sessions");
@@ -9171,11 +9452,7 @@ async function createDaemonContext(store = new FileSessionStore(SESSIONS_DIRECTO
9171
9452
  // packages/daemon/src/client/replayClient.ts
9172
9453
  var MAX_GAP_MS = 1500;
9173
9454
  var READ_ONLY_REASON = "Replay is read-only";
9174
- var PROMPT_TYPES = new Set([
9175
- "agent.prompt.requested",
9176
- "agent.prompt.queue.requested",
9177
- "agent.prompt.steer.requested"
9178
- ]);
9455
+ var PROMPT_TYPES = new Set(["agent.prompt.requested"]);
9179
9456
 
9180
9457
  class ReplayClient {
9181
9458
  stored;
@@ -9288,20 +9565,13 @@ var agentHandlers = {
9288
9565
  async* "agent.prompt.requested"({ agents }, request) {
9289
9566
  yield* agents.requireSession(request.agentSessionId).prompt(request.content);
9290
9567
  },
9291
- async* "agent.prompt.queue.requested"({ agents }, request) {
9292
- const turn = agents.requireSession(request.agentSessionId).prompt(request.content, "later");
9293
- yield { type: "agent.prompt.accepted", agentSessionId: request.agentSessionId };
9294
- yield* turn;
9295
- },
9296
- async* "agent.prompt.steer.requested"({ agents }, request) {
9297
- const turn = agents.requireSession(request.agentSessionId).prompt(request.content, "now");
9298
- yield { type: "agent.prompt.accepted", agentSessionId: request.agentSessionId };
9299
- yield* turn;
9300
- },
9301
9568
  async* "agent.prompt.cancel.requested"({ agents }, request) {
9302
9569
  await agents.requireSession(request.agentSessionId).cancel();
9303
9570
  yield { type: "agent.prompt.cancelled", agentSessionId: request.agentSessionId };
9304
9571
  },
9572
+ async* "agent.task.stop.requested"({ agents }, request) {
9573
+ await agents.requireSession(request.agentSessionId).stopTask(request.taskId);
9574
+ },
9305
9575
  async* "agent.tool.approval.answered"({ agents }, request) {
9306
9576
  agents.requireSession(request.agentSessionId).answerApproval(request.toolUseId, request.decision);
9307
9577
  },
@@ -9378,12 +9648,16 @@ var agentHandlers = {
9378
9648
  // packages/daemon/src/handlers/optionsHandlers.ts
9379
9649
  var optionsHandlers = {
9380
9650
  async* "options.load.requested"({ optionsPath }) {
9381
- const { background } = await readOptions2(optionsPath);
9382
- yield { type: "options.loaded", background };
9651
+ const { background, theme } = await readOptions2(optionsPath);
9652
+ yield { type: "options.loaded", background, theme };
9383
9653
  },
9384
9654
  async* "options.background.save.requested"({ optionsPath }, request) {
9385
9655
  await saveBackground(optionsPath, request.color);
9386
9656
  yield { type: "options.background.saved", color: request.color };
9657
+ },
9658
+ async* "options.theme.save.requested"({ optionsPath }, request) {
9659
+ await saveTheme(optionsPath, request.theme);
9660
+ yield { type: "options.theme.saved", theme: request.theme };
9387
9661
  }
9388
9662
  };
9389
9663
 
@@ -9419,14 +9693,14 @@ var sessionHandlers = {
9419
9693
  };
9420
9694
 
9421
9695
  // packages/daemon/src/workspace/readFileDiffs.ts
9422
- import { relative, resolve as resolve2 } from "path";
9696
+ import { relative, resolve as resolve3 } from "path";
9423
9697
  var isTracked = async (cwd, path) => (await Bun.$`git ls-files --error-unmatch -- ${path}`.cwd(cwd).quiet().nothrow()).exitCode === 0;
9424
9698
  async function readPatch(cwd, path) {
9425
9699
  const command = await isTracked(cwd, path) ? Bun.$`git diff --no-color HEAD -- ${path}` : Bun.$`git diff --no-color --no-index -- /dev/null ${path}`;
9426
9700
  return (await command.cwd(cwd).quiet().nothrow()).text();
9427
9701
  }
9428
9702
  async function readFileDiff(cwd, path) {
9429
- const absolute = resolve2(cwd, path);
9703
+ const absolute = resolve3(cwd, path);
9430
9704
  const patch = await readPatch(cwd, absolute);
9431
9705
  return {
9432
9706
  path: relative(cwd, absolute) || path,
@@ -11134,7 +11408,7 @@ async function startDaemon(port = DEFAULT_PORT, context) {
11134
11408
  }
11135
11409
  // apps/tui/src/startTui.ts
11136
11410
  import {
11137
- BoxRenderable as BoxRenderable16,
11411
+ BoxRenderable as BoxRenderable19,
11138
11412
  createCliRenderer,
11139
11413
  createHostClipboard
11140
11414
  } from "@opentui/core";
@@ -11176,7 +11450,7 @@ function buildPalette(colors, background = TRANSPARENT_BACKGROUND) {
11176
11450
  },
11177
11451
  fg: {
11178
11452
  primary: hex("text"),
11179
- secondary: rgbToHex2(tint(color("text"), color("textMuted"), SECONDARY_TEXT_WEIGHT)),
11453
+ secondary: hex("secondary") ?? rgbToHex2(tint(color("text"), color("textMuted"), SECONDARY_TEXT_WEIGHT)),
11180
11454
  muted: hex("textMuted")
11181
11455
  },
11182
11456
  border: { subtle: hex("borderSubtle"), focus: hex("borderActive"), separator: hex("border") },
@@ -11187,7 +11461,38 @@ function buildPalette(colors, background = TRANSPARENT_BACKGROUND) {
11187
11461
  warning: hex("warning"),
11188
11462
  info: hex("info")
11189
11463
  },
11190
- diff: { addedBg: hex("diffAddedBg"), removedBg: hex("diffRemovedBg") }
11464
+ diff: {
11465
+ addedBg: hex("diffAddedBg"),
11466
+ removedBg: hex("diffRemovedBg"),
11467
+ contextBg: hex("diffContextBg"),
11468
+ addedText: hex("diffAdded"),
11469
+ removedText: hex("diffRemoved"),
11470
+ contextText: hex("diffContext"),
11471
+ lineNumber: hex("diffLineNumber"),
11472
+ addedLineNumberBg: hex("diffAddedLineNumberBg"),
11473
+ removedLineNumberBg: hex("diffRemovedLineNumberBg")
11474
+ },
11475
+ syntax: {
11476
+ comment: hex("syntaxComment"),
11477
+ keyword: hex("syntaxKeyword"),
11478
+ function: hex("syntaxFunction"),
11479
+ variable: hex("syntaxVariable"),
11480
+ string: hex("syntaxString"),
11481
+ number: hex("syntaxNumber"),
11482
+ type: hex("syntaxType"),
11483
+ operator: hex("syntaxOperator"),
11484
+ punctuation: hex("syntaxPunctuation")
11485
+ },
11486
+ markdown: {
11487
+ heading: hex("markdownHeading"),
11488
+ strong: hex("markdownStrong"),
11489
+ emphasis: hex("markdownEmph"),
11490
+ listItem: hex("markdownListItem"),
11491
+ blockQuote: hex("markdownBlockQuote"),
11492
+ code: hex("markdownCode"),
11493
+ link: hex("markdownLink"),
11494
+ linkText: hex("markdownLinkText")
11495
+ }
11191
11496
  };
11192
11497
  }
11193
11498
  // apps/tui/src/theme/assets/aura.json
@@ -18656,10 +18961,16 @@ var createDiffView = (renderer, syntaxStyle, patch, filetype) => new DiffRendera
18656
18961
  syntaxStyle,
18657
18962
  view: "unified",
18658
18963
  showLineNumbers: true,
18964
+ fg: Theme.fg.primary,
18659
18965
  addedBg: Theme.diff.addedBg,
18660
18966
  removedBg: Theme.diff.removedBg,
18661
- addedSignColor: Theme.status.success,
18662
- removedSignColor: Theme.status.danger,
18967
+ contextBg: Theme.diff.contextBg,
18968
+ lineNumberFg: Theme.diff.lineNumber,
18969
+ lineNumberBg: Theme.diff.contextBg,
18970
+ addedLineNumberBg: Theme.diff.addedLineNumberBg,
18971
+ removedLineNumberBg: Theme.diff.removedLineNumberBg,
18972
+ addedSignColor: Theme.diff.addedText,
18973
+ removedSignColor: Theme.diff.removedText,
18663
18974
  flexShrink: 0
18664
18975
  });
18665
18976
 
@@ -18948,11 +19259,11 @@ var buildImageBlock = (mediaType, bytes) => ({
18948
19259
  // apps/tui/src/prompt/pastedPaths.ts
18949
19260
  import { statSync } from "fs";
18950
19261
  import { homedir as homedir6 } from "os";
18951
- import { isAbsolute } from "path";
19262
+ import { isAbsolute as isAbsolute2 } from "path";
18952
19263
  var unwrapPath = (raw) => raw.trim().replace(/^(['"])(.*)\1$/, "$2").replace(/^file:\/\//, "").replace(/^~(?=\/)/, homedir6()).replace(/\\(.)/g, "$1");
18953
19264
  var isExistingFile = (path) => {
18954
19265
  try {
18955
- return isAbsolute(path) && statSync(path).isFile();
19266
+ return isAbsolute2(path) && statSync(path).isFile();
18956
19267
  } catch {
18957
19268
  return false;
18958
19269
  }
@@ -18998,7 +19309,7 @@ function planPaste(event, attachments) {
18998
19309
 
18999
19310
  // apps/tui/src/components/promptInput.ts
19000
19311
  var MAX_INPUT_LINES = 8;
19001
- var BUSY_TITLE = " Ctrl+Q (queue) Ctrl+W (steer) ";
19312
+ var BUSY_PLACEHOLDER = "Enter (steer) Ctrl+Q (queue)";
19002
19313
  var ATTACHMENT_STYLE = "attachment";
19003
19314
  var PLACEHOLDER = "Type your prompt, /slash command, or @file reference...";
19004
19315
  var KEY_BINDINGS = [
@@ -19020,12 +19331,11 @@ class PromptInput {
19020
19331
  constructor(renderer, handlers) {
19021
19332
  this.renderable = new BoxRenderable5(renderer, {
19022
19333
  border: ["top", "bottom"],
19334
+ borderColor: Theme.fg.muted,
19023
19335
  borderStyle: "rounded",
19024
- bottomTitleAlignment: "right",
19025
19336
  flexDirection: "row",
19026
19337
  height: 3,
19027
- flexShrink: 0,
19028
- paddingLeft: 1
19338
+ flexShrink: 0
19029
19339
  });
19030
19340
  this.attachmentStyle = SyntaxStyle.create(renderer.nativeScene);
19031
19341
  this.textarea = new TextareaRenderable(renderer, {
@@ -19086,16 +19396,15 @@ class PromptInput {
19086
19396
  this.textarea.blur();
19087
19397
  }
19088
19398
  setBusy(busy) {
19089
- const title = busy ? BUSY_TITLE : undefined;
19090
- if (this.renderable.bottomTitle !== title)
19091
- this.renderable.bottomTitle = title;
19399
+ const placeholder = busy ? BUSY_PLACEHOLDER : PLACEHOLDER;
19400
+ if (this.textarea.placeholder !== placeholder)
19401
+ this.textarea.placeholder = placeholder;
19092
19402
  }
19093
19403
  setBorderColor(color) {
19094
19404
  this.renderable.borderColor = color;
19095
19405
  }
19096
19406
  applyColors() {
19097
- this.renderable.titleColor = Theme.fg.primary;
19098
- this.chevron.fg = Theme.accent.brand;
19407
+ this.chevron.fg = Theme.fg.muted;
19099
19408
  this.textarea.placeholderColor = Theme.fg.muted;
19100
19409
  this.textarea.textColor = Theme.fg.primary;
19101
19410
  this.textarea.focusedTextColor = Theme.fg.primary;
@@ -19197,6 +19506,25 @@ class AssistantMessageView {
19197
19506
  }
19198
19507
  }
19199
19508
 
19509
+ // apps/tui/src/components/markdownNoticeView.ts
19510
+ import { MarkdownRenderable as MarkdownRenderable2 } from "@opentui/core";
19511
+
19512
+ class MarkdownNoticeView {
19513
+ renderable;
19514
+ constructor(renderer, mutedSyntaxStyle) {
19515
+ this.renderable = new MarkdownRenderable2(renderer, {
19516
+ content: "",
19517
+ syntaxStyle: mutedSyntaxStyle,
19518
+ marginTop: 1,
19519
+ flexShrink: 0
19520
+ });
19521
+ }
19522
+ update(entry) {
19523
+ if (this.renderable.content !== entry.text)
19524
+ this.renderable.content = entry.text;
19525
+ }
19526
+ }
19527
+
19200
19528
  // apps/tui/src/components/noticeView.ts
19201
19529
  import { fg as fg7, StyledText as StyledText6, TextRenderable as TextRenderable7 } from "@opentui/core";
19202
19530
  class NoticeView {
@@ -19851,7 +20179,9 @@ var clipLines = (text, maxLines) => {
19851
20179
  // apps/tui/src/format/toolInputText.ts
19852
20180
  var VALUE_LENGTH = 200;
19853
20181
  var formatInputValue = (value) => truncateText(typeof value === "string" ? value : JSON.stringify(value), VALUE_LENGTH);
19854
- var formatToolInput = ({ input }) => Object.entries(input).map(([key, value]) => `${key}: ${formatInputValue(value)}`).join(`
20182
+ var formatBatchEdit = ({ input }) => listBatchEditPaths(input).map((path, index) => `${index + 1}. ${formatInputValue(path)}`).join(`
20183
+ `);
20184
+ var formatToolInput = (call) => call.name === BATCH_EDIT_TOOL_NAME ? formatBatchEdit(call) : Object.entries(call.input).map(([key, value]) => `${key}: ${formatInputValue(value)}`).join(`
19855
20185
  `);
19856
20186
 
19857
20187
  // apps/tui/src/format/toolSummary.ts
@@ -19859,9 +20189,15 @@ var SUMMARY_LENGTH = 80;
19859
20189
  var SUMMARY_KEYS = ["file_path", "path", "command", "pattern", "query", "url", "description"];
19860
20190
  var SEARCH_TOOLS = new Set(["Glob", "Grep"]);
19861
20191
  var summarizeSearch = ({ pattern, path }) => typeof path === "string" ? `${pattern} in ${path}` : String(pattern);
20192
+ var summarizeBatchEdit = (input) => {
20193
+ const paths = listBatchEditPaths(input);
20194
+ return `${paths.length} edits in ${new Set(paths).size} files`;
20195
+ };
19862
20196
  function summarizeToolCall({ name, input, status, progress }) {
19863
20197
  if (status === "running" && progress)
19864
20198
  return truncateText(progress, SUMMARY_LENGTH);
20199
+ if (name === BATCH_EDIT_TOOL_NAME)
20200
+ return summarizeBatchEdit(input);
19865
20201
  if (SEARCH_TOOLS.has(name) && typeof input.pattern === "string") {
19866
20202
  return truncateText(summarizeSearch(input), SUMMARY_LENGTH);
19867
20203
  }
@@ -19870,6 +20206,8 @@ function summarizeToolCall({ name, input, status, progress }) {
19870
20206
  return truncateText(value, SUMMARY_LENGTH);
19871
20207
  }
19872
20208
  var describeApproval = ({ name, input }) => {
20209
+ if (name === BATCH_EDIT_TOOL_NAME)
20210
+ return `Apply ${summarizeBatchEdit(input)} with BatchEdit?`;
19873
20211
  if (name === "Bash")
19874
20212
  return `Run command \`${truncateText(String(input.command ?? ""), 60)}\`?`;
19875
20213
  const path = input.file_path ?? input.notebook_path;
@@ -20114,8 +20452,8 @@ class ToolCallView {
20114
20452
  const glyph = call.expanded ? COLLAPSE_GLYPHS.expanded : COLLAPSE_GLYPHS.collapsed;
20115
20453
  const accent = this.hovered ? Theme.accent.brand : undefined;
20116
20454
  this.title.content = new StyledText10([
20117
- fg11(accent ?? toolColor(call.status))(`${glyph} `),
20118
- bold6(fg11(accent ?? Theme.fg.primary)(call.name)),
20455
+ fg11(accent ?? toolColor(call.status))(glyph),
20456
+ bold6(fg11(accent ?? Theme.fg.primary)(stripCustomPrefix(call.name))),
20119
20457
  fg11(Theme.fg.secondary)(` ${summarizeToolCall(call)}`),
20120
20458
  fg11(Theme.fg.muted)(stats)
20121
20459
  ]);
@@ -20222,7 +20560,6 @@ class UserMessageView {
20222
20560
  return;
20223
20561
  this.entry = entry;
20224
20562
  this.text.content = new StyledText11([
20225
- bold7(fg12(Theme.accent.brand)("\u276F ")),
20226
20563
  bold7(fg12(Theme.fg.primary)(clipLines(entry.text, USER_LINES)))
20227
20564
  ]);
20228
20565
  this.clock.content = new StyledText11([fg12(Theme.fg.muted)(formatClock(entry.at))]);
@@ -20276,9 +20613,9 @@ class ScrollbackView {
20276
20613
  else if (below > 0)
20277
20614
  this.renderable.scrollBy(Math.min(below, above));
20278
20615
  }
20279
- reset(syntaxStyle) {
20616
+ reset(syntaxStyle, mutedSyntaxStyle) {
20280
20617
  this.dropViews(() => true);
20281
- this.options = { syntaxStyle, hooks: { ...this.options.hooks, syntaxStyle } };
20618
+ this.options = { syntaxStyle, mutedSyntaxStyle, hooks: { ...this.options.hooks, syntaxStyle } };
20282
20619
  }
20283
20620
  dropRemovedViews(state) {
20284
20621
  const live = new Set(state.transcript.map((entry) => entry.id));
@@ -20300,14 +20637,14 @@ class ScrollbackView {
20300
20637
  return view;
20301
20638
  }
20302
20639
  createView(entry) {
20303
- const { syntaxStyle, hooks } = this.options;
20640
+ const { syntaxStyle, mutedSyntaxStyle, hooks } = this.options;
20304
20641
  switch (entry.kind) {
20305
20642
  case "user":
20306
20643
  return new UserMessageView(this.renderer);
20307
20644
  case "assistant":
20308
20645
  return new AssistantMessageView(this.renderer, syntaxStyle);
20309
20646
  case "notice":
20310
- return new NoticeView(this.renderer);
20647
+ return entry.markdown ? new MarkdownNoticeView(this.renderer, mutedSyntaxStyle) : new NoticeView(this.renderer);
20311
20648
  case "thought":
20312
20649
  return new ThoughtView(this.renderer);
20313
20650
  case "tools":
@@ -20386,15 +20723,14 @@ class SessionInfoBar {
20386
20723
  limits;
20387
20724
  constructor(renderer) {
20388
20725
  this.renderable = new BoxRenderable13(renderer, {
20389
- marginBottom: 1,
20390
20726
  flexShrink: 0,
20391
20727
  flexDirection: "row",
20392
20728
  flexWrap: "wrap",
20393
20729
  rowGap: 1,
20394
20730
  columnGap: 0,
20395
20731
  justifyContent: "space-between",
20396
- paddingLeft: 1,
20397
- paddingRight: 1
20732
+ paddingLeft: 2,
20733
+ paddingRight: 2
20398
20734
  });
20399
20735
  this.info = new TextRenderable13(renderer, { content: "", wrapMode: "none", flexShrink: 0 });
20400
20736
  this.limits = new TextRenderable13(renderer, {
@@ -20450,12 +20786,14 @@ class StatusLine {
20450
20786
  constructor(renderer, onStop) {
20451
20787
  this.renderable = new BoxRenderable14(renderer, {
20452
20788
  height: 2,
20453
- paddingTop: 1,
20454
20789
  flexShrink: 0,
20455
20790
  flexDirection: "row",
20456
20791
  justifyContent: "space-between",
20792
+ border: ["top"],
20793
+ borderColor: Theme.border.subtle,
20457
20794
  paddingLeft: 2,
20458
- paddingRight: 2
20795
+ paddingRight: 2,
20796
+ visible: false
20459
20797
  });
20460
20798
  this.label = new TextRenderable14(renderer, { content: "", wrapMode: "none" });
20461
20799
  this.stats = new TextRenderable14(renderer, {
@@ -20471,9 +20809,11 @@ class StatusLine {
20471
20809
  update(state) {
20472
20810
  if (state.status === "idle") {
20473
20811
  this.label.content = state.lastRun ? new StyledText13(buildLastRunChunks(state.lastRun, readLastPrompt(state))) : "";
20812
+ this.renderable.visible = state.lastRun !== undefined;
20474
20813
  this.stats.content = "";
20475
20814
  return;
20476
20815
  }
20816
+ this.renderable.visible = true;
20477
20817
  const label = agentLabel(state.status);
20478
20818
  const busy = label.icon === undefined;
20479
20819
  const frame = SPINNER_FRAMES[Math.floor(state.now / SPINNER_STEP_MS) % SPINNER_FRAMES.length];
@@ -20490,8 +20830,198 @@ class StatusLine {
20490
20830
  }
20491
20831
  }
20492
20832
 
20833
+ // apps/tui/src/components/promptQueueView.ts
20834
+ import {
20835
+ BoxRenderable as BoxRenderable15,
20836
+ bold as bold9,
20837
+ dim as dim7,
20838
+ fg as fg16,
20839
+ StyledText as StyledText14,
20840
+ TextRenderable as TextRenderable15
20841
+ } from "@opentui/core";
20842
+
20843
+ // apps/tui/src/format/promptQueueRows.ts
20844
+ var MAX_QUEUE_ROWS = 5;
20845
+ var QUEUE_TEXT_LENGTH = 80;
20846
+ var buildQueueRows = (queued) => queued.slice(0, MAX_QUEUE_ROWS).map((content, index) => `${index + 1}. ${truncateText(readPromptText(content), QUEUE_TEXT_LENGTH)}`);
20847
+ var buildQueueHeader = (queued) => `Queued${SEPARATOR}${queued.length} waiting`;
20848
+ var countHiddenQueued = (queued) => Math.max(queued.length - MAX_QUEUE_ROWS, 0);
20849
+
20850
+ // apps/tui/src/components/promptQueueView.ts
20851
+ class PromptQueueView {
20852
+ renderable;
20853
+ header;
20854
+ rows;
20855
+ overflow;
20856
+ constructor(renderer) {
20857
+ this.renderable = new BoxRenderable15(renderer, {
20858
+ flexShrink: 0,
20859
+ paddingLeft: 2,
20860
+ paddingRight: 2,
20861
+ visible: false
20862
+ });
20863
+ this.header = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20864
+ this.overflow = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20865
+ this.rows = Array.from({ length: MAX_QUEUE_ROWS }, () => new TextRenderable15(renderer, { content: "", wrapMode: "none" }));
20866
+ this.renderable.add(this.header);
20867
+ for (const row of this.rows)
20868
+ this.renderable.add(row);
20869
+ this.renderable.add(this.overflow);
20870
+ }
20871
+ update({ queuedPrompts }) {
20872
+ this.renderable.visible = queuedPrompts.length > 0;
20873
+ if (queuedPrompts.length === 0)
20874
+ return;
20875
+ this.header.content = new StyledText14([
20876
+ bold9(fg16(Theme.fg.secondary)(buildQueueHeader(queuedPrompts)))
20877
+ ]);
20878
+ const lines = buildQueueRows(queuedPrompts);
20879
+ this.rows.forEach((row, index) => {
20880
+ row.content = new StyledText14([fg16(Theme.fg.primary)(lines[index] ?? "")]);
20881
+ });
20882
+ const hidden = countHiddenQueued(queuedPrompts);
20883
+ this.overflow.content = hidden ? new StyledText14([dim7(fg16(Theme.fg.muted)(` +${hidden} more`))]) : "";
20884
+ }
20885
+ }
20886
+
20887
+ // apps/tui/src/components/taskPanel.ts
20888
+ import {
20889
+ BoxRenderable as BoxRenderable17,
20890
+ bold as bold11,
20891
+ dim as dim9,
20892
+ fg as fg18,
20893
+ StyledText as StyledText16,
20894
+ TextRenderable as TextRenderable17
20895
+ } from "@opentui/core";
20896
+
20897
+ // apps/tui/src/format/taskRows.ts
20898
+ var MAX_TASK_ROWS = 5;
20899
+ var KIND_LABELS = { "general-purpose": "Agent", local_bash: "Bash" };
20900
+ var describeLastTool = ({ lastToolCall, lastToolName }) => {
20901
+ if (!lastToolCall)
20902
+ return lastToolName;
20903
+ const { name, input } = lastToolCall;
20904
+ const summary = summarizeToolCall({
20905
+ id: "",
20906
+ name,
20907
+ input,
20908
+ status: "running",
20909
+ expanded: false,
20910
+ startedAt: 0
20911
+ });
20912
+ return `${name} ${summary}`;
20913
+ };
20914
+ var readDetail = (task) => {
20915
+ if (task.paused)
20916
+ return "Paused";
20917
+ return task.summary ?? describeLastTool(task) ?? "Starting...";
20918
+ };
20919
+ var buildTaskRow = (task, now) => ({
20920
+ taskId: task.id,
20921
+ kind: KIND_LABELS[task.kind] ?? task.kind,
20922
+ model: task.model ?? "",
20923
+ title: task.description,
20924
+ stats: [formatDuration(Math.max(now - task.startedAt, 0)), `\u2193${formatTokenCount(task.tokens)}`].join(SEPARATOR).trim(),
20925
+ detail: readDetail(task)
20926
+ });
20927
+ var buildTaskRows = (tasks, now) => tasks.slice(0, MAX_TASK_ROWS).map((task) => buildTaskRow(task, now));
20928
+ var buildTaskHeader = (tasks) => `Tasks${SEPARATOR}${tasks.length} running`;
20929
+ var countHiddenTasks = (tasks) => Math.max(tasks.length - MAX_TASK_ROWS, 0);
20930
+
20931
+ // apps/tui/src/components/taskRowView.ts
20932
+ import {
20933
+ BoxRenderable as BoxRenderable16,
20934
+ bold as bold10,
20935
+ dim as dim8,
20936
+ fg as fg17,
20937
+ StyledText as StyledText15,
20938
+ TextRenderable as TextRenderable16
20939
+ } from "@opentui/core";
20940
+ var SPINNER_STEP_MS2 = 100;
20941
+
20942
+ class TaskRowView {
20943
+ renderable;
20944
+ title;
20945
+ stop;
20946
+ detail;
20947
+ taskId = "";
20948
+ constructor(renderer, onStop) {
20949
+ this.renderable = new BoxRenderable16(renderer, { flexShrink: 0, visible: false });
20950
+ const line = new BoxRenderable16(renderer, {
20951
+ flexDirection: "row",
20952
+ justifyContent: "space-between"
20953
+ });
20954
+ this.title = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
20955
+ this.stop = new TextRenderable16(renderer, {
20956
+ content: "",
20957
+ wrapMode: "none",
20958
+ flexShrink: 0,
20959
+ selectable: false,
20960
+ onMouseDown: () => onStop(this.taskId)
20961
+ });
20962
+ this.detail = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
20963
+ line.add(this.title);
20964
+ line.add(this.stop);
20965
+ this.renderable.add(line);
20966
+ this.renderable.add(this.detail);
20967
+ }
20968
+ update(row, now) {
20969
+ this.renderable.visible = row !== undefined;
20970
+ if (!row)
20971
+ return;
20972
+ this.taskId = row.taskId;
20973
+ const frame = SPINNER_FRAMES[Math.floor(now / SPINNER_STEP_MS2) % SPINNER_FRAMES.length];
20974
+ this.title.content = new StyledText15([
20975
+ fg17(Theme.status.success)(`${frame} `),
20976
+ bold10(fg17(Theme.fg.primary)(row.kind)),
20977
+ fg17(Theme.fg.muted)(row.model ? ` (${row.model})` : ""),
20978
+ fg17(Theme.fg.secondary)(` ${row.title}`)
20979
+ ]);
20980
+ this.stop.content = new StyledText15([
20981
+ fg17(Theme.fg.muted)(`${row.stats} `),
20982
+ bold10(fg17(Theme.fg.secondary)("[stop]"))
20983
+ ]);
20984
+ this.detail.content = new StyledText15([dim8(fg17(Theme.fg.muted)(` \u2514 ${row.detail}`))]);
20985
+ }
20986
+ }
20987
+
20988
+ // apps/tui/src/components/taskPanel.ts
20989
+ class TaskPanel {
20990
+ renderable;
20991
+ header;
20992
+ rows;
20993
+ overflow;
20994
+ constructor(renderer, onStop) {
20995
+ this.renderable = new BoxRenderable17(renderer, {
20996
+ flexShrink: 0,
20997
+ paddingLeft: 2,
20998
+ paddingRight: 2,
20999
+ border: ["top"],
21000
+ borderColor: Theme.border.subtle,
21001
+ visible: false
21002
+ });
21003
+ this.header = new TextRenderable17(renderer, { content: "", wrapMode: "none" });
21004
+ this.overflow = new TextRenderable17(renderer, { content: "", wrapMode: "none" });
21005
+ this.rows = Array.from({ length: MAX_TASK_ROWS }, () => new TaskRowView(renderer, onStop));
21006
+ this.renderable.add(this.header);
21007
+ for (const row of this.rows)
21008
+ this.renderable.add(row.renderable);
21009
+ this.renderable.add(this.overflow);
21010
+ }
21011
+ update({ tasks, now }) {
21012
+ this.renderable.visible = tasks.length > 0;
21013
+ if (tasks.length === 0)
21014
+ return;
21015
+ const rows = buildTaskRows(tasks, now);
21016
+ this.header.content = new StyledText16([bold11(fg18(Theme.fg.primary)(buildTaskHeader(tasks)))]);
21017
+ this.rows.forEach((view, index) => view.update(rows[index], now));
21018
+ const hidden = countHiddenTasks(tasks);
21019
+ this.overflow.content = hidden ? new StyledText16([dim9(fg18(Theme.fg.muted)(` +${hidden} more`))]) : "";
21020
+ }
21021
+ }
21022
+
20493
21023
  // apps/tui/src/components/toastView.ts
20494
- import { BoxRenderable as BoxRenderable15, fg as fg16, StyledText as StyledText14, TextRenderable as TextRenderable15 } from "@opentui/core";
21024
+ import { BoxRenderable as BoxRenderable18, fg as fg19, StyledText as StyledText17, TextRenderable as TextRenderable18 } from "@opentui/core";
20495
21025
  var TOAST_ICON = "\u25CF";
20496
21026
 
20497
21027
  class ToastView {
@@ -20499,7 +21029,7 @@ class ToastView {
20499
21029
  text;
20500
21030
  toastId;
20501
21031
  constructor(renderer) {
20502
- this.renderable = new BoxRenderable15(renderer, {
21032
+ this.renderable = new BoxRenderable18(renderer, {
20503
21033
  position: "absolute",
20504
21034
  top: 1,
20505
21035
  right: 2,
@@ -20510,7 +21040,7 @@ class ToastView {
20510
21040
  borderStyle: "rounded",
20511
21041
  visible: false
20512
21042
  });
20513
- this.text = new TextRenderable15(renderer, { content: "", wrapMode: "none", selectable: false });
21043
+ this.text = new TextRenderable18(renderer, { content: "", wrapMode: "none", selectable: false });
20514
21044
  this.renderable.add(this.text);
20515
21045
  }
20516
21046
  update({ ui }) {
@@ -20521,9 +21051,9 @@ class ToastView {
20521
21051
  this.toastId = toast.id;
20522
21052
  this.renderable.borderColor = Theme.border.focus;
20523
21053
  this.renderable.backgroundColor = Theme.bg.surface;
20524
- this.text.content = new StyledText14([
20525
- fg16(Theme.status.success)(`${TOAST_ICON} `),
20526
- fg16(Theme.fg.primary)(toast.text)
21054
+ this.text.content = new StyledText17([
21055
+ fg19(Theme.status.success)(`${TOAST_ICON} `),
21056
+ fg19(Theme.fg.primary)(toast.text)
20527
21057
  ]);
20528
21058
  }
20529
21059
  }
@@ -20552,6 +21082,9 @@ function requestAgent(store, request, onMissing = "notify") {
20552
21082
  return true;
20553
21083
  }
20554
21084
 
21085
+ // apps/tui/src/input/themeActions.ts
21086
+ var saveTheme2 = (store, theme) => store.request({ type: "options.theme.save.requested", theme });
21087
+
20555
21088
  // apps/tui/src/input/workflowCommands.ts
20556
21089
  var postNotice = (store, text) => store.emit({ type: "ui.notice.posted", text });
20557
21090
  var WORKFLOW_COMMANDS = {
@@ -20651,7 +21184,7 @@ var SLASH_COMMANDS = {
20651
21184
  return store.emit({ type: "ui.theme.picker.opened" });
20652
21185
  const parsed = themeNameSchema.safeParse(argument);
20653
21186
  if (parsed.success)
20654
- return store.emit({ type: "ui.theme.selected", theme: parsed.data });
21187
+ return saveTheme2(store, parsed.data);
20655
21188
  store.emit({ type: "ui.notice.posted", text: `Unknown theme: ${argument}` });
20656
21189
  }
20657
21190
  },
@@ -20680,8 +21213,10 @@ var SLASH_COMMANDS = {
20680
21213
  }
20681
21214
  },
20682
21215
  "/effort": {
20683
- description: "Show or set reasoning effort",
21216
+ description: "Pick a reasoning effort, or set one with /effort <level>",
20684
21217
  run(store, level) {
21218
+ if (!level)
21219
+ return store.emit({ type: "ui.effort.picker.opened" });
20685
21220
  const parsed = effortLevelSchema.safeParse(level);
20686
21221
  if (parsed.success) {
20687
21222
  return void requestAgent(store, {
@@ -20753,6 +21288,14 @@ function buildPickerView(state) {
20753
21288
  }));
20754
21289
  return { title: "Select a theme", rows, selected: picker.selected };
20755
21290
  }
21291
+ if (picker.kind === "effort") {
21292
+ const current = state.effort || DEFAULT_EFFORT;
21293
+ const rows = effortLevelSchema.options.map((level) => ({
21294
+ name: `${level === current ? "\u25CF " : " "}${level}`,
21295
+ description: ""
21296
+ }));
21297
+ return { title: "Select an effort level", rows, selected: picker.selected };
21298
+ }
20756
21299
  if (picker.kind === "rewind") {
20757
21300
  const rows = listCheckpoints(state).map((entry) => ({
20758
21301
  name: truncateText(entry.text.replace(/\s+/g, " "), CHECKPOINT_TEXT_LENGTH),
@@ -20770,7 +21313,6 @@ function buildPickerView(state) {
20770
21313
  var buildListView = (state) => buildPickerView(state) ?? buildAutocompleteView(state);
20771
21314
 
20772
21315
  // apps/tui/src/input/agentSessionActions.ts
20773
- var DEFAULT_EFFORT = "high";
20774
21316
  var DEFAULT_MODEL = "claude-haiku-5-5";
20775
21317
  var buildSessionOptions = (store) => ({
20776
21318
  model: store.state.model || DEFAULT_MODEL,
@@ -20949,23 +21491,22 @@ function runSlashCommand(store, line) {
20949
21491
  }
20950
21492
 
20951
21493
  // apps/tui/src/input/promptActions.ts
20952
- var DELIVERY_REQUESTS = {
20953
- send: "agent.prompt.requested",
20954
- queue: "agent.prompt.queue.requested",
20955
- steer: "agent.prompt.steer.requested"
20956
- };
20957
- var BUSY_NOTICE = "Agent is busy: Ctrl+Q queues, Ctrl+W steers, Esc Esc interrupts the turn";
20958
21494
  var buildContent = (text, images) => images.length > 0 ? [{ type: "text", text }, ...images] : text;
20959
21495
  function deliverPrompt(store, content, delivery, images) {
20960
- const busy = isAgentBusy(store);
20961
- if (busy && delivery === "send") {
20962
- store.emit({ type: "ui.notice.posted", text: BUSY_NOTICE });
20963
- return false;
21496
+ const body = buildContent(content, images);
21497
+ if (delivery === "queue" && isAgentBusy(store)) {
21498
+ store.emit({ type: "ui.prompt.queued", content: body });
21499
+ return true;
20964
21500
  }
20965
- return requestAgent(store, {
20966
- type: DELIVERY_REQUESTS[busy ? delivery : "send"],
20967
- content: buildContent(content, images)
20968
- });
21501
+ return requestAgent(store, { type: "agent.prompt.requested", content: body });
21502
+ }
21503
+ function sendNextQueuedPrompt(store) {
21504
+ const [next] = store.state.queuedPrompts;
21505
+ if (next === undefined || store.state.status !== "idle")
21506
+ return;
21507
+ const sent = requestAgent(store, { type: "agent.prompt.requested", content: next }, "ignore");
21508
+ if (sent)
21509
+ store.emit({ type: "ui.prompt.dequeued" });
20969
21510
  }
20970
21511
  function submitPrompt(store, text, delivery = "send", images = []) {
20971
21512
  const content = text.trim();
@@ -20982,6 +21523,9 @@ function answerApproval(store, decision, toolUseId) {
20982
21523
  if (target)
20983
21524
  requestAgent(store, { type: "agent.tool.approval.answered", toolUseId: target, decision });
20984
21525
  }
21526
+ function stopTask(store, taskId) {
21527
+ requestAgent(store, { type: "agent.task.stop.requested", taskId });
21528
+ }
20985
21529
  function cancelTurn(store) {
20986
21530
  requestAgent(store, { type: "agent.prompt.cancel.requested" });
20987
21531
  }
@@ -21046,7 +21590,12 @@ var PICKER_CONFIRMS = {
21046
21590
  theme(store, _state, selected) {
21047
21591
  const theme = THEME_NAMES[selected];
21048
21592
  if (theme)
21049
- store.emit({ type: "ui.theme.selected", theme });
21593
+ saveTheme2(store, theme);
21594
+ },
21595
+ effort(store, _state, selected) {
21596
+ const effort = effortLevelSchema.options[selected];
21597
+ if (effort)
21598
+ requestAgent(store, { type: "agent.effort.set.requested", effort });
21050
21599
  },
21051
21600
  rewind(store, state, selected) {
21052
21601
  const userMessageId = listCheckpoints(state)[selected]?.checkpointId;
@@ -21094,7 +21643,7 @@ var dismissQuestion = (store) => sendAnswers(store, {});
21094
21643
 
21095
21644
  // apps/tui/src/input/routeKeys.ts
21096
21645
  var createKeyPresses = () => ({ escapeAt: 0, ctrlCAt: 0 });
21097
- var DELIVERY_KEYS = { q: "queue", w: "steer" };
21646
+ var DELIVERY_KEYS = { q: "queue" };
21098
21647
  var DOUBLE_ESCAPE_MS = 500;
21099
21648
  var EXIT_CONFIRM_MS = 3000;
21100
21649
  var EXIT_NOTICE = "Press Ctrl + C again to exit";
@@ -21439,21 +21988,70 @@ class AppStore {
21439
21988
 
21440
21989
  // apps/tui/src/theme/syntaxStyle.ts
21441
21990
  import { SyntaxStyle as SyntaxStyle2 } from "@opentui/core";
21442
- var createSyntaxStyle = (renderer) => SyntaxStyle2.fromStyles({
21443
- default: { fg: Theme.fg.primary },
21444
- "markup.heading": { fg: Theme.accent.brand, bold: true },
21445
- "markup.strong": { fg: Theme.fg.primary, bold: true },
21446
- "markup.italic": { fg: Theme.fg.secondary, italic: true },
21447
- "markup.raw": { fg: Theme.status.warning },
21448
- "markup.link": { fg: Theme.status.info, underline: true },
21449
- "markup.list": { fg: Theme.accent.highlight },
21450
- keyword: { fg: Theme.accent.brand },
21451
- string: { fg: Theme.status.success },
21452
- comment: { fg: Theme.fg.muted, italic: true },
21453
- function: { fg: Theme.accent.highlight },
21454
- type: { fg: Theme.status.warning },
21455
- number: { fg: Theme.status.info }
21456
- }, renderer.nativeScene);
21991
+
21992
+ // apps/tui/src/theme/syntaxRules.ts
21993
+ var rule = (scope, foreground, style = {}) => ({
21994
+ scope,
21995
+ style: { foreground, ...style }
21996
+ });
21997
+ var buildMutedSyntaxRules = (palette) => buildSyntaxRules(palette).map(({ scope, style }) => ({
21998
+ scope,
21999
+ style: { ...style, foreground: palette.fg.muted }
22000
+ }));
22001
+ var buildSyntaxRules = ({ fg, status, syntax, markdown, diff, bg }) => [
22002
+ rule(["default"], fg.primary),
22003
+ rule(["comment", "comment.documentation"], syntax.comment, { italic: true }),
22004
+ rule(["string", "symbol", "character.special", "character"], syntax.string),
22005
+ rule(["number", "boolean", "constant", "float"], syntax.number),
22006
+ rule(["keyword.return", "keyword.conditional", "keyword.repeat", "keyword.coroutine"], syntax.keyword, {
22007
+ italic: true
22008
+ }),
22009
+ rule(["keyword.type"], syntax.type, { bold: true, italic: true }),
22010
+ rule(["keyword.function", "function.method"], syntax.function),
22011
+ rule(["keyword"], syntax.keyword, { italic: true }),
22012
+ rule(["keyword.import", "string.escape", "string.regexp", "tag.attribute", "keyword.export"], syntax.keyword),
22013
+ rule(["operator", "keyword.operator", "punctuation.delimiter", "keyword.conditional.ternary"], syntax.operator),
22014
+ rule(["variable", "variable.parameter", "function.method.call", "function.call", "property", "parameter", "field"], syntax.variable),
22015
+ rule(["variable.member", "function", "constructor"], syntax.function),
22016
+ rule(["type", "module", "class", "namespace"], syntax.type),
22017
+ rule(["type.definition"], syntax.type, { bold: true }),
22018
+ rule(["punctuation", "punctuation.bracket"], syntax.punctuation),
22019
+ rule(["variable.builtin", "type.builtin", "function.builtin", "module.builtin", "constant.builtin", "variable.super"], status.danger),
22020
+ rule(["keyword.directive", "keyword.modifier", "keyword.exception"], syntax.keyword, { italic: true }),
22021
+ rule(["punctuation.special", "tag.delimiter"], syntax.operator),
22022
+ rule(["markup.heading", "markup.heading.2", "markup.heading.3", "markup.heading.4", "markup.heading.5", "markup.heading.6"], markdown.heading, { bold: true }),
22023
+ rule(["markup.heading.1"], markdown.heading, { bold: true, underline: true }),
22024
+ rule(["markup.bold", "markup.strong"], markdown.strong, { bold: true }),
22025
+ rule(["markup.italic"], markdown.emphasis, { italic: true }),
22026
+ rule(["markup.list"], markdown.listItem),
22027
+ rule(["markup.quote"], markdown.blockQuote, { italic: true }),
22028
+ rule(["markup.raw", "markup.raw.block"], markdown.code),
22029
+ rule(["markup.raw.inline"], markdown.code, { background: bg.surface }),
22030
+ rule(["markup.link", "markup.link.url", "string.special", "string.special.url"], markdown.link, {
22031
+ underline: true
22032
+ }),
22033
+ rule(["markup.link.label"], markdown.linkText, { underline: true }),
22034
+ rule(["label"], markdown.linkText),
22035
+ rule(["spell", "nospell"], fg.primary),
22036
+ rule(["markup.underline"], fg.primary, { underline: true }),
22037
+ rule(["comment.error"], status.danger, { italic: true, bold: true }),
22038
+ rule(["comment.warning"], status.warning, { italic: true, bold: true }),
22039
+ rule(["comment.todo", "comment.note"], status.info, { italic: true, bold: true }),
22040
+ rule(["attribute", "annotation"], status.warning),
22041
+ rule(["tag"], status.danger),
22042
+ rule(["markup.strikethrough", "markup.list.unchecked", "debug"], fg.muted),
22043
+ rule(["markup.list.checked"], status.success),
22044
+ rule(["diff.plus"], diff.addedText, { background: diff.addedBg }),
22045
+ rule(["diff.minus"], diff.removedText, { background: diff.removedBg }),
22046
+ rule(["diff.delta"], diff.contextText, { background: diff.contextBg }),
22047
+ rule(["error"], status.danger, { bold: true }),
22048
+ rule(["warning"], status.warning, { bold: true }),
22049
+ rule(["info"], status.info)
22050
+ ];
22051
+
22052
+ // apps/tui/src/theme/syntaxStyle.ts
22053
+ var createSyntaxStyle = (renderer) => SyntaxStyle2.fromTheme(buildSyntaxRules(Theme), renderer.nativeScene);
22054
+ var createMutedSyntaxStyle = (renderer) => SyntaxStyle2.fromTheme(buildMutedSyntaxRules(Theme), renderer.nativeScene);
21457
22055
 
21458
22056
  // apps/tui/src/startTui.ts
21459
22057
  var CLOCK_INTERVAL_MS = 100;
@@ -21463,10 +22061,10 @@ var readShownTheme = ({ theme, ui }) => (ui.picker?.kind === "theme" ? THEME_NAM
21463
22061
  var readShownBackground = ({ background, ui }) => ui.backgroundEditor?.color ?? background;
21464
22062
  async function startTui(client, options = {}) {
21465
22063
  const activeRenderer = options.renderer ?? await createCliRenderer({ exitOnCtrlC: false, useMouse: true });
21466
- activeRenderer.setTerminalTitle("Claudin");
21467
22064
  const store = new AppStore(client);
21468
22065
  const clipboard = options.clipboard ?? createHostClipboard();
21469
22066
  let syntaxStyle = createSyntaxStyle(activeRenderer);
22067
+ let mutedSyntaxStyle = createMutedSyntaxStyle(activeRenderer);
21470
22068
  let appliedTheme = readShownTheme(store.state);
21471
22069
  let appliedBackground = readShownBackground(store.state);
21472
22070
  const header = new HeaderBar(activeRenderer);
@@ -21475,8 +22073,11 @@ async function startTui(client, options = {}) {
21475
22073
  const backgroundPanel = new BackgroundPanel(activeRenderer);
21476
22074
  const toastView = new ToastView(activeRenderer);
21477
22075
  const statusLine = new StatusLine(activeRenderer, () => cancelTurn(store));
22076
+ const taskPanel = new TaskPanel(activeRenderer, (taskId) => stopTask(store, taskId));
22077
+ const promptQueue = new PromptQueueView(activeRenderer);
21478
22078
  const scrollback = new ScrollbackView(activeRenderer, {
21479
22079
  syntaxStyle,
22080
+ mutedSyntaxStyle,
21480
22081
  hooks: {
21481
22082
  syntaxStyle,
21482
22083
  onDecision: (decision, id) => answerApproval(store, decision, id),
@@ -21509,7 +22110,7 @@ async function startTui(client, options = {}) {
21509
22110
  saveDraft(text);
21510
22111
  }
21511
22112
  });
21512
- const root = new BoxRenderable16(activeRenderer, {
22113
+ const root = new BoxRenderable19(activeRenderer, {
21513
22114
  width: "100%",
21514
22115
  height: "100%",
21515
22116
  flexDirection: "column",
@@ -21520,10 +22121,12 @@ async function startTui(client, options = {}) {
21520
22121
  scrollback,
21521
22122
  diffViewer,
21522
22123
  statusLine,
22124
+ promptQueue,
21523
22125
  popover,
21524
22126
  backgroundPanel,
21525
22127
  input,
21526
- infoBar
22128
+ infoBar,
22129
+ taskPanel
21527
22130
  ];
21528
22131
  for (const part of parts)
21529
22132
  root.add(part.renderable);
@@ -21532,7 +22135,7 @@ async function startTui(client, options = {}) {
21532
22135
  activeRenderer.on("selection", (selection) => {
21533
22136
  copySelection(store, activeRenderer, clipboard, selection);
21534
22137
  });
21535
- for (const part of [header, statusLine, popover, backgroundPanel, input, infoBar]) {
22138
+ for (const part of [header, statusLine, taskPanel, popover, backgroundPanel, input, infoBar]) {
21536
22139
  part.renderable.onMouseScroll = ({ scroll }) => {
21537
22140
  if (scroll?.direction === "up")
21538
22141
  scrollback.scrollByLines(-1);
@@ -21550,11 +22153,13 @@ async function startTui(client, options = {}) {
21550
22153
  appliedBackground = shownBackground;
21551
22154
  applyTheme(shownTheme, shownBackground);
21552
22155
  if (themeChanged) {
21553
- const previous = syntaxStyle;
22156
+ const previous = [syntaxStyle, mutedSyntaxStyle];
21554
22157
  syntaxStyle = createSyntaxStyle(activeRenderer);
21555
- scrollback.reset(syntaxStyle);
22158
+ mutedSyntaxStyle = createMutedSyntaxStyle(activeRenderer);
22159
+ scrollback.reset(syntaxStyle, mutedSyntaxStyle);
21556
22160
  diffViewer.reset(syntaxStyle);
21557
- previous.destroy();
22161
+ for (const style of previous)
22162
+ style.destroy();
21558
22163
  }
21559
22164
  root.backgroundColor = Theme.bg.default;
21560
22165
  input.applyColors();
@@ -21564,6 +22169,8 @@ async function startTui(client, options = {}) {
21564
22169
  syncTheme(state);
21565
22170
  header.update(state);
21566
22171
  statusLine.update(state);
22172
+ taskPanel.update(state);
22173
+ promptQueue.update(state);
21567
22174
  toastView.update(state);
21568
22175
  infoBar.update(state);
21569
22176
  scrollback.update(state);
@@ -21575,7 +22182,7 @@ async function startTui(client, options = {}) {
21575
22182
  input.setFocused(inputFocused);
21576
22183
  scrollback.setFocused(!inputFocused);
21577
22184
  input.setBusy(isStatusBusy(state.status));
21578
- input.setBorderColor(inputFocused ? Theme.status.success : Theme.border.separator);
22185
+ input.setBorderColor(inputFocused ? Theme.fg.muted : Theme.border.separator);
21579
22186
  const awaitingId = (findAwaitingCalls(state)[0] ?? findQuestionCalls(state)[0])?.id;
21580
22187
  if (awaitingId && awaitingId !== revealedCallId) {
21581
22188
  scrollback.revealEntry(findGroupId(state, awaitingId) ?? "");
@@ -21583,13 +22190,17 @@ async function startTui(client, options = {}) {
21583
22190
  revealedCallId = awaitingId;
21584
22191
  };
21585
22192
  store.subscribe(render);
22193
+ store.subscribe(() => sendNextQueuedPrompt(store));
21586
22194
  const notifier = new TurnNotifier(activeRenderer);
21587
22195
  store.subscribeOwnEvents((event) => {
21588
22196
  reactToEvent(store, event);
21589
22197
  notifier.notify(event);
21590
22198
  if (event.type === "ui.exit.requested")
21591
22199
  activeRenderer.destroy();
22200
+ if (event.type === "ui.title.set")
22201
+ activeRenderer.setTerminalTitle(event.title);
21592
22202
  });
22203
+ store.emit({ type: "ui.title.set", title: "Claudin" });
21593
22204
  const editor = {
21594
22205
  read: () => input.text,
21595
22206
  replace: (text) => input.replaceText(text, text.length)
@@ -21611,7 +22222,8 @@ async function startTui(client, options = {}) {
21611
22222
  key.preventDefault();
21612
22223
  });
21613
22224
  const clock = setInterval(() => {
21614
- if (store.state.status !== "idle")
22225
+ const { status, tasks } = store.state;
22226
+ if (status !== "idle" || tasks.length > 0)
21615
22227
  store.emit({ type: "ui.clock.ticked", now: Date.now() });
21616
22228
  }, CLOCK_INTERVAL_MS);
21617
22229
  let lastRateLimitAt = Date.now();