@couralabs/claudin 0.1.5 → 0.1.7

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 +677 -195
  2. package/package.json +1 -1
package/claudin.js CHANGED
@@ -1,5 +1,16 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+
4
+ // packages/daemon/src/auth/connectionCredentials.ts
5
+ import { randomBytes } from "crypto";
6
+ var createRunId = () => crypto.randomUUID();
7
+ var createAccessToken = () => randomBytes(32).toString("base64url");
8
+ function appendCredentials(url, { token, runId }) {
9
+ const target = new URL(url);
10
+ target.searchParams.set("token", token);
11
+ target.searchParams.set("runId", runId);
12
+ return target.toString();
13
+ }
3
14
  // node_modules/.bun/zod@4.6.7/node_modules/zod/v4/core/util.js
4
15
  function getEnumValues(entries) {
5
16
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
@@ -5928,6 +5939,12 @@ var backgroundColorSchema = object({
5928
5939
  a: channelSchema
5929
5940
  });
5930
5941
  var TRANSPARENT_BACKGROUND = { r: 0, g: 0, b: 0, a: 0 };
5942
+ // packages/events/src/models/customTools.ts
5943
+ var CUSTOM_SERVER_NAME = "claudin";
5944
+ var BATCH_EDIT_BASE_NAME = "BatchEdit";
5945
+ var BATCH_EDIT_TOOL_NAME = `mcp__${CUSTOM_SERVER_NAME}__${BATCH_EDIT_BASE_NAME}`;
5946
+ var CUSTOM_PREFIX = `mcp__${CUSTOM_SERVER_NAME}__`;
5947
+ var stripCustomPrefix = (toolName) => toolName.startsWith(CUSTOM_PREFIX) ? toolName.slice(CUSTOM_PREFIX.length) : toolName;
5931
5948
  // packages/events/src/models/fileDiff.ts
5932
5949
  var fileDiffSchema = object({
5933
5950
  path: string2(),
@@ -6064,6 +6081,7 @@ function appendEntry(state, entry) {
6064
6081
  };
6065
6082
  }
6066
6083
  var appendNotice = (state, text) => appendEntry(state, { kind: "notice", text });
6084
+ var appendMarkdownNotice = (state, text) => appendEntry(state, { kind: "notice", text, markdown: true });
6067
6085
 
6068
6086
  // packages/events/src/reducers/reduceHistory.ts
6069
6087
  var NO_MATCH_NOTICE = "No earlier prompt matches";
@@ -6111,6 +6129,7 @@ var agentSessionId = string2();
6111
6129
  var content = sdkValue();
6112
6130
  var agentOptions = sdkValue().optional();
6113
6131
  var effortLevelSchema = _enum(["low", "medium", "high", "xhigh", "max"]);
6132
+ var DEFAULT_EFFORT = "high";
6114
6133
  var permissionModeSchema = _enum(["auto", "acceptEdits", "bypassPermissions", "dontAsk"]);
6115
6134
  var DEFAULT_PERMISSION_MODE = "bypassPermissions";
6116
6135
  var PERMISSION_MODE_LABELS = {
@@ -6145,8 +6164,6 @@ var agentRequestSchemas = [
6145
6164
  options: agentOptions
6146
6165
  }),
6147
6166
  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
6167
  object({ type: literal("agent.prompt.cancel.requested"), agentSessionId }),
6151
6168
  object({ type: literal("agent.task.stop.requested"), agentSessionId, taskId: string2() }),
6152
6169
  object({
@@ -6218,6 +6235,7 @@ var createSessionState = (sessionId) => ({
6218
6235
  models: [],
6219
6236
  sessionList: [],
6220
6237
  queuedTurns: 0,
6238
+ queuedPrompts: [],
6221
6239
  tasks: [],
6222
6240
  transcript: [],
6223
6241
  entryCounter: 0,
@@ -6317,10 +6335,11 @@ function describeSystemMessage(message) {
6317
6335
  var OPEN_STATUSES = new Set(["running", "awaiting_approval", "awaiting_answer"]);
6318
6336
  var isToolsEntry = (entry) => entry.kind === "tools";
6319
6337
  var listToolCalls = (state) => state.transcript.filter(isToolsEntry).flatMap((entry) => entry.calls);
6320
- var EDIT_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
6321
- var readEditedPath = ({ input }) => input.file_path ?? input.notebook_path;
6338
+ var EDIT_TOOLS = new Set(["Write", "Edit", "MultiEdit", "mcp__claudin__BatchEdit"]);
6339
+ var listBatchEditPaths = (input) => Array.isArray(input.edits) ? input.edits.map((edit) => edit?.file_path) : [];
6340
+ 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] : [];
6322
6341
  var listEditedFiles = (state) => [
6323
- ...new Set(listToolCalls(state).filter((call) => EDIT_TOOLS.has(call.name) && call.status === "completed").map(readEditedPath).filter((path) => typeof path === "string"))
6342
+ ...new Set(listToolCalls(state).filter((call) => call.status === "completed").flatMap(readEditedPaths).filter((path) => typeof path === "string"))
6324
6343
  ];
6325
6344
  function countTurnTools(state) {
6326
6345
  const lastUser = state.transcript.findLastIndex((entry) => entry.kind === "user");
@@ -6519,6 +6538,43 @@ function reduceStreamEvent(state, event) {
6519
6538
  }
6520
6539
  }
6521
6540
 
6541
+ // packages/events/src/reducers/reduceTaskModel.ts
6542
+ var readMessageModel = (message) => {
6543
+ if (message.type === "assistant")
6544
+ return message.message.model;
6545
+ if (message.type !== "stream_event")
6546
+ return;
6547
+ const event = message.event;
6548
+ return event.type === "message_start" ? event.message?.model : undefined;
6549
+ };
6550
+ var readRequestedModel = (state, toolUseId) => {
6551
+ const model = listToolCalls(state).find((call) => call.id === toolUseId)?.input.model;
6552
+ return typeof model === "string" ? model : undefined;
6553
+ };
6554
+ function reduceTaskModel(state, message) {
6555
+ if (message.type !== "assistant" && message.type !== "stream_event")
6556
+ return state;
6557
+ const model = readMessageModel(message);
6558
+ const parentId = message.parent_tool_use_id;
6559
+ const stale = (task) => task.toolUseId === parentId && task.model !== model;
6560
+ if (!model || !parentId || !state.tasks.some(stale))
6561
+ return state;
6562
+ const tasks = state.tasks.map((task) => stale(task) ? { ...task, model } : task);
6563
+ return { ...state, tasks };
6564
+ }
6565
+
6566
+ // packages/events/src/reducers/reduceTaskToolUse.ts
6567
+ function reduceTaskToolUse(state, message) {
6568
+ if (message.type !== "assistant" || !message.parent_tool_use_id)
6569
+ return state;
6570
+ const call = message.message.content.findLast((block) => block.type === "tool_use");
6571
+ if (call?.type !== "tool_use")
6572
+ return state;
6573
+ const lastToolCall = { name: call.name, input: call.input ?? {} };
6574
+ const tasks = state.tasks.map((task) => task.toolUseId === message.parent_tool_use_id ? { ...task, lastToolCall } : task);
6575
+ return { ...state, tasks };
6576
+ }
6577
+
6522
6578
  // packages/events/src/reducers/reduceTaskProgress.ts
6523
6579
  function reduceTaskProgress(state, message) {
6524
6580
  const { tool_use_id: toolUseId, summary } = message;
@@ -6539,6 +6595,8 @@ var startTask = (state, message) => {
6539
6595
  kind: message.subagent_type ?? message.task_type ?? "task",
6540
6596
  description: message.description,
6541
6597
  startedAt: state.now,
6598
+ toolUseId: message.tool_use_id,
6599
+ model: readRequestedModel(state, message.tool_use_id),
6542
6600
  tokens: 0,
6543
6601
  paused: false
6544
6602
  };
@@ -6609,9 +6667,12 @@ function reduceAgentMessage(state, message) {
6609
6667
  };
6610
6668
  case "stream_event":
6611
6669
  if (message.parent_tool_use_id)
6612
- return state;
6670
+ return reduceTaskModel(state, message);
6613
6671
  return reduceStreamEvent(state, message.event);
6614
6672
  case "assistant":
6673
+ if (message.parent_tool_use_id) {
6674
+ return reduceTaskToolUse(reduceTaskModel(state, message), message);
6675
+ }
6615
6676
  return reduceAssistant(state, message);
6616
6677
  case "user":
6617
6678
  return isPromptEcho(message) ? reduceCheckpoint(state, message) : reduceToolResults(state, message);
@@ -6628,7 +6689,8 @@ function reduceTaskMessage(state, message) {
6628
6689
  return reduceTaskProgress(state, message);
6629
6690
  }
6630
6691
  if (message.type === "system" && message.subtype === "task_notification") {
6631
- return reduceNoticeMessage(state, message);
6692
+ const text = describeSystemMessage(message);
6693
+ return text ? appendMarkdownNotice(state, text) : state;
6632
6694
  }
6633
6695
  return state;
6634
6696
  }
@@ -6812,6 +6874,23 @@ function changeQuestionCustom(state, text) {
6812
6874
  return question ? withDraft(state, { ...question, custom: text }) : state;
6813
6875
  }
6814
6876
 
6877
+ // packages/events/src/reducers/reduceTheme.ts
6878
+ var applyLoadedTheme = (state, theme = state.theme) => ({
6879
+ ...state,
6880
+ theme
6881
+ });
6882
+ var applySavedTheme = (state, theme) => appendNotice({ ...state, theme }, `Theme set to ${theme}`);
6883
+
6884
+ // packages/events/src/reducers/reducePromptQueue.ts
6885
+ var queuePrompt = (state, content) => ({
6886
+ ...state,
6887
+ queuedPrompts: [...state.queuedPrompts, content]
6888
+ });
6889
+ var dequeuePrompt = (state) => ({
6890
+ ...state,
6891
+ queuedPrompts: state.queuedPrompts.slice(1)
6892
+ });
6893
+
6815
6894
  // packages/events/src/reducers/reduceToast.ts
6816
6895
  var withToast = (state, toast) => ({
6817
6896
  ...state,
@@ -6839,6 +6918,8 @@ var pickerSize = (state, kind) => {
6839
6918
  return state.models.length;
6840
6919
  if (kind === "theme")
6841
6920
  return THEME_NAMES.length;
6921
+ if (kind === "effort")
6922
+ return effortLevelSchema.options.length;
6842
6923
  if (kind === "rewind")
6843
6924
  return listCheckpoints(state).length;
6844
6925
  return state.sessionList.length;
@@ -6857,6 +6938,11 @@ var openThemePicker = (state) => {
6857
6938
  const selected = Math.max(THEME_NAMES.indexOf(state.theme), 0);
6858
6939
  return { ...state, ui: { ...state.ui, picker: { kind: "theme", selected } } };
6859
6940
  };
6941
+ var openEffortPicker = (state) => {
6942
+ const current = state.effort || DEFAULT_EFFORT;
6943
+ const selected = Math.max(effortLevelSchema.options.findIndex((level) => level === current), 0);
6944
+ return { ...state, ui: { ...state.ui, picker: { kind: "effort", selected } } };
6945
+ };
6860
6946
  function openRewindPicker(state) {
6861
6947
  const size = listCheckpoints(state).length;
6862
6948
  if (size === 0)
@@ -6881,8 +6967,12 @@ function reduceUi(state, event) {
6881
6967
  return { ...state, ui: { ...state.ui, picker: null } };
6882
6968
  case "ui.theme.picker.opened":
6883
6969
  return openThemePicker(state);
6884
- case "ui.theme.selected":
6885
- return appendNotice({ ...state, theme: event.theme }, `Theme set to ${event.theme}`);
6970
+ case "ui.effort.picker.opened":
6971
+ return openEffortPicker(state);
6972
+ case "ui.prompt.queued":
6973
+ return queuePrompt(state, event.content);
6974
+ case "ui.prompt.dequeued":
6975
+ return dequeuePrompt(state);
6886
6976
  case "ui.rewind.picker.opened":
6887
6977
  return openRewindPicker(state);
6888
6978
  case "ui.history.moved":
@@ -6961,9 +7051,6 @@ var reduceTimedEvent = (state, event, at) => reduceSessionState({ ...state, now:
6961
7051
  function reduceSessionState(state, event) {
6962
7052
  switch (event.type) {
6963
7053
  case "agent.prompt.requested":
6964
- return reducePrompt(state, event.content, false);
6965
- case "agent.prompt.queue.requested":
6966
- case "agent.prompt.steer.requested":
6967
7054
  return reducePrompt(state, event.content, state.status !== "idle");
6968
7055
  case "agent.message.received":
6969
7056
  return reduceAgentMessage(state, event.message);
@@ -7040,9 +7127,11 @@ function reduceSessionState(state, event) {
7040
7127
  case "workspace.info.received":
7041
7128
  return { ...state, cwd: event.cwd, gitBranch: event.gitBranch, gitStats: event.gitStats };
7042
7129
  case "options.loaded":
7043
- return applyLoadedBackground(state, event.background);
7130
+ return applyLoadedTheme(applyLoadedBackground(state, event.background), event.theme);
7044
7131
  case "options.background.saved":
7045
7132
  return applySavedBackground(state, event.color);
7133
+ case "options.theme.saved":
7134
+ return applySavedTheme(state, event.theme);
7046
7135
  case "workspace.files.received":
7047
7136
  return updateFileMatches(state, event.query, event.matches);
7048
7137
  default:
@@ -7055,13 +7144,22 @@ var optionsRequestSchemas = [
7055
7144
  object({
7056
7145
  type: literal("options.background.save.requested"),
7057
7146
  color: backgroundColorSchema
7058
- })
7147
+ }),
7148
+ object({ type: literal("options.theme.save.requested"), theme: themeNameSchema })
7059
7149
  ];
7060
7150
  var optionsResponseSchemas = [
7061
- object({ type: literal("options.loaded"), background: backgroundColorSchema.optional() }),
7062
- object({ type: literal("options.background.saved"), color: backgroundColorSchema })
7151
+ object({
7152
+ type: literal("options.loaded"),
7153
+ background: backgroundColorSchema.optional(),
7154
+ theme: themeNameSchema.optional()
7155
+ }),
7156
+ object({ type: literal("options.background.saved"), color: backgroundColorSchema }),
7157
+ object({ type: literal("options.theme.saved"), theme: themeNameSchema })
7063
7158
  ];
7064
- var optionsFileSchema = looseObject({ background: backgroundColorSchema.optional() });
7159
+ var optionsFileSchema = looseObject({
7160
+ background: backgroundColorSchema.optional(),
7161
+ theme: themeNameSchema.optional()
7162
+ });
7065
7163
  // packages/events/src/requests/sessionRequestEvents.ts
7066
7164
  var sessionRequestSchemas = [
7067
7165
  object({ type: literal("session.create.requested") }),
@@ -7122,7 +7220,6 @@ var agentResponseSchemas = [
7122
7220
  }),
7123
7221
  object({ type: literal("agent.session.created"), agentSessionId: agentSessionId2 }),
7124
7222
  object({ type: literal("agent.message.received"), message: unknown() }),
7125
- object({ type: literal("agent.prompt.accepted"), agentSessionId: agentSessionId2 }),
7126
7223
  object({ type: literal("agent.prompt.cancelled"), agentSessionId: agentSessionId2 }),
7127
7224
  object({
7128
7225
  type: literal("agent.tool.approval.required"),
@@ -7464,8 +7561,8 @@ class DaemonClient {
7464
7561
  this.socket = socket;
7465
7562
  socket.addEventListener("message", (event) => this.notifyListeners(String(event.data)));
7466
7563
  }
7467
- static async connect(url) {
7468
- const socket = new WebSocket(url);
7564
+ static async connect(url, credentials) {
7565
+ const socket = new WebSocket(appendCredentials(url, credentials));
7469
7566
  await new Promise((resolve, reject) => {
7470
7567
  socket.addEventListener("open", () => resolve(), { once: true });
7471
7568
  socket.addEventListener("error", () => reject(new Error(`Cannot reach ${url}`)), {
@@ -7532,11 +7629,10 @@ var sdkRuntime = {
7532
7629
  };
7533
7630
  // packages/agent/src/runtime/buildUserMessage.ts
7534
7631
  var isSlashCommand = (content) => readPromptText(content).trimStart().startsWith("/");
7535
- var buildUserMessage = (content, priority) => ({
7632
+ var buildUserMessage = (content) => ({
7536
7633
  type: "user",
7537
7634
  message: { role: "user", content },
7538
7635
  parent_tool_use_id: null,
7539
- priority,
7540
7636
  client_composed: isSlashCommand(content) ? undefined : true
7541
7637
  });
7542
7638
  // node_modules/.bun/p-defer@4.0.1/node_modules/p-defer/index.js
@@ -8731,8 +8827,10 @@ class PermissionGate {
8731
8827
  }
8732
8828
  open(kind, toolUseId, input, signal, announcement) {
8733
8829
  return new Promise((settle) => {
8734
- this.pending.set(toolUseId, { kind, input, settle });
8735
- signal.addEventListener("abort", () => this.dismiss(kind, toolUseId), { once: true });
8830
+ const onAbort = () => this.dismiss(kind, toolUseId);
8831
+ const detach = () => signal.removeEventListener("abort", onAbort);
8832
+ this.pending.set(toolUseId, { kind, input, settle, detach });
8833
+ signal.addEventListener("abort", onAbort, { once: true });
8736
8834
  this.emit(announcement);
8737
8835
  });
8738
8836
  }
@@ -8747,6 +8845,7 @@ class PermissionGate {
8747
8845
  if (pending?.kind !== kind)
8748
8846
  return;
8749
8847
  this.pending.delete(toolUseId);
8848
+ pending.detach();
8750
8849
  return pending;
8751
8850
  }
8752
8851
  listIds(kind) {
@@ -8761,6 +8860,110 @@ class PermissionGate {
8761
8860
  }
8762
8861
  }
8763
8862
 
8863
+ // packages/agent/src/tools/customToolsServer.ts
8864
+ import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
8865
+
8866
+ // packages/agent/src/tools/batchEdit/batchEditTool.ts
8867
+ import { tool } from "@anthropic-ai/claude-agent-sdk";
8868
+
8869
+ // packages/agent/src/tools/batchEdit/batchEditSchema.ts
8870
+ var textEditSchema = object({
8871
+ file_path: string2().describe("Absolute path of the file to modify"),
8872
+ old_string: string2().describe("Exact text to replace; empty only to create a new file with new_string"),
8873
+ new_string: string2().describe("Replacement text; must differ from old_string"),
8874
+ replace_all: boolean2().optional().describe("Replace every occurrence of old_string")
8875
+ });
8876
+ var batchEditShape = {
8877
+ edits: array(textEditSchema).min(1).describe("Edits applied in order; edits to one file see the result of earlier ones")
8878
+ };
8879
+
8880
+ // packages/agent/src/tools/batchEdit/stageEdits.ts
8881
+ import { isAbsolute, resolve } from "path";
8882
+
8883
+ // packages/agent/src/tools/batchEdit/applyTextEdit.ts
8884
+ var failEdit = (reason) => ({ ok: false, reason });
8885
+ function applyTextEdit(content, edit) {
8886
+ const { old_string: oldText, new_string: newText, replace_all: replaceAll } = edit;
8887
+ if (oldText === newText)
8888
+ return failEdit("old_string and new_string are identical");
8889
+ if (oldText === "") {
8890
+ return content === "" ? { ok: true, content: newText, replacements: 1 } : failEdit("old_string is empty but the file already has content");
8891
+ }
8892
+ const parts = content.split(oldText);
8893
+ const matches = parts.length - 1;
8894
+ if (matches === 0)
8895
+ return failEdit("old_string was not found in the file");
8896
+ if (matches > 1 && !replaceAll) {
8897
+ return failEdit(`old_string matches ${matches} times; add context or set replace_all`);
8898
+ }
8899
+ return { ok: true, content: parts.join(newText), replacements: matches };
8900
+ }
8901
+
8902
+ // packages/agent/src/tools/batchEdit/stageEdits.ts
8903
+ var failStage = (index, edit, reason) => ({
8904
+ ok: false,
8905
+ message: `Edit ${index + 1} (${edit.file_path}): ${reason}`
8906
+ });
8907
+ async function stageEdits(edits, readFile) {
8908
+ const files = new Map;
8909
+ for (const [index, edit] of edits.entries()) {
8910
+ if (!isAbsolute(edit.file_path))
8911
+ return failStage(index, edit, "file_path must be absolute");
8912
+ const path = resolve(edit.file_path);
8913
+ const current = files.get(path)?.content ?? await readFile(path);
8914
+ if (current === undefined && edit.old_string !== "") {
8915
+ return failStage(index, edit, "the file does not exist");
8916
+ }
8917
+ const outcome = applyTextEdit(current ?? "", edit);
8918
+ if (!outcome.ok)
8919
+ return failStage(index, edit, outcome.reason);
8920
+ const replacements = (files.get(path)?.replacements ?? 0) + outcome.replacements;
8921
+ files.set(path, { content: outcome.content, replacements });
8922
+ }
8923
+ return { ok: true, files };
8924
+ }
8925
+
8926
+ // packages/agent/src/tools/batchEdit/runBatchEdit.ts
8927
+ var readFileText = async (path) => {
8928
+ const file = Bun.file(path);
8929
+ return await file.exists() ? file.text() : undefined;
8930
+ };
8931
+ var describeFiles = (files) => [...files].map(([path, { replacements }]) => `- ${path}: ${replacements} replacement(s)`).join(`
8932
+ `);
8933
+ async function runBatchEdit(edits, readFile = readFileText) {
8934
+ const staged = await stageEdits(edits, readFile);
8935
+ if (!staged.ok) {
8936
+ return { isError: true, text: `${staged.message}
8937
+ No files were changed.` };
8938
+ }
8939
+ try {
8940
+ await Promise.all([...staged.files].map(([path, { content }]) => Bun.write(path, content)));
8941
+ } catch (error) {
8942
+ return { isError: true, text: `Writing failed, files may be partly changed: ${error}` };
8943
+ }
8944
+ return {
8945
+ isError: false,
8946
+ text: `Applied ${edits.length} edit(s) to ${staged.files.size} file(s):
8947
+ ${describeFiles(staged.files)}`
8948
+ };
8949
+ }
8950
+
8951
+ // packages/agent/src/tools/batchEdit/batchEditTool.ts
8952
+ 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.`;
8953
+ var batchEditTool = tool(BATCH_EDIT_BASE_NAME, DESCRIPTION, batchEditShape, async ({ edits }) => {
8954
+ const { isError, text } = await runBatchEdit(edits);
8955
+ return { isError, content: [{ type: "text", text }] };
8956
+ });
8957
+
8958
+ // packages/agent/src/tools/customToolsServer.ts
8959
+ var CUSTOM_MCP_SERVERS = {
8960
+ [CUSTOM_SERVER_NAME]: createSdkMcpServer({
8961
+ name: CUSTOM_SERVER_NAME,
8962
+ tools: [batchEditTool],
8963
+ alwaysLoad: true
8964
+ })
8965
+ };
8966
+
8764
8967
  // packages/agent/src/profile/adhdSystemPrompt.ts
8765
8968
  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.
8766
8969
 
@@ -8875,8 +9078,7 @@ var AGENT_TOOLS = [
8875
9078
  "TodoWrite",
8876
9079
  "AskUserQuestion",
8877
9080
  "Write",
8878
- "Edit",
8879
- "NotebookEdit"
9081
+ "Edit"
8880
9082
  ];
8881
9083
  var AGENT_DISALLOWED_TOOLS = [
8882
9084
  "Agent(Explore)",
@@ -8903,11 +9105,12 @@ var TOOL_USAGE = {
8903
9105
  AskUserQuestion: "ask the user",
8904
9106
  Write: "create new files with content",
8905
9107
  Edit: "update files",
8906
- NotebookEdit: "update notebook cells"
9108
+ mcp__claudin__BatchEdit: "update files mutiples files or multiple replaces in same file"
8907
9109
  };
8908
9110
  var TOOL_GUIDANCE = `Always use the dedicated tool, never a workaround:
8909
9111
  ${AGENT_TOOLS.map((tool) => `- Use *${tool}* to ${TOOL_USAGE[tool]}`).join(`
8910
- `)}`;
9112
+ `)}
9113
+ - Use *${BATCH_EDIT_TOOL_NAME}* instead of repeated *Edit* calls to make several edits, across one or many files, in one call`;
8911
9114
 
8912
9115
  // packages/agent/src/profile/agentSystemPrompt.ts
8913
9116
  var AGENT_SYSTEM_PROMPT = [ADHD_SYSTEM_PROMPT, TOOL_GUIDANCE, SUBAGENT_GUIDANCE].join(`
@@ -8917,6 +9120,8 @@ var AGENT_SYSTEM_PROMPT = [ADHD_SYSTEM_PROMPT, TOOL_GUIDANCE, SUBAGENT_GUIDANCE]
8917
9120
  // packages/agent/src/profile/agentProfile.ts
8918
9121
  var AGENT_PROFILE_OPTIONS = {
8919
9122
  tools: AGENT_TOOLS,
9123
+ mcpServers: CUSTOM_MCP_SERVERS,
9124
+ toolAliases: { [BATCH_EDIT_BASE_NAME]: BATCH_EDIT_TOOL_NAME },
8920
9125
  disallowedTools: AGENT_DISALLOWED_TOOLS,
8921
9126
  systemPrompt: AGENT_SYSTEM_PROMPT,
8922
9127
  agents: AGENT_SUBAGENTS,
@@ -8951,11 +9156,25 @@ var AGENT_PROFILE_OPTIONS = {
8951
9156
  includeHookEvents: true
8952
9157
  };
8953
9158
 
8954
- // packages/agent/src/session/readBackgroundTaskCount.ts
8955
- function readBackgroundTaskCount(message, current) {
8956
- if (message.type !== "system" || message.subtype !== "background_tasks_changed")
9159
+ // packages/agent/src/session/readLiveTaskIds.ts
9160
+ var FINISHED_STATUSES2 = ["completed", "failed", "killed"];
9161
+ var withTask = (current, id) => new Set([...current, id]);
9162
+ var withoutTask = (current, id) => new Set([...current].filter((taskId) => taskId !== id));
9163
+ function readLiveTaskIds(message, current) {
9164
+ if (message.type !== "system")
8957
9165
  return current;
8958
- return message.tasks.length;
9166
+ switch (message.subtype) {
9167
+ case "background_tasks_changed":
9168
+ return new Set(message.tasks.filter((task) => !task.ambient).map((task) => task.task_id));
9169
+ case "task_started":
9170
+ return message.ambient ? current : withTask(current, message.task_id);
9171
+ case "task_updated":
9172
+ return FINISHED_STATUSES2.includes(message.patch.status ?? "") ? withoutTask(current, message.task_id) : current;
9173
+ case "task_notification":
9174
+ return withoutTask(current, message.task_id);
9175
+ default:
9176
+ return current;
9177
+ }
8959
9178
  }
8960
9179
 
8961
9180
  // packages/agent/src/session/agentSession.ts
@@ -8972,7 +9191,7 @@ class AgentSession {
8972
9191
  elicitations;
8973
9192
  query;
8974
9193
  pendingTurns = 0;
8975
- backgroundTaskCount = 0;
9194
+ liveTaskIds = new Set;
8976
9195
  pumping = false;
8977
9196
  constructor(agentSessionId, baseOptions, runtime) {
8978
9197
  this.baseOptions = baseOptions;
@@ -8988,13 +9207,13 @@ class AgentSession {
8988
9207
  }
8989
9208
  });
8990
9209
  }
8991
- prompt(content, priority) {
8992
- this.send(content, priority);
9210
+ prompt(content) {
9211
+ this.send(content);
8993
9212
  return this.streamTurn();
8994
9213
  }
8995
- send(content, priority) {
9214
+ send(content) {
8996
9215
  this.pendingTurns += 1;
8997
- this.input.push(buildUserMessage(content, priority));
9216
+ this.input.push(buildUserMessage(content));
8998
9217
  }
8999
9218
  async* streamTurn() {
9000
9219
  if (this.pumping)
@@ -9006,7 +9225,6 @@ class AgentSession {
9006
9225
  try {
9007
9226
  yield* sink;
9008
9227
  } finally {
9009
- this.pumping = false;
9010
9228
  this.listenGates(() => {});
9011
9229
  }
9012
9230
  }
@@ -9094,16 +9312,18 @@ class AgentSession {
9094
9312
  if (done)
9095
9313
  break;
9096
9314
  sink.push({ type: "agent.message.received", message: value });
9097
- this.backgroundTaskCount = readBackgroundTaskCount(value, this.backgroundTaskCount);
9315
+ this.liveTaskIds = readLiveTaskIds(value, this.liveTaskIds);
9098
9316
  if (value.type !== "result")
9099
9317
  continue;
9100
9318
  this.pendingTurns = Math.max(this.pendingTurns - 1, 0);
9101
- if (this.pendingTurns === 0 && this.backgroundTaskCount === 0)
9319
+ if (this.pendingTurns === 0 && this.liveTaskIds.size === 0)
9102
9320
  break;
9103
9321
  }
9322
+ this.pumping = false;
9104
9323
  sink.end();
9105
9324
  } catch (error) {
9106
9325
  this.pendingTurns = 0;
9326
+ this.pumping = false;
9107
9327
  sink.end(error);
9108
9328
  }
9109
9329
  }
@@ -9127,14 +9347,13 @@ class AgentSessionManager {
9127
9347
  }
9128
9348
  createSession(options = {}) {
9129
9349
  const sessionId = options.sessionId ?? crypto.randomUUID();
9130
- this.sessions.set(sessionId, new AgentSession(sessionId, { ...options, sessionId }, this.runtime));
9350
+ this.replaceSession(sessionId, { ...options, sessionId });
9131
9351
  return sessionId;
9132
9352
  }
9133
9353
  async resumeSession(sessionId, options = {}) {
9134
9354
  if (!await this.runtime.getInfo(sessionId))
9135
9355
  return this.createSession({ ...options, sessionId });
9136
- const resumed = new AgentSession(sessionId, { ...options, resume: sessionId }, this.runtime);
9137
- this.sessions.set(sessionId, resumed);
9356
+ this.replaceSession(sessionId, { ...options, resume: sessionId });
9138
9357
  return sessionId;
9139
9358
  }
9140
9359
  async forkSession(sessionId, forkOptions, options = {}) {
@@ -9149,6 +9368,10 @@ class AgentSessionManager {
9149
9368
  for (const sessionId of [...this.sessions.keys()])
9150
9369
  this.closeSession(sessionId);
9151
9370
  }
9371
+ replaceSession(sessionId, options) {
9372
+ this.closeSession(sessionId);
9373
+ this.sessions.set(sessionId, new AgentSession(sessionId, options, this.runtime));
9374
+ }
9152
9375
  requireSession(sessionId) {
9153
9376
  const session = this.sessions.get(sessionId);
9154
9377
  if (!session)
@@ -9165,19 +9388,21 @@ async function readOptions2(path) {
9165
9388
  const parsed = optionsFileSchema.safeParse(stored);
9166
9389
  return parsed.success ? parsed.data : {};
9167
9390
  }
9168
- async function saveBackground(path, background) {
9169
- await Bun.write(path, JSON.stringify({ ...await readOptions2(path), background }, null, 2));
9170
- }
9391
+ var saveOption = async (path, option) => {
9392
+ await Bun.write(path, JSON.stringify({ ...await readOptions2(path), ...option }, null, 2));
9393
+ };
9394
+ var saveBackground = (path, background) => saveOption(path, { background });
9395
+ var saveTheme = (path, theme) => saveOption(path, { theme });
9171
9396
 
9172
9397
  // packages/daemon/src/workspace/resolvePath.ts
9173
9398
  import { stat } from "fs/promises";
9174
9399
  import { homedir as homedir2 } from "os";
9175
- import { join as join3, resolve } from "path";
9400
+ import { join as join3, resolve as resolve2 } from "path";
9176
9401
  var isDirectory = async (path) => (await stat(path).catch(() => {
9177
9402
  return;
9178
9403
  }))?.isDirectory() ?? false;
9179
9404
  var expandHome = (path) => path === "~" || path.startsWith("~/") ? join3(homedir2(), path.slice(1)) : path;
9180
- var resolveUserPath = (base, path) => resolve(base, expandHome(path.trim()));
9405
+ var resolveUserPath = (base, path) => resolve2(base, expandHome(path.trim()));
9181
9406
 
9182
9407
  // packages/daemon/src/server/daemonContext.ts
9183
9408
  var SESSIONS_DIRECTORY = join4(homedir3(), ".claudin", "sessions");
@@ -9245,11 +9470,7 @@ async function createDaemonContext(store = new FileSessionStore(SESSIONS_DIRECTO
9245
9470
  // packages/daemon/src/client/replayClient.ts
9246
9471
  var MAX_GAP_MS = 1500;
9247
9472
  var READ_ONLY_REASON = "Replay is read-only";
9248
- var PROMPT_TYPES = new Set([
9249
- "agent.prompt.requested",
9250
- "agent.prompt.queue.requested",
9251
- "agent.prompt.steer.requested"
9252
- ]);
9473
+ var PROMPT_TYPES = new Set(["agent.prompt.requested"]);
9253
9474
 
9254
9475
  class ReplayClient {
9255
9476
  stored;
@@ -9300,6 +9521,31 @@ class ReplayClient {
9300
9521
  import { once } from "events";
9301
9522
  import { WebSocketServer } from "ws";
9302
9523
 
9524
+ // packages/daemon/src/auth/checkConnection.ts
9525
+ import { timingSafeEqual } from "crypto";
9526
+ var isSameSecret = (given, expected) => {
9527
+ const givenBytes = Buffer.from(given);
9528
+ const expectedBytes = Buffer.from(expected);
9529
+ return givenBytes.length === expectedBytes.length && timingSafeEqual(givenBytes, expectedBytes);
9530
+ };
9531
+ function readRunId(request) {
9532
+ return new URL(request.url ?? "/", "ws://127.0.0.1").searchParams.get("runId");
9533
+ }
9534
+ function checkConnection(request, token, liveRunIds) {
9535
+ if (request.headers.origin)
9536
+ return { status: 403, reason: "Browser origins are not allowed" };
9537
+ const params = new URL(request.url ?? "/", "ws://127.0.0.1").searchParams;
9538
+ if (!isSameSecret(params.get("token") ?? "", token)) {
9539
+ return { status: 401, reason: "Invalid access token" };
9540
+ }
9541
+ const runId = params.get("runId");
9542
+ if (!runId)
9543
+ return { status: 400, reason: "Missing run id" };
9544
+ if (liveRunIds.has(runId))
9545
+ return { status: 409, reason: "Run id already connected" };
9546
+ return;
9547
+ }
9548
+
9303
9549
  // packages/daemon/src/persistence/rateLimitCache.ts
9304
9550
  import { mkdir as mkdir2, readFile, writeFile } from "fs/promises";
9305
9551
  import { homedir as homedir4 } from "os";
@@ -9362,16 +9608,6 @@ var agentHandlers = {
9362
9608
  async* "agent.prompt.requested"({ agents }, request) {
9363
9609
  yield* agents.requireSession(request.agentSessionId).prompt(request.content);
9364
9610
  },
9365
- async* "agent.prompt.queue.requested"({ agents }, request) {
9366
- const turn = agents.requireSession(request.agentSessionId).prompt(request.content, "later");
9367
- yield { type: "agent.prompt.accepted", agentSessionId: request.agentSessionId };
9368
- yield* turn;
9369
- },
9370
- async* "agent.prompt.steer.requested"({ agents }, request) {
9371
- const turn = agents.requireSession(request.agentSessionId).prompt(request.content, "now");
9372
- yield { type: "agent.prompt.accepted", agentSessionId: request.agentSessionId };
9373
- yield* turn;
9374
- },
9375
9611
  async* "agent.prompt.cancel.requested"({ agents }, request) {
9376
9612
  await agents.requireSession(request.agentSessionId).cancel();
9377
9613
  yield { type: "agent.prompt.cancelled", agentSessionId: request.agentSessionId };
@@ -9455,12 +9691,16 @@ var agentHandlers = {
9455
9691
  // packages/daemon/src/handlers/optionsHandlers.ts
9456
9692
  var optionsHandlers = {
9457
9693
  async* "options.load.requested"({ optionsPath }) {
9458
- const { background } = await readOptions2(optionsPath);
9459
- yield { type: "options.loaded", background };
9694
+ const { background, theme } = await readOptions2(optionsPath);
9695
+ yield { type: "options.loaded", background, theme };
9460
9696
  },
9461
9697
  async* "options.background.save.requested"({ optionsPath }, request) {
9462
9698
  await saveBackground(optionsPath, request.color);
9463
9699
  yield { type: "options.background.saved", color: request.color };
9700
+ },
9701
+ async* "options.theme.save.requested"({ optionsPath }, request) {
9702
+ await saveTheme(optionsPath, request.theme);
9703
+ yield { type: "options.theme.saved", theme: request.theme };
9464
9704
  }
9465
9705
  };
9466
9706
 
@@ -9496,14 +9736,14 @@ var sessionHandlers = {
9496
9736
  };
9497
9737
 
9498
9738
  // packages/daemon/src/workspace/readFileDiffs.ts
9499
- import { relative, resolve as resolve2 } from "path";
9739
+ import { relative, resolve as resolve3 } from "path";
9500
9740
  var isTracked = async (cwd, path) => (await Bun.$`git ls-files --error-unmatch -- ${path}`.cwd(cwd).quiet().nothrow()).exitCode === 0;
9501
9741
  async function readPatch(cwd, path) {
9502
9742
  const command = await isTracked(cwd, path) ? Bun.$`git diff --no-color HEAD -- ${path}` : Bun.$`git diff --no-color --no-index -- /dev/null ${path}`;
9503
9743
  return (await command.cwd(cwd).quiet().nothrow()).text();
9504
9744
  }
9505
9745
  async function readFileDiff(cwd, path) {
9506
- const absolute = resolve2(cwd, path);
9746
+ const absolute = resolve3(cwd, path);
9507
9747
  const patch = await readPatch(cwd, absolute);
9508
9748
  return {
9509
9749
  path: relative(cwd, absolute) || path,
@@ -11163,11 +11403,18 @@ var workspaceHandlers = {
11163
11403
 
11164
11404
  // packages/daemon/src/server/handleRequest.ts
11165
11405
  var handlers = { ...sessionHandlers, ...agentHandlers, ...workspaceHandlers, ...optionsHandlers };
11406
+ var SESSION_SWITCH_TYPES = new Set([
11407
+ "session.created",
11408
+ "session.restored",
11409
+ "session.forked"
11410
+ ]);
11166
11411
  async function* handleRequest(context, request) {
11167
- await context.session.dispatch(request);
11412
+ const origin = context.session;
11413
+ await origin.dispatch(request);
11168
11414
  const handler = handlers[request.type];
11169
11415
  for await (const response of handler(context, request)) {
11170
- await context.session.dispatch(response);
11416
+ const target = SESSION_SWITCH_TYPES.has(response.type) ? context.session : origin;
11417
+ await target.dispatch(response);
11171
11418
  yield response;
11172
11419
  }
11173
11420
  }
@@ -11185,7 +11432,12 @@ function publishEvent(clients, origin, event, toOrigin = true) {
11185
11432
  }
11186
11433
 
11187
11434
  // packages/daemon/src/server/startDaemon.ts
11188
- var DEFAULT_PORT = 7777;
11435
+ var LOOPBACK_HOST = "127.0.0.1";
11436
+ async function reportFailure(context, server, socket, error) {
11437
+ const failure = { type: "request.failed", reason: String(error) };
11438
+ await context.session.dispatch(failure).catch(() => {});
11439
+ publishEvent(server.clients, socket, failure);
11440
+ }
11189
11441
  async function answerMessage(context, server, socket, data) {
11190
11442
  try {
11191
11443
  const request = requestEventSchema.parse(JSON.parse(data));
@@ -11194,15 +11446,27 @@ async function answerMessage(context, server, socket, data) {
11194
11446
  publishEvent(server.clients, socket, response);
11195
11447
  }
11196
11448
  } catch (error) {
11197
- const failure = { type: "request.failed", reason: String(error) };
11198
- await context.session.dispatch(failure);
11199
- publishEvent(server.clients, socket, failure);
11449
+ await reportFailure(context, server, socket, error);
11200
11450
  }
11201
11451
  }
11202
- async function startDaemon(port = DEFAULT_PORT, context) {
11452
+ async function startDaemon(port, token, context) {
11203
11453
  const daemonContext = context ?? await createDaemonContext();
11204
- const server = new WebSocketServer({ port });
11205
- server.on("connection", (socket) => {
11454
+ const liveRunIds = new Set;
11455
+ const server = new WebSocketServer({
11456
+ port,
11457
+ host: LOOPBACK_HOST,
11458
+ verifyClient: ({ req }, respond) => {
11459
+ const rejection = checkConnection(req, token, liveRunIds);
11460
+ if (rejection)
11461
+ respond(false, rejection.status, rejection.reason);
11462
+ else
11463
+ respond(true);
11464
+ }
11465
+ });
11466
+ server.on("connection", (socket, request) => {
11467
+ const runId = readRunId(request) ?? "";
11468
+ liveRunIds.add(runId);
11469
+ socket.once("close", () => liveRunIds.delete(runId));
11206
11470
  socket.on("message", (data) => answerMessage(daemonContext, server, socket, data.toString()));
11207
11471
  });
11208
11472
  server.once("close", () => daemonContext.agents.closeAll());
@@ -11211,7 +11475,7 @@ async function startDaemon(port = DEFAULT_PORT, context) {
11211
11475
  }
11212
11476
  // apps/tui/src/startTui.ts
11213
11477
  import {
11214
- BoxRenderable as BoxRenderable18,
11478
+ BoxRenderable as BoxRenderable19,
11215
11479
  createCliRenderer,
11216
11480
  createHostClipboard
11217
11481
  } from "@opentui/core";
@@ -11253,7 +11517,7 @@ function buildPalette(colors, background = TRANSPARENT_BACKGROUND) {
11253
11517
  },
11254
11518
  fg: {
11255
11519
  primary: hex("text"),
11256
- secondary: rgbToHex2(tint(color("text"), color("textMuted"), SECONDARY_TEXT_WEIGHT)),
11520
+ secondary: hex("secondary") ?? rgbToHex2(tint(color("text"), color("textMuted"), SECONDARY_TEXT_WEIGHT)),
11257
11521
  muted: hex("textMuted")
11258
11522
  },
11259
11523
  border: { subtle: hex("borderSubtle"), focus: hex("borderActive"), separator: hex("border") },
@@ -11264,7 +11528,38 @@ function buildPalette(colors, background = TRANSPARENT_BACKGROUND) {
11264
11528
  warning: hex("warning"),
11265
11529
  info: hex("info")
11266
11530
  },
11267
- diff: { addedBg: hex("diffAddedBg"), removedBg: hex("diffRemovedBg") }
11531
+ diff: {
11532
+ addedBg: hex("diffAddedBg"),
11533
+ removedBg: hex("diffRemovedBg"),
11534
+ contextBg: hex("diffContextBg"),
11535
+ addedText: hex("diffAdded"),
11536
+ removedText: hex("diffRemoved"),
11537
+ contextText: hex("diffContext"),
11538
+ lineNumber: hex("diffLineNumber"),
11539
+ addedLineNumberBg: hex("diffAddedLineNumberBg"),
11540
+ removedLineNumberBg: hex("diffRemovedLineNumberBg")
11541
+ },
11542
+ syntax: {
11543
+ comment: hex("syntaxComment"),
11544
+ keyword: hex("syntaxKeyword"),
11545
+ function: hex("syntaxFunction"),
11546
+ variable: hex("syntaxVariable"),
11547
+ string: hex("syntaxString"),
11548
+ number: hex("syntaxNumber"),
11549
+ type: hex("syntaxType"),
11550
+ operator: hex("syntaxOperator"),
11551
+ punctuation: hex("syntaxPunctuation")
11552
+ },
11553
+ markdown: {
11554
+ heading: hex("markdownHeading"),
11555
+ strong: hex("markdownStrong"),
11556
+ emphasis: hex("markdownEmph"),
11557
+ listItem: hex("markdownListItem"),
11558
+ blockQuote: hex("markdownBlockQuote"),
11559
+ code: hex("markdownCode"),
11560
+ link: hex("markdownLink"),
11561
+ linkText: hex("markdownLinkText")
11562
+ }
11268
11563
  };
11269
11564
  }
11270
11565
  // apps/tui/src/theme/assets/aura.json
@@ -18733,10 +19028,16 @@ var createDiffView = (renderer, syntaxStyle, patch, filetype) => new DiffRendera
18733
19028
  syntaxStyle,
18734
19029
  view: "unified",
18735
19030
  showLineNumbers: true,
19031
+ fg: Theme.fg.primary,
18736
19032
  addedBg: Theme.diff.addedBg,
18737
19033
  removedBg: Theme.diff.removedBg,
18738
- addedSignColor: Theme.status.success,
18739
- removedSignColor: Theme.status.danger,
19034
+ contextBg: Theme.diff.contextBg,
19035
+ lineNumberFg: Theme.diff.lineNumber,
19036
+ lineNumberBg: Theme.diff.contextBg,
19037
+ addedLineNumberBg: Theme.diff.addedLineNumberBg,
19038
+ removedLineNumberBg: Theme.diff.removedLineNumberBg,
19039
+ addedSignColor: Theme.diff.addedText,
19040
+ removedSignColor: Theme.diff.removedText,
18740
19041
  flexShrink: 0
18741
19042
  });
18742
19043
 
@@ -19025,11 +19326,11 @@ var buildImageBlock = (mediaType, bytes) => ({
19025
19326
  // apps/tui/src/prompt/pastedPaths.ts
19026
19327
  import { statSync } from "fs";
19027
19328
  import { homedir as homedir6 } from "os";
19028
- import { isAbsolute } from "path";
19329
+ import { isAbsolute as isAbsolute2 } from "path";
19029
19330
  var unwrapPath = (raw) => raw.trim().replace(/^(['"])(.*)\1$/, "$2").replace(/^file:\/\//, "").replace(/^~(?=\/)/, homedir6()).replace(/\\(.)/g, "$1");
19030
19331
  var isExistingFile = (path) => {
19031
19332
  try {
19032
- return isAbsolute(path) && statSync(path).isFile();
19333
+ return isAbsolute2(path) && statSync(path).isFile();
19033
19334
  } catch {
19034
19335
  return false;
19035
19336
  }
@@ -19075,7 +19376,7 @@ function planPaste(event, attachments) {
19075
19376
 
19076
19377
  // apps/tui/src/components/promptInput.ts
19077
19378
  var MAX_INPUT_LINES = 8;
19078
- var BUSY_TITLE = " Ctrl+Q (queue) Ctrl+W (steer) ";
19379
+ var BUSY_PLACEHOLDER = "Enter (steer) Ctrl+Q (queue)";
19079
19380
  var ATTACHMENT_STYLE = "attachment";
19080
19381
  var PLACEHOLDER = "Type your prompt, /slash command, or @file reference...";
19081
19382
  var KEY_BINDINGS = [
@@ -19097,12 +19398,11 @@ class PromptInput {
19097
19398
  constructor(renderer, handlers) {
19098
19399
  this.renderable = new BoxRenderable5(renderer, {
19099
19400
  border: ["top", "bottom"],
19401
+ borderColor: Theme.fg.muted,
19100
19402
  borderStyle: "rounded",
19101
- bottomTitleAlignment: "right",
19102
19403
  flexDirection: "row",
19103
19404
  height: 3,
19104
- flexShrink: 0,
19105
- paddingLeft: 1
19405
+ flexShrink: 0
19106
19406
  });
19107
19407
  this.attachmentStyle = SyntaxStyle.create(renderer.nativeScene);
19108
19408
  this.textarea = new TextareaRenderable(renderer, {
@@ -19163,16 +19463,15 @@ class PromptInput {
19163
19463
  this.textarea.blur();
19164
19464
  }
19165
19465
  setBusy(busy) {
19166
- const title = busy ? BUSY_TITLE : undefined;
19167
- if (this.renderable.bottomTitle !== title)
19168
- this.renderable.bottomTitle = title;
19466
+ const placeholder = busy ? BUSY_PLACEHOLDER : PLACEHOLDER;
19467
+ if (this.textarea.placeholder !== placeholder)
19468
+ this.textarea.placeholder = placeholder;
19169
19469
  }
19170
19470
  setBorderColor(color) {
19171
19471
  this.renderable.borderColor = color;
19172
19472
  }
19173
19473
  applyColors() {
19174
- this.renderable.titleColor = Theme.fg.primary;
19175
- this.chevron.fg = Theme.accent.brand;
19474
+ this.chevron.fg = Theme.fg.muted;
19176
19475
  this.textarea.placeholderColor = Theme.fg.muted;
19177
19476
  this.textarea.textColor = Theme.fg.primary;
19178
19477
  this.textarea.focusedTextColor = Theme.fg.primary;
@@ -19274,6 +19573,25 @@ class AssistantMessageView {
19274
19573
  }
19275
19574
  }
19276
19575
 
19576
+ // apps/tui/src/components/markdownNoticeView.ts
19577
+ import { MarkdownRenderable as MarkdownRenderable2 } from "@opentui/core";
19578
+
19579
+ class MarkdownNoticeView {
19580
+ renderable;
19581
+ constructor(renderer, mutedSyntaxStyle) {
19582
+ this.renderable = new MarkdownRenderable2(renderer, {
19583
+ content: "",
19584
+ syntaxStyle: mutedSyntaxStyle,
19585
+ marginTop: 1,
19586
+ flexShrink: 0
19587
+ });
19588
+ }
19589
+ update(entry) {
19590
+ if (this.renderable.content !== entry.text)
19591
+ this.renderable.content = entry.text;
19592
+ }
19593
+ }
19594
+
19277
19595
  // apps/tui/src/components/noticeView.ts
19278
19596
  import { fg as fg7, StyledText as StyledText6, TextRenderable as TextRenderable7 } from "@opentui/core";
19279
19597
  class NoticeView {
@@ -19928,7 +20246,9 @@ var clipLines = (text, maxLines) => {
19928
20246
  // apps/tui/src/format/toolInputText.ts
19929
20247
  var VALUE_LENGTH = 200;
19930
20248
  var formatInputValue = (value) => truncateText(typeof value === "string" ? value : JSON.stringify(value), VALUE_LENGTH);
19931
- var formatToolInput = ({ input }) => Object.entries(input).map(([key, value]) => `${key}: ${formatInputValue(value)}`).join(`
20249
+ var formatBatchEdit = ({ input }) => listBatchEditPaths(input).map((path, index) => `${index + 1}. ${formatInputValue(path)}`).join(`
20250
+ `);
20251
+ var formatToolInput = (call) => call.name === BATCH_EDIT_TOOL_NAME ? formatBatchEdit(call) : Object.entries(call.input).map(([key, value]) => `${key}: ${formatInputValue(value)}`).join(`
19932
20252
  `);
19933
20253
 
19934
20254
  // apps/tui/src/format/toolSummary.ts
@@ -19936,9 +20256,15 @@ var SUMMARY_LENGTH = 80;
19936
20256
  var SUMMARY_KEYS = ["file_path", "path", "command", "pattern", "query", "url", "description"];
19937
20257
  var SEARCH_TOOLS = new Set(["Glob", "Grep"]);
19938
20258
  var summarizeSearch = ({ pattern, path }) => typeof path === "string" ? `${pattern} in ${path}` : String(pattern);
20259
+ var summarizeBatchEdit = (input) => {
20260
+ const paths = listBatchEditPaths(input);
20261
+ return `${paths.length} edits in ${new Set(paths).size} files`;
20262
+ };
19939
20263
  function summarizeToolCall({ name, input, status, progress }) {
19940
20264
  if (status === "running" && progress)
19941
20265
  return truncateText(progress, SUMMARY_LENGTH);
20266
+ if (name === BATCH_EDIT_TOOL_NAME)
20267
+ return summarizeBatchEdit(input);
19942
20268
  if (SEARCH_TOOLS.has(name) && typeof input.pattern === "string") {
19943
20269
  return truncateText(summarizeSearch(input), SUMMARY_LENGTH);
19944
20270
  }
@@ -19947,6 +20273,8 @@ function summarizeToolCall({ name, input, status, progress }) {
19947
20273
  return truncateText(value, SUMMARY_LENGTH);
19948
20274
  }
19949
20275
  var describeApproval = ({ name, input }) => {
20276
+ if (name === BATCH_EDIT_TOOL_NAME)
20277
+ return `Apply ${summarizeBatchEdit(input)} with BatchEdit?`;
19950
20278
  if (name === "Bash")
19951
20279
  return `Run command \`${truncateText(String(input.command ?? ""), 60)}\`?`;
19952
20280
  const path = input.file_path ?? input.notebook_path;
@@ -20192,7 +20520,7 @@ class ToolCallView {
20192
20520
  const accent = this.hovered ? Theme.accent.brand : undefined;
20193
20521
  this.title.content = new StyledText10([
20194
20522
  fg11(accent ?? toolColor(call.status))(glyph),
20195
- bold6(fg11(accent ?? Theme.fg.primary)(call.name)),
20523
+ bold6(fg11(accent ?? Theme.fg.primary)(stripCustomPrefix(call.name))),
20196
20524
  fg11(Theme.fg.secondary)(` ${summarizeToolCall(call)}`),
20197
20525
  fg11(Theme.fg.muted)(stats)
20198
20526
  ]);
@@ -20352,9 +20680,9 @@ class ScrollbackView {
20352
20680
  else if (below > 0)
20353
20681
  this.renderable.scrollBy(Math.min(below, above));
20354
20682
  }
20355
- reset(syntaxStyle) {
20683
+ reset(syntaxStyle, mutedSyntaxStyle) {
20356
20684
  this.dropViews(() => true);
20357
- this.options = { syntaxStyle, hooks: { ...this.options.hooks, syntaxStyle } };
20685
+ this.options = { syntaxStyle, mutedSyntaxStyle, hooks: { ...this.options.hooks, syntaxStyle } };
20358
20686
  }
20359
20687
  dropRemovedViews(state) {
20360
20688
  const live = new Set(state.transcript.map((entry) => entry.id));
@@ -20376,14 +20704,14 @@ class ScrollbackView {
20376
20704
  return view;
20377
20705
  }
20378
20706
  createView(entry) {
20379
- const { syntaxStyle, hooks } = this.options;
20707
+ const { syntaxStyle, mutedSyntaxStyle, hooks } = this.options;
20380
20708
  switch (entry.kind) {
20381
20709
  case "user":
20382
20710
  return new UserMessageView(this.renderer);
20383
20711
  case "assistant":
20384
20712
  return new AssistantMessageView(this.renderer, syntaxStyle);
20385
20713
  case "notice":
20386
- return new NoticeView(this.renderer);
20714
+ return entry.markdown ? new MarkdownNoticeView(this.renderer, mutedSyntaxStyle) : new NoticeView(this.renderer);
20387
20715
  case "thought":
20388
20716
  return new ThoughtView(this.renderer);
20389
20717
  case "tools":
@@ -20462,15 +20790,14 @@ class SessionInfoBar {
20462
20790
  limits;
20463
20791
  constructor(renderer) {
20464
20792
  this.renderable = new BoxRenderable13(renderer, {
20465
- marginBottom: 1,
20466
20793
  flexShrink: 0,
20467
20794
  flexDirection: "row",
20468
20795
  flexWrap: "wrap",
20469
20796
  rowGap: 1,
20470
20797
  columnGap: 0,
20471
20798
  justifyContent: "space-between",
20472
- paddingLeft: 1,
20473
- paddingRight: 1
20799
+ paddingLeft: 2,
20800
+ paddingRight: 2
20474
20801
  });
20475
20802
  this.info = new TextRenderable13(renderer, { content: "", wrapMode: "none", flexShrink: 0 });
20476
20803
  this.limits = new TextRenderable13(renderer, {
@@ -20526,12 +20853,14 @@ class StatusLine {
20526
20853
  constructor(renderer, onStop) {
20527
20854
  this.renderable = new BoxRenderable14(renderer, {
20528
20855
  height: 2,
20529
- paddingTop: 1,
20530
20856
  flexShrink: 0,
20531
20857
  flexDirection: "row",
20532
20858
  justifyContent: "space-between",
20859
+ border: ["top"],
20860
+ borderColor: Theme.border.subtle,
20533
20861
  paddingLeft: 2,
20534
- paddingRight: 2
20862
+ paddingRight: 2,
20863
+ visible: false
20535
20864
  });
20536
20865
  this.label = new TextRenderable14(renderer, { content: "", wrapMode: "none" });
20537
20866
  this.stats = new TextRenderable14(renderer, {
@@ -20547,9 +20876,11 @@ class StatusLine {
20547
20876
  update(state) {
20548
20877
  if (state.status === "idle") {
20549
20878
  this.label.content = state.lastRun ? new StyledText13(buildLastRunChunks(state.lastRun, readLastPrompt(state))) : "";
20879
+ this.renderable.visible = state.lastRun !== undefined;
20550
20880
  this.stats.content = "";
20551
20881
  return;
20552
20882
  }
20883
+ this.renderable.visible = true;
20553
20884
  const label = agentLabel(state.status);
20554
20885
  const busy = label.icon === undefined;
20555
20886
  const frame = SPINNER_FRAMES[Math.floor(state.now / SPINNER_STEP_MS) % SPINNER_FRAMES.length];
@@ -20566,26 +20897,96 @@ class StatusLine {
20566
20897
  }
20567
20898
  }
20568
20899
 
20900
+ // apps/tui/src/components/promptQueueView.ts
20901
+ import {
20902
+ BoxRenderable as BoxRenderable15,
20903
+ bold as bold9,
20904
+ dim as dim7,
20905
+ fg as fg16,
20906
+ StyledText as StyledText14,
20907
+ TextRenderable as TextRenderable15
20908
+ } from "@opentui/core";
20909
+
20910
+ // apps/tui/src/format/promptQueueRows.ts
20911
+ var MAX_QUEUE_ROWS = 5;
20912
+ var QUEUE_TEXT_LENGTH = 80;
20913
+ var buildQueueRows = (queued) => queued.slice(0, MAX_QUEUE_ROWS).map((content, index) => `${index + 1}. ${truncateText(readPromptText(content), QUEUE_TEXT_LENGTH)}`);
20914
+ var buildQueueHeader = (queued) => `Queued${SEPARATOR}${queued.length} waiting`;
20915
+ var countHiddenQueued = (queued) => Math.max(queued.length - MAX_QUEUE_ROWS, 0);
20916
+
20917
+ // apps/tui/src/components/promptQueueView.ts
20918
+ class PromptQueueView {
20919
+ renderable;
20920
+ header;
20921
+ rows;
20922
+ overflow;
20923
+ constructor(renderer) {
20924
+ this.renderable = new BoxRenderable15(renderer, {
20925
+ flexShrink: 0,
20926
+ paddingLeft: 2,
20927
+ paddingRight: 2,
20928
+ visible: false
20929
+ });
20930
+ this.header = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20931
+ this.overflow = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20932
+ this.rows = Array.from({ length: MAX_QUEUE_ROWS }, () => new TextRenderable15(renderer, { content: "", wrapMode: "none" }));
20933
+ this.renderable.add(this.header);
20934
+ for (const row of this.rows)
20935
+ this.renderable.add(row);
20936
+ this.renderable.add(this.overflow);
20937
+ }
20938
+ update({ queuedPrompts }) {
20939
+ this.renderable.visible = queuedPrompts.length > 0;
20940
+ if (queuedPrompts.length === 0)
20941
+ return;
20942
+ this.header.content = new StyledText14([
20943
+ bold9(fg16(Theme.fg.secondary)(buildQueueHeader(queuedPrompts)))
20944
+ ]);
20945
+ const lines = buildQueueRows(queuedPrompts);
20946
+ this.rows.forEach((row, index) => {
20947
+ row.content = new StyledText14([fg16(Theme.fg.primary)(lines[index] ?? "")]);
20948
+ });
20949
+ const hidden = countHiddenQueued(queuedPrompts);
20950
+ this.overflow.content = hidden ? new StyledText14([dim7(fg16(Theme.fg.muted)(` +${hidden} more`))]) : "";
20951
+ }
20952
+ }
20953
+
20569
20954
  // apps/tui/src/components/taskPanel.ts
20570
20955
  import {
20571
- BoxRenderable as BoxRenderable16,
20572
- bold as bold10,
20573
- dim as dim8,
20574
- fg as fg17,
20575
- StyledText as StyledText15,
20576
- TextRenderable as TextRenderable16
20956
+ BoxRenderable as BoxRenderable17,
20957
+ bold as bold11,
20958
+ dim as dim9,
20959
+ fg as fg18,
20960
+ StyledText as StyledText16,
20961
+ TextRenderable as TextRenderable17
20577
20962
  } from "@opentui/core";
20578
20963
 
20579
20964
  // apps/tui/src/format/taskRows.ts
20580
20965
  var MAX_TASK_ROWS = 5;
20581
- var readDetail = ({ summary, lastToolName, paused }) => {
20582
- if (paused)
20966
+ var KIND_LABELS = { "general-purpose": "Agent", local_bash: "Bash" };
20967
+ var describeLastTool = ({ lastToolCall, lastToolName }) => {
20968
+ if (!lastToolCall)
20969
+ return lastToolName;
20970
+ const { name, input } = lastToolCall;
20971
+ const summary = summarizeToolCall({
20972
+ id: "",
20973
+ name,
20974
+ input,
20975
+ status: "running",
20976
+ expanded: false,
20977
+ startedAt: 0
20978
+ });
20979
+ return `${name} ${summary}`;
20980
+ };
20981
+ var readDetail = (task) => {
20982
+ if (task.paused)
20583
20983
  return "Paused";
20584
- return summary ?? lastToolName ?? "Starting...";
20984
+ return task.summary ?? describeLastTool(task) ?? "Starting...";
20585
20985
  };
20586
20986
  var buildTaskRow = (task, now) => ({
20587
20987
  taskId: task.id,
20588
- kind: task.kind,
20988
+ kind: KIND_LABELS[task.kind] ?? task.kind,
20989
+ model: task.model ?? "",
20589
20990
  title: task.description,
20590
20991
  stats: [formatDuration(Math.max(now - task.startedAt, 0)), `\u2193${formatTokenCount(task.tokens)}`].join(SEPARATOR).trim(),
20591
20992
  detail: readDetail(task)
@@ -20596,12 +20997,12 @@ var countHiddenTasks = (tasks) => Math.max(tasks.length - MAX_TASK_ROWS, 0);
20596
20997
 
20597
20998
  // apps/tui/src/components/taskRowView.ts
20598
20999
  import {
20599
- BoxRenderable as BoxRenderable15,
20600
- bold as bold9,
20601
- dim as dim7,
20602
- fg as fg16,
20603
- StyledText as StyledText14,
20604
- TextRenderable as TextRenderable15
21000
+ BoxRenderable as BoxRenderable16,
21001
+ bold as bold10,
21002
+ dim as dim8,
21003
+ fg as fg17,
21004
+ StyledText as StyledText15,
21005
+ TextRenderable as TextRenderable16
20605
21006
  } from "@opentui/core";
20606
21007
  var SPINNER_STEP_MS2 = 100;
20607
21008
 
@@ -20612,20 +21013,20 @@ class TaskRowView {
20612
21013
  detail;
20613
21014
  taskId = "";
20614
21015
  constructor(renderer, onStop) {
20615
- this.renderable = new BoxRenderable15(renderer, { flexShrink: 0, visible: false });
20616
- const line = new BoxRenderable15(renderer, {
21016
+ this.renderable = new BoxRenderable16(renderer, { flexShrink: 0, visible: false });
21017
+ const line = new BoxRenderable16(renderer, {
20617
21018
  flexDirection: "row",
20618
21019
  justifyContent: "space-between"
20619
21020
  });
20620
- this.title = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20621
- this.stop = new TextRenderable15(renderer, {
21021
+ this.title = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
21022
+ this.stop = new TextRenderable16(renderer, {
20622
21023
  content: "",
20623
21024
  wrapMode: "none",
20624
21025
  flexShrink: 0,
20625
21026
  selectable: false,
20626
21027
  onMouseDown: () => onStop(this.taskId)
20627
21028
  });
20628
- this.detail = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
21029
+ this.detail = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
20629
21030
  line.add(this.title);
20630
21031
  line.add(this.stop);
20631
21032
  this.renderable.add(line);
@@ -20637,16 +21038,17 @@ class TaskRowView {
20637
21038
  return;
20638
21039
  this.taskId = row.taskId;
20639
21040
  const frame = SPINNER_FRAMES[Math.floor(now / SPINNER_STEP_MS2) % SPINNER_FRAMES.length];
20640
- this.title.content = new StyledText14([
20641
- fg16(Theme.status.success)(`${frame} `),
20642
- bold9(fg16(Theme.fg.primary)(row.kind)),
20643
- fg16(Theme.fg.secondary)(` ${row.title}`)
21041
+ this.title.content = new StyledText15([
21042
+ fg17(Theme.status.success)(`${frame} `),
21043
+ bold10(fg17(Theme.fg.primary)(row.kind)),
21044
+ fg17(Theme.fg.muted)(row.model ? ` (${row.model})` : ""),
21045
+ fg17(Theme.fg.secondary)(` ${row.title}`)
20644
21046
  ]);
20645
- this.stop.content = new StyledText14([
20646
- fg16(Theme.fg.muted)(`${row.stats} `),
20647
- bold9(fg16(Theme.fg.secondary)("[stop]"))
21047
+ this.stop.content = new StyledText15([
21048
+ fg17(Theme.fg.muted)(`${row.stats} `),
21049
+ bold10(fg17(Theme.fg.secondary)("[stop]"))
20648
21050
  ]);
20649
- this.detail.content = new StyledText14([dim7(fg16(Theme.fg.muted)(` \u2514 ${row.detail}`))]);
21051
+ this.detail.content = new StyledText15([dim8(fg17(Theme.fg.muted)(` \u2514 ${row.detail}`))]);
20650
21052
  }
20651
21053
  }
20652
21054
 
@@ -20657,14 +21059,16 @@ class TaskPanel {
20657
21059
  rows;
20658
21060
  overflow;
20659
21061
  constructor(renderer, onStop) {
20660
- this.renderable = new BoxRenderable16(renderer, {
21062
+ this.renderable = new BoxRenderable17(renderer, {
20661
21063
  flexShrink: 0,
20662
21064
  paddingLeft: 2,
20663
21065
  paddingRight: 2,
21066
+ border: ["top"],
21067
+ borderColor: Theme.border.subtle,
20664
21068
  visible: false
20665
21069
  });
20666
- this.header = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
20667
- this.overflow = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
21070
+ this.header = new TextRenderable17(renderer, { content: "", wrapMode: "none" });
21071
+ this.overflow = new TextRenderable17(renderer, { content: "", wrapMode: "none" });
20668
21072
  this.rows = Array.from({ length: MAX_TASK_ROWS }, () => new TaskRowView(renderer, onStop));
20669
21073
  this.renderable.add(this.header);
20670
21074
  for (const row of this.rows)
@@ -20676,15 +21080,15 @@ class TaskPanel {
20676
21080
  if (tasks.length === 0)
20677
21081
  return;
20678
21082
  const rows = buildTaskRows(tasks, now);
20679
- this.header.content = new StyledText15([bold10(fg17(Theme.fg.secondary)(buildTaskHeader(tasks)))]);
21083
+ this.header.content = new StyledText16([bold11(fg18(Theme.fg.primary)(buildTaskHeader(tasks)))]);
20680
21084
  this.rows.forEach((view, index) => view.update(rows[index], now));
20681
21085
  const hidden = countHiddenTasks(tasks);
20682
- this.overflow.content = hidden ? new StyledText15([dim8(fg17(Theme.fg.muted)(` +${hidden} more`))]) : "";
21086
+ this.overflow.content = hidden ? new StyledText16([dim9(fg18(Theme.fg.muted)(` +${hidden} more`))]) : "";
20683
21087
  }
20684
21088
  }
20685
21089
 
20686
21090
  // apps/tui/src/components/toastView.ts
20687
- import { BoxRenderable as BoxRenderable17, fg as fg18, StyledText as StyledText16, TextRenderable as TextRenderable17 } from "@opentui/core";
21091
+ import { BoxRenderable as BoxRenderable18, fg as fg19, StyledText as StyledText17, TextRenderable as TextRenderable18 } from "@opentui/core";
20688
21092
  var TOAST_ICON = "\u25CF";
20689
21093
 
20690
21094
  class ToastView {
@@ -20692,7 +21096,7 @@ class ToastView {
20692
21096
  text;
20693
21097
  toastId;
20694
21098
  constructor(renderer) {
20695
- this.renderable = new BoxRenderable17(renderer, {
21099
+ this.renderable = new BoxRenderable18(renderer, {
20696
21100
  position: "absolute",
20697
21101
  top: 1,
20698
21102
  right: 2,
@@ -20703,7 +21107,7 @@ class ToastView {
20703
21107
  borderStyle: "rounded",
20704
21108
  visible: false
20705
21109
  });
20706
- this.text = new TextRenderable17(renderer, { content: "", wrapMode: "none", selectable: false });
21110
+ this.text = new TextRenderable18(renderer, { content: "", wrapMode: "none", selectable: false });
20707
21111
  this.renderable.add(this.text);
20708
21112
  }
20709
21113
  update({ ui }) {
@@ -20714,9 +21118,9 @@ class ToastView {
20714
21118
  this.toastId = toast.id;
20715
21119
  this.renderable.borderColor = Theme.border.focus;
20716
21120
  this.renderable.backgroundColor = Theme.bg.surface;
20717
- this.text.content = new StyledText16([
20718
- fg18(Theme.status.success)(`${TOAST_ICON} `),
20719
- fg18(Theme.fg.primary)(toast.text)
21121
+ this.text.content = new StyledText17([
21122
+ fg19(Theme.status.success)(`${TOAST_ICON} `),
21123
+ fg19(Theme.fg.primary)(toast.text)
20720
21124
  ]);
20721
21125
  }
20722
21126
  }
@@ -20745,6 +21149,9 @@ function requestAgent(store, request, onMissing = "notify") {
20745
21149
  return true;
20746
21150
  }
20747
21151
 
21152
+ // apps/tui/src/input/themeActions.ts
21153
+ var saveTheme2 = (store, theme) => store.request({ type: "options.theme.save.requested", theme });
21154
+
20748
21155
  // apps/tui/src/input/workflowCommands.ts
20749
21156
  var postNotice = (store, text) => store.emit({ type: "ui.notice.posted", text });
20750
21157
  var WORKFLOW_COMMANDS = {
@@ -20844,7 +21251,7 @@ var SLASH_COMMANDS = {
20844
21251
  return store.emit({ type: "ui.theme.picker.opened" });
20845
21252
  const parsed = themeNameSchema.safeParse(argument);
20846
21253
  if (parsed.success)
20847
- return store.emit({ type: "ui.theme.selected", theme: parsed.data });
21254
+ return saveTheme2(store, parsed.data);
20848
21255
  store.emit({ type: "ui.notice.posted", text: `Unknown theme: ${argument}` });
20849
21256
  }
20850
21257
  },
@@ -20873,8 +21280,10 @@ var SLASH_COMMANDS = {
20873
21280
  }
20874
21281
  },
20875
21282
  "/effort": {
20876
- description: "Show or set reasoning effort",
21283
+ description: "Pick a reasoning effort, or set one with /effort <level>",
20877
21284
  run(store, level) {
21285
+ if (!level)
21286
+ return store.emit({ type: "ui.effort.picker.opened" });
20878
21287
  const parsed = effortLevelSchema.safeParse(level);
20879
21288
  if (parsed.success) {
20880
21289
  return void requestAgent(store, {
@@ -20946,6 +21355,14 @@ function buildPickerView(state) {
20946
21355
  }));
20947
21356
  return { title: "Select a theme", rows, selected: picker.selected };
20948
21357
  }
21358
+ if (picker.kind === "effort") {
21359
+ const current = state.effort || DEFAULT_EFFORT;
21360
+ const rows = effortLevelSchema.options.map((level) => ({
21361
+ name: `${level === current ? "\u25CF " : " "}${level}`,
21362
+ description: ""
21363
+ }));
21364
+ return { title: "Select an effort level", rows, selected: picker.selected };
21365
+ }
20949
21366
  if (picker.kind === "rewind") {
20950
21367
  const rows = listCheckpoints(state).map((entry) => ({
20951
21368
  name: truncateText(entry.text.replace(/\s+/g, " "), CHECKPOINT_TEXT_LENGTH),
@@ -20963,7 +21380,6 @@ function buildPickerView(state) {
20963
21380
  var buildListView = (state) => buildPickerView(state) ?? buildAutocompleteView(state);
20964
21381
 
20965
21382
  // apps/tui/src/input/agentSessionActions.ts
20966
- var DEFAULT_EFFORT = "high";
20967
21383
  var DEFAULT_MODEL = "claude-haiku-5-5";
20968
21384
  var buildSessionOptions = (store) => ({
20969
21385
  model: store.state.model || DEFAULT_MODEL,
@@ -21142,23 +21558,22 @@ function runSlashCommand(store, line) {
21142
21558
  }
21143
21559
 
21144
21560
  // apps/tui/src/input/promptActions.ts
21145
- var DELIVERY_REQUESTS = {
21146
- send: "agent.prompt.requested",
21147
- queue: "agent.prompt.queue.requested",
21148
- steer: "agent.prompt.steer.requested"
21149
- };
21150
- var BUSY_NOTICE = "Agent is busy: Ctrl+Q queues, Ctrl+W steers, Esc Esc interrupts the turn";
21151
21561
  var buildContent = (text, images) => images.length > 0 ? [{ type: "text", text }, ...images] : text;
21152
21562
  function deliverPrompt(store, content, delivery, images) {
21153
- const busy = isAgentBusy(store);
21154
- if (busy && delivery === "send") {
21155
- store.emit({ type: "ui.notice.posted", text: BUSY_NOTICE });
21156
- return false;
21563
+ const body = buildContent(content, images);
21564
+ if (delivery === "queue" && isAgentBusy(store)) {
21565
+ store.emit({ type: "ui.prompt.queued", content: body });
21566
+ return true;
21157
21567
  }
21158
- return requestAgent(store, {
21159
- type: DELIVERY_REQUESTS[busy ? delivery : "send"],
21160
- content: buildContent(content, images)
21161
- });
21568
+ return requestAgent(store, { type: "agent.prompt.requested", content: body });
21569
+ }
21570
+ function sendNextQueuedPrompt(store) {
21571
+ const [next] = store.state.queuedPrompts;
21572
+ if (next === undefined || store.state.status !== "idle")
21573
+ return;
21574
+ const sent = requestAgent(store, { type: "agent.prompt.requested", content: next }, "ignore");
21575
+ if (sent)
21576
+ store.emit({ type: "ui.prompt.dequeued" });
21162
21577
  }
21163
21578
  function submitPrompt(store, text, delivery = "send", images = []) {
21164
21579
  const content = text.trim();
@@ -21242,7 +21657,12 @@ var PICKER_CONFIRMS = {
21242
21657
  theme(store, _state, selected) {
21243
21658
  const theme = THEME_NAMES[selected];
21244
21659
  if (theme)
21245
- store.emit({ type: "ui.theme.selected", theme });
21660
+ saveTheme2(store, theme);
21661
+ },
21662
+ effort(store, _state, selected) {
21663
+ const effort = effortLevelSchema.options[selected];
21664
+ if (effort)
21665
+ requestAgent(store, { type: "agent.effort.set.requested", effort });
21246
21666
  },
21247
21667
  rewind(store, state, selected) {
21248
21668
  const userMessageId = listCheckpoints(state)[selected]?.checkpointId;
@@ -21290,7 +21710,7 @@ var dismissQuestion = (store) => sendAnswers(store, {});
21290
21710
 
21291
21711
  // apps/tui/src/input/routeKeys.ts
21292
21712
  var createKeyPresses = () => ({ escapeAt: 0, ctrlCAt: 0 });
21293
- var DELIVERY_KEYS = { q: "queue", w: "steer" };
21713
+ var DELIVERY_KEYS = { q: "queue" };
21294
21714
  var DOUBLE_ESCAPE_MS = 500;
21295
21715
  var EXIT_CONFIRM_MS = 3000;
21296
21716
  var EXIT_NOTICE = "Press Ctrl + C again to exit";
@@ -21635,21 +22055,70 @@ class AppStore {
21635
22055
 
21636
22056
  // apps/tui/src/theme/syntaxStyle.ts
21637
22057
  import { SyntaxStyle as SyntaxStyle2 } from "@opentui/core";
21638
- var createSyntaxStyle = (renderer) => SyntaxStyle2.fromStyles({
21639
- default: { fg: Theme.fg.primary },
21640
- "markup.heading": { fg: Theme.accent.brand, bold: true },
21641
- "markup.strong": { fg: Theme.fg.primary, bold: true },
21642
- "markup.italic": { fg: Theme.fg.secondary, italic: true },
21643
- "markup.raw": { fg: Theme.status.warning },
21644
- "markup.link": { fg: Theme.status.info, underline: true },
21645
- "markup.list": { fg: Theme.accent.highlight },
21646
- keyword: { fg: Theme.accent.brand },
21647
- string: { fg: Theme.status.success },
21648
- comment: { fg: Theme.fg.muted, italic: true },
21649
- function: { fg: Theme.accent.highlight },
21650
- type: { fg: Theme.status.warning },
21651
- number: { fg: Theme.status.info }
21652
- }, renderer.nativeScene);
22058
+
22059
+ // apps/tui/src/theme/syntaxRules.ts
22060
+ var rule = (scope, foreground, style = {}) => ({
22061
+ scope,
22062
+ style: { foreground, ...style }
22063
+ });
22064
+ var buildMutedSyntaxRules = (palette) => buildSyntaxRules(palette).map(({ scope, style }) => ({
22065
+ scope,
22066
+ style: { ...style, foreground: palette.fg.muted }
22067
+ }));
22068
+ var buildSyntaxRules = ({ fg, status, syntax, markdown, diff, bg }) => [
22069
+ rule(["default"], fg.primary),
22070
+ rule(["comment", "comment.documentation"], syntax.comment, { italic: true }),
22071
+ rule(["string", "symbol", "character.special", "character"], syntax.string),
22072
+ rule(["number", "boolean", "constant", "float"], syntax.number),
22073
+ rule(["keyword.return", "keyword.conditional", "keyword.repeat", "keyword.coroutine"], syntax.keyword, {
22074
+ italic: true
22075
+ }),
22076
+ rule(["keyword.type"], syntax.type, { bold: true, italic: true }),
22077
+ rule(["keyword.function", "function.method"], syntax.function),
22078
+ rule(["keyword"], syntax.keyword, { italic: true }),
22079
+ rule(["keyword.import", "string.escape", "string.regexp", "tag.attribute", "keyword.export"], syntax.keyword),
22080
+ rule(["operator", "keyword.operator", "punctuation.delimiter", "keyword.conditional.ternary"], syntax.operator),
22081
+ rule(["variable", "variable.parameter", "function.method.call", "function.call", "property", "parameter", "field"], syntax.variable),
22082
+ rule(["variable.member", "function", "constructor"], syntax.function),
22083
+ rule(["type", "module", "class", "namespace"], syntax.type),
22084
+ rule(["type.definition"], syntax.type, { bold: true }),
22085
+ rule(["punctuation", "punctuation.bracket"], syntax.punctuation),
22086
+ rule(["variable.builtin", "type.builtin", "function.builtin", "module.builtin", "constant.builtin", "variable.super"], status.danger),
22087
+ rule(["keyword.directive", "keyword.modifier", "keyword.exception"], syntax.keyword, { italic: true }),
22088
+ rule(["punctuation.special", "tag.delimiter"], syntax.operator),
22089
+ rule(["markup.heading", "markup.heading.2", "markup.heading.3", "markup.heading.4", "markup.heading.5", "markup.heading.6"], markdown.heading, { bold: true }),
22090
+ rule(["markup.heading.1"], markdown.heading, { bold: true, underline: true }),
22091
+ rule(["markup.bold", "markup.strong"], markdown.strong, { bold: true }),
22092
+ rule(["markup.italic"], markdown.emphasis, { italic: true }),
22093
+ rule(["markup.list"], markdown.listItem),
22094
+ rule(["markup.quote"], markdown.blockQuote, { italic: true }),
22095
+ rule(["markup.raw", "markup.raw.block"], markdown.code),
22096
+ rule(["markup.raw.inline"], markdown.code, { background: bg.surface }),
22097
+ rule(["markup.link", "markup.link.url", "string.special", "string.special.url"], markdown.link, {
22098
+ underline: true
22099
+ }),
22100
+ rule(["markup.link.label"], markdown.linkText, { underline: true }),
22101
+ rule(["label"], markdown.linkText),
22102
+ rule(["spell", "nospell"], fg.primary),
22103
+ rule(["markup.underline"], fg.primary, { underline: true }),
22104
+ rule(["comment.error"], status.danger, { italic: true, bold: true }),
22105
+ rule(["comment.warning"], status.warning, { italic: true, bold: true }),
22106
+ rule(["comment.todo", "comment.note"], status.info, { italic: true, bold: true }),
22107
+ rule(["attribute", "annotation"], status.warning),
22108
+ rule(["tag"], status.danger),
22109
+ rule(["markup.strikethrough", "markup.list.unchecked", "debug"], fg.muted),
22110
+ rule(["markup.list.checked"], status.success),
22111
+ rule(["diff.plus"], diff.addedText, { background: diff.addedBg }),
22112
+ rule(["diff.minus"], diff.removedText, { background: diff.removedBg }),
22113
+ rule(["diff.delta"], diff.contextText, { background: diff.contextBg }),
22114
+ rule(["error"], status.danger, { bold: true }),
22115
+ rule(["warning"], status.warning, { bold: true }),
22116
+ rule(["info"], status.info)
22117
+ ];
22118
+
22119
+ // apps/tui/src/theme/syntaxStyle.ts
22120
+ var createSyntaxStyle = (renderer) => SyntaxStyle2.fromTheme(buildSyntaxRules(Theme), renderer.nativeScene);
22121
+ var createMutedSyntaxStyle = (renderer) => SyntaxStyle2.fromTheme(buildMutedSyntaxRules(Theme), renderer.nativeScene);
21653
22122
 
21654
22123
  // apps/tui/src/startTui.ts
21655
22124
  var CLOCK_INTERVAL_MS = 100;
@@ -21662,6 +22131,7 @@ async function startTui(client, options = {}) {
21662
22131
  const store = new AppStore(client);
21663
22132
  const clipboard = options.clipboard ?? createHostClipboard();
21664
22133
  let syntaxStyle = createSyntaxStyle(activeRenderer);
22134
+ let mutedSyntaxStyle = createMutedSyntaxStyle(activeRenderer);
21665
22135
  let appliedTheme = readShownTheme(store.state);
21666
22136
  let appliedBackground = readShownBackground(store.state);
21667
22137
  const header = new HeaderBar(activeRenderer);
@@ -21671,8 +22141,10 @@ async function startTui(client, options = {}) {
21671
22141
  const toastView = new ToastView(activeRenderer);
21672
22142
  const statusLine = new StatusLine(activeRenderer, () => cancelTurn(store));
21673
22143
  const taskPanel = new TaskPanel(activeRenderer, (taskId) => stopTask(store, taskId));
22144
+ const promptQueue = new PromptQueueView(activeRenderer);
21674
22145
  const scrollback = new ScrollbackView(activeRenderer, {
21675
22146
  syntaxStyle,
22147
+ mutedSyntaxStyle,
21676
22148
  hooks: {
21677
22149
  syntaxStyle,
21678
22150
  onDecision: (decision, id) => answerApproval(store, decision, id),
@@ -21705,7 +22177,7 @@ async function startTui(client, options = {}) {
21705
22177
  saveDraft(text);
21706
22178
  }
21707
22179
  });
21708
- const root = new BoxRenderable18(activeRenderer, {
22180
+ const root = new BoxRenderable19(activeRenderer, {
21709
22181
  width: "100%",
21710
22182
  height: "100%",
21711
22183
  flexDirection: "column",
@@ -21716,11 +22188,12 @@ async function startTui(client, options = {}) {
21716
22188
  scrollback,
21717
22189
  diffViewer,
21718
22190
  statusLine,
21719
- taskPanel,
22191
+ promptQueue,
21720
22192
  popover,
21721
22193
  backgroundPanel,
21722
22194
  input,
21723
- infoBar
22195
+ infoBar,
22196
+ taskPanel
21724
22197
  ];
21725
22198
  for (const part of parts)
21726
22199
  root.add(part.renderable);
@@ -21747,11 +22220,13 @@ async function startTui(client, options = {}) {
21747
22220
  appliedBackground = shownBackground;
21748
22221
  applyTheme(shownTheme, shownBackground);
21749
22222
  if (themeChanged) {
21750
- const previous = syntaxStyle;
22223
+ const previous = [syntaxStyle, mutedSyntaxStyle];
21751
22224
  syntaxStyle = createSyntaxStyle(activeRenderer);
21752
- scrollback.reset(syntaxStyle);
22225
+ mutedSyntaxStyle = createMutedSyntaxStyle(activeRenderer);
22226
+ scrollback.reset(syntaxStyle, mutedSyntaxStyle);
21753
22227
  diffViewer.reset(syntaxStyle);
21754
- previous.destroy();
22228
+ for (const style of previous)
22229
+ style.destroy();
21755
22230
  }
21756
22231
  root.backgroundColor = Theme.bg.default;
21757
22232
  input.applyColors();
@@ -21762,6 +22237,7 @@ async function startTui(client, options = {}) {
21762
22237
  header.update(state);
21763
22238
  statusLine.update(state);
21764
22239
  taskPanel.update(state);
22240
+ promptQueue.update(state);
21765
22241
  toastView.update(state);
21766
22242
  infoBar.update(state);
21767
22243
  scrollback.update(state);
@@ -21773,7 +22249,7 @@ async function startTui(client, options = {}) {
21773
22249
  input.setFocused(inputFocused);
21774
22250
  scrollback.setFocused(!inputFocused);
21775
22251
  input.setBusy(isStatusBusy(state.status));
21776
- input.setBorderColor(inputFocused ? Theme.status.success : Theme.border.separator);
22252
+ input.setBorderColor(inputFocused ? Theme.fg.muted : Theme.border.separator);
21777
22253
  const awaitingId = (findAwaitingCalls(state)[0] ?? findQuestionCalls(state)[0])?.id;
21778
22254
  if (awaitingId && awaitingId !== revealedCallId) {
21779
22255
  scrollback.revealEntry(findGroupId(state, awaitingId) ?? "");
@@ -21781,6 +22257,7 @@ async function startTui(client, options = {}) {
21781
22257
  revealedCallId = awaitingId;
21782
22258
  };
21783
22259
  store.subscribe(render);
22260
+ store.subscribe(() => sendNextQueuedPrompt(store));
21784
22261
  const notifier = new TurnNotifier(activeRenderer);
21785
22262
  store.subscribeOwnEvents((event) => {
21786
22263
  reactToEvent(store, event);
@@ -23892,7 +24369,8 @@ function useColor() {
23892
24369
  var program = new Command;
23893
24370
 
23894
24371
  // apps/cli/src/index.ts
23895
- var DEFAULT_PORT2 = "7777";
24372
+ var DEFAULT_PORT = "7777";
24373
+ var runId = createRunId();
23896
24374
  var program2 = new Command().name("claudin").enablePositionalOptions();
23897
24375
  var printResumeHint = (sessionId) => {
23898
24376
  if (!sessionId)
@@ -23906,10 +24384,12 @@ var exitOnDestroy = (renderer) => {
23906
24384
  renderer.on("destroy", () => process.exit(0));
23907
24385
  };
23908
24386
  var openContext = (sessionId) => createDaemonContext(undefined, sessionId).catch((error) => program2.error(error.message));
23909
- var daemonUrl = (port) => `ws://localhost:${Number(port)}`;
23910
- program2.option("-p, --port <port>", "daemon port", DEFAULT_PORT2).option("-s, --session <id>", "resume a previous session").action(async (opts) => {
23911
- const server = await startDaemon(Number(opts.port), await openContext(opts.session));
23912
- const renderer = await startTui(await DaemonClient.connect(daemonUrl(opts.port)), {
24387
+ var daemonUrl = (port) => `ws://127.0.0.1:${Number(port)}`;
24388
+ program2.option("-p, --port <port>", "daemon port", DEFAULT_PORT).option("-s, --session <id>", "resume a previous session").action(async (opts) => {
24389
+ const token = createAccessToken();
24390
+ const server = await startDaemon(Number(opts.port), token, await openContext(opts.session));
24391
+ const client = await DaemonClient.connect(daemonUrl(opts.port), { token, runId });
24392
+ const renderer = await startTui(client, {
23913
24393
  resumeSessionId: opts.session,
23914
24394
  onExit: printResumeHint
23915
24395
  });
@@ -23919,14 +24399,16 @@ program2.option("-p, --port <port>", "daemon port", DEFAULT_PORT2).option("-s, -
23919
24399
  server.close(() => process.exit(0));
23920
24400
  });
23921
24401
  });
23922
- program2.command("daemon").description("Run the daemon without a UI so terminals can attach to it").option("-p, --port <port>", "daemon port", DEFAULT_PORT2).option("-s, --session <id>", "serve a previous session").action(async (opts) => {
23923
- await startDaemon(Number(opts.port), await openContext(opts.session));
23924
- process.stdout.write(`Daemon on ${daemonUrl(opts.port)}
23925
- Attach with: claudin attach -p ${opts.port}
24402
+ program2.command("daemon").description("Run the daemon without a UI so terminals can attach to it").option("-p, --port <port>", "daemon port", DEFAULT_PORT).option("-s, --session <id>", "serve a previous session").action(async (opts) => {
24403
+ const token = createAccessToken();
24404
+ await startDaemon(Number(opts.port), token, await openContext(opts.session));
24405
+ process.stdout.write(`Daemon on ${daemonUrl(opts.port)} (run ${runId})
24406
+ Attach with: claudin attach -p ${opts.port} --token ${token}
23926
24407
  `);
23927
24408
  });
23928
- program2.command("attach").description("Attach another terminal to a running daemon's live session").option("-p, --port <port>", "daemon port", DEFAULT_PORT2).action(async (opts) => {
23929
- const client = await DaemonClient.connect(daemonUrl(opts.port)).catch((error) => program2.error(error.message));
24409
+ program2.command("attach").description("Attach another terminal to a running daemon's live session").option("-p, --port <port>", "daemon port", DEFAULT_PORT).option("-t, --token <token>", "access token printed by the daemon (or CLAUDIN_TOKEN)").action(async (opts) => {
24410
+ const token = opts.token ?? process.env.CLAUDIN_TOKEN ?? program2.error("An access token is required: pass --token or set CLAUDIN_TOKEN");
24411
+ const client = await DaemonClient.connect(daemonUrl(opts.port), { token, runId }).catch((error) => program2.error(error.message));
23930
24412
  const renderer = await startTui(client, { attach: true, onExit: printResumeHint });
23931
24413
  client.onDisconnect(() => renderer.destroy());
23932
24414
  exitOnDestroy(renderer);