@couralabs/claudin 0.1.4 → 0.1.5

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 +218 -16
  2. package/package.json +1 -1
package/claudin.js CHANGED
@@ -6148,6 +6148,7 @@ var agentRequestSchemas = [
6148
6148
  object({ type: literal("agent.prompt.queue.requested"), agentSessionId, content }),
6149
6149
  object({ type: literal("agent.prompt.steer.requested"), agentSessionId, content }),
6150
6150
  object({ type: literal("agent.prompt.cancel.requested"), agentSessionId }),
6151
+ object({ type: literal("agent.task.stop.requested"), agentSessionId, taskId: string2() }),
6151
6152
  object({
6152
6153
  type: literal("agent.tool.approval.answered"),
6153
6154
  agentSessionId,
@@ -6217,6 +6218,7 @@ var createSessionState = (sessionId) => ({
6217
6218
  models: [],
6218
6219
  sessionList: [],
6219
6220
  queuedTurns: 0,
6221
+ tasks: [],
6220
6222
  transcript: [],
6221
6223
  entryCounter: 0,
6222
6224
  now: Date.now(),
@@ -6525,6 +6527,56 @@ function reduceTaskProgress(state, message) {
6525
6527
  return updateToolCall(state, toolUseId, (call) => ({ ...call, progress: summary }));
6526
6528
  }
6527
6529
 
6530
+ // packages/events/src/reducers/reduceTasks.ts
6531
+ var FINISHED_STATUSES = ["completed", "failed", "killed"];
6532
+ var isTaskMessage = (message) => message.type === "system" && ["task_started", "task_progress", "task_updated", "task_notification"].includes(message.subtype);
6533
+ var patchTask = (tasks, id, patch) => tasks.map((task) => task.id === id ? patch(task) : task);
6534
+ var startTask = (state, message) => {
6535
+ if (message.ambient || message.skip_transcript)
6536
+ return state.tasks;
6537
+ const task = {
6538
+ id: message.task_id,
6539
+ kind: message.subagent_type ?? message.task_type ?? "task",
6540
+ description: message.description,
6541
+ startedAt: state.now,
6542
+ tokens: 0,
6543
+ paused: false
6544
+ };
6545
+ return [...state.tasks.filter(({ id }) => id !== task.id), task];
6546
+ };
6547
+ var updateTask = (tasks, message) => {
6548
+ const { status, description } = message.patch;
6549
+ if (status && FINISHED_STATUSES.includes(status)) {
6550
+ return tasks.filter(({ id }) => id !== message.task_id);
6551
+ }
6552
+ return patchTask(tasks, message.task_id, (task) => ({
6553
+ ...task,
6554
+ description: description ?? task.description,
6555
+ paused: status ? status === "paused" : task.paused
6556
+ }));
6557
+ };
6558
+ var readTasks = (state, message) => {
6559
+ switch (message.subtype) {
6560
+ case "task_started":
6561
+ return startTask(state, message);
6562
+ case "task_progress":
6563
+ return patchTask(state.tasks, message.task_id, (task) => ({
6564
+ ...task,
6565
+ lastToolName: message.last_tool_name ?? task.lastToolName,
6566
+ summary: message.summary ?? task.summary,
6567
+ tokens: message.usage.total_tokens
6568
+ }));
6569
+ case "task_updated":
6570
+ return updateTask(state.tasks, message);
6571
+ case "task_notification":
6572
+ return state.tasks.filter(({ id }) => id !== message.task_id);
6573
+ }
6574
+ };
6575
+ var reduceTasks = (state, message) => ({
6576
+ ...state,
6577
+ tasks: readTasks(state, message)
6578
+ });
6579
+
6528
6580
  // packages/events/src/reducers/reduceToolResults.ts
6529
6581
  function applyResult(state, block) {
6530
6582
  if (block.type !== "tool_result" || !block.tool_use_id)
@@ -6542,10 +6594,10 @@ function reduceToolResults(state, message) {
6542
6594
 
6543
6595
  // packages/events/src/reducers/reduceAgentMessage.ts
6544
6596
  function reduceAgentMessage(state, message) {
6597
+ if (isTaskMessage(message))
6598
+ return reduceTaskMessage(reduceTasks(state, message), message);
6545
6599
  switch (message.type) {
6546
6600
  case "system":
6547
- if (message.subtype === "task_progress")
6548
- return reduceTaskProgress(state, message);
6549
6601
  if (message.subtype !== "init")
6550
6602
  return reduceNoticeMessage(state, message);
6551
6603
  return {
@@ -6571,6 +6623,15 @@ function reduceAgentMessage(state, message) {
6571
6623
  return reduceNoticeMessage(state, message);
6572
6624
  }
6573
6625
  }
6626
+ function reduceTaskMessage(state, message) {
6627
+ if (message.type === "system" && message.subtype === "task_progress") {
6628
+ return reduceTaskProgress(state, message);
6629
+ }
6630
+ if (message.type === "system" && message.subtype === "task_notification") {
6631
+ return reduceNoticeMessage(state, message);
6632
+ }
6633
+ return state;
6634
+ }
6574
6635
  function reduceNoticeMessage(state, message) {
6575
6636
  const text = describeSystemMessage(message);
6576
6637
  return text ? appendNotice(state, text) : state;
@@ -6855,6 +6916,7 @@ function reduceUi(state, event) {
6855
6916
  case "ui.question.recorded":
6856
6917
  return recordQuestionAnswer(state, event);
6857
6918
  case "ui.exit.requested":
6919
+ case "ui.title.set":
6858
6920
  return state;
6859
6921
  case "ui.notice.posted":
6860
6922
  return appendNotice(state, event.text);
@@ -8889,6 +8951,13 @@ var AGENT_PROFILE_OPTIONS = {
8889
8951
  includeHookEvents: true
8890
8952
  };
8891
8953
 
8954
+ // packages/agent/src/session/readBackgroundTaskCount.ts
8955
+ function readBackgroundTaskCount(message, current) {
8956
+ if (message.type !== "system" || message.subtype !== "background_tasks_changed")
8957
+ return current;
8958
+ return message.tasks.length;
8959
+ }
8960
+
8892
8961
  // packages/agent/src/session/agentSession.ts
8893
8962
  function requireRewind(result) {
8894
8963
  if (!result.canRewind)
@@ -8903,6 +8972,7 @@ class AgentSession {
8903
8972
  elicitations;
8904
8973
  query;
8905
8974
  pendingTurns = 0;
8975
+ backgroundTaskCount = 0;
8906
8976
  pumping = false;
8907
8977
  constructor(agentSessionId, baseOptions, runtime) {
8908
8978
  this.baseOptions = baseOptions;
@@ -8951,6 +9021,9 @@ class AgentSession {
8951
9021
  this.rejectAllPrompts();
8952
9022
  await this.query.interrupt();
8953
9023
  }
9024
+ stopTask(taskId) {
9025
+ return this.query.stopTask(taskId);
9026
+ }
8954
9027
  async setModel(model) {
8955
9028
  await this.query.setModel(model);
8956
9029
  this.baseOptions = { ...this.baseOptions, model };
@@ -9021,10 +9094,11 @@ class AgentSession {
9021
9094
  if (done)
9022
9095
  break;
9023
9096
  sink.push({ type: "agent.message.received", message: value });
9097
+ this.backgroundTaskCount = readBackgroundTaskCount(value, this.backgroundTaskCount);
9024
9098
  if (value.type !== "result")
9025
9099
  continue;
9026
9100
  this.pendingTurns = Math.max(this.pendingTurns - 1, 0);
9027
- if (this.pendingTurns === 0)
9101
+ if (this.pendingTurns === 0 && this.backgroundTaskCount === 0)
9028
9102
  break;
9029
9103
  }
9030
9104
  sink.end();
@@ -9302,6 +9376,9 @@ var agentHandlers = {
9302
9376
  await agents.requireSession(request.agentSessionId).cancel();
9303
9377
  yield { type: "agent.prompt.cancelled", agentSessionId: request.agentSessionId };
9304
9378
  },
9379
+ async* "agent.task.stop.requested"({ agents }, request) {
9380
+ await agents.requireSession(request.agentSessionId).stopTask(request.taskId);
9381
+ },
9305
9382
  async* "agent.tool.approval.answered"({ agents }, request) {
9306
9383
  agents.requireSession(request.agentSessionId).answerApproval(request.toolUseId, request.decision);
9307
9384
  },
@@ -11134,7 +11211,7 @@ async function startDaemon(port = DEFAULT_PORT, context) {
11134
11211
  }
11135
11212
  // apps/tui/src/startTui.ts
11136
11213
  import {
11137
- BoxRenderable as BoxRenderable16,
11214
+ BoxRenderable as BoxRenderable18,
11138
11215
  createCliRenderer,
11139
11216
  createHostClipboard
11140
11217
  } from "@opentui/core";
@@ -20114,7 +20191,7 @@ class ToolCallView {
20114
20191
  const glyph = call.expanded ? COLLAPSE_GLYPHS.expanded : COLLAPSE_GLYPHS.collapsed;
20115
20192
  const accent = this.hovered ? Theme.accent.brand : undefined;
20116
20193
  this.title.content = new StyledText10([
20117
- fg11(accent ?? toolColor(call.status))(`${glyph} `),
20194
+ fg11(accent ?? toolColor(call.status))(glyph),
20118
20195
  bold6(fg11(accent ?? Theme.fg.primary)(call.name)),
20119
20196
  fg11(Theme.fg.secondary)(` ${summarizeToolCall(call)}`),
20120
20197
  fg11(Theme.fg.muted)(stats)
@@ -20222,7 +20299,6 @@ class UserMessageView {
20222
20299
  return;
20223
20300
  this.entry = entry;
20224
20301
  this.text.content = new StyledText11([
20225
- bold7(fg12(Theme.accent.brand)("\u276F ")),
20226
20302
  bold7(fg12(Theme.fg.primary)(clipLines(entry.text, USER_LINES)))
20227
20303
  ]);
20228
20304
  this.clock.content = new StyledText11([fg12(Theme.fg.muted)(formatClock(entry.at))]);
@@ -20490,8 +20566,125 @@ class StatusLine {
20490
20566
  }
20491
20567
  }
20492
20568
 
20569
+ // apps/tui/src/components/taskPanel.ts
20570
+ 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
20577
+ } from "@opentui/core";
20578
+
20579
+ // apps/tui/src/format/taskRows.ts
20580
+ var MAX_TASK_ROWS = 5;
20581
+ var readDetail = ({ summary, lastToolName, paused }) => {
20582
+ if (paused)
20583
+ return "Paused";
20584
+ return summary ?? lastToolName ?? "Starting...";
20585
+ };
20586
+ var buildTaskRow = (task, now) => ({
20587
+ taskId: task.id,
20588
+ kind: task.kind,
20589
+ title: task.description,
20590
+ stats: [formatDuration(Math.max(now - task.startedAt, 0)), `\u2193${formatTokenCount(task.tokens)}`].join(SEPARATOR).trim(),
20591
+ detail: readDetail(task)
20592
+ });
20593
+ var buildTaskRows = (tasks, now) => tasks.slice(0, MAX_TASK_ROWS).map((task) => buildTaskRow(task, now));
20594
+ var buildTaskHeader = (tasks) => `Tasks${SEPARATOR}${tasks.length} running`;
20595
+ var countHiddenTasks = (tasks) => Math.max(tasks.length - MAX_TASK_ROWS, 0);
20596
+
20597
+ // apps/tui/src/components/taskRowView.ts
20598
+ 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
20605
+ } from "@opentui/core";
20606
+ var SPINNER_STEP_MS2 = 100;
20607
+
20608
+ class TaskRowView {
20609
+ renderable;
20610
+ title;
20611
+ stop;
20612
+ detail;
20613
+ taskId = "";
20614
+ constructor(renderer, onStop) {
20615
+ this.renderable = new BoxRenderable15(renderer, { flexShrink: 0, visible: false });
20616
+ const line = new BoxRenderable15(renderer, {
20617
+ flexDirection: "row",
20618
+ justifyContent: "space-between"
20619
+ });
20620
+ this.title = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20621
+ this.stop = new TextRenderable15(renderer, {
20622
+ content: "",
20623
+ wrapMode: "none",
20624
+ flexShrink: 0,
20625
+ selectable: false,
20626
+ onMouseDown: () => onStop(this.taskId)
20627
+ });
20628
+ this.detail = new TextRenderable15(renderer, { content: "", wrapMode: "none" });
20629
+ line.add(this.title);
20630
+ line.add(this.stop);
20631
+ this.renderable.add(line);
20632
+ this.renderable.add(this.detail);
20633
+ }
20634
+ update(row, now) {
20635
+ this.renderable.visible = row !== undefined;
20636
+ if (!row)
20637
+ return;
20638
+ this.taskId = row.taskId;
20639
+ 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}`)
20644
+ ]);
20645
+ this.stop.content = new StyledText14([
20646
+ fg16(Theme.fg.muted)(`${row.stats} `),
20647
+ bold9(fg16(Theme.fg.secondary)("[stop]"))
20648
+ ]);
20649
+ this.detail.content = new StyledText14([dim7(fg16(Theme.fg.muted)(` \u2514 ${row.detail}`))]);
20650
+ }
20651
+ }
20652
+
20653
+ // apps/tui/src/components/taskPanel.ts
20654
+ class TaskPanel {
20655
+ renderable;
20656
+ header;
20657
+ rows;
20658
+ overflow;
20659
+ constructor(renderer, onStop) {
20660
+ this.renderable = new BoxRenderable16(renderer, {
20661
+ flexShrink: 0,
20662
+ paddingLeft: 2,
20663
+ paddingRight: 2,
20664
+ visible: false
20665
+ });
20666
+ this.header = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
20667
+ this.overflow = new TextRenderable16(renderer, { content: "", wrapMode: "none" });
20668
+ this.rows = Array.from({ length: MAX_TASK_ROWS }, () => new TaskRowView(renderer, onStop));
20669
+ this.renderable.add(this.header);
20670
+ for (const row of this.rows)
20671
+ this.renderable.add(row.renderable);
20672
+ this.renderable.add(this.overflow);
20673
+ }
20674
+ update({ tasks, now }) {
20675
+ this.renderable.visible = tasks.length > 0;
20676
+ if (tasks.length === 0)
20677
+ return;
20678
+ const rows = buildTaskRows(tasks, now);
20679
+ this.header.content = new StyledText15([bold10(fg17(Theme.fg.secondary)(buildTaskHeader(tasks)))]);
20680
+ this.rows.forEach((view, index) => view.update(rows[index], now));
20681
+ const hidden = countHiddenTasks(tasks);
20682
+ this.overflow.content = hidden ? new StyledText15([dim8(fg17(Theme.fg.muted)(` +${hidden} more`))]) : "";
20683
+ }
20684
+ }
20685
+
20493
20686
  // apps/tui/src/components/toastView.ts
20494
- import { BoxRenderable as BoxRenderable15, fg as fg16, StyledText as StyledText14, TextRenderable as TextRenderable15 } from "@opentui/core";
20687
+ import { BoxRenderable as BoxRenderable17, fg as fg18, StyledText as StyledText16, TextRenderable as TextRenderable17 } from "@opentui/core";
20495
20688
  var TOAST_ICON = "\u25CF";
20496
20689
 
20497
20690
  class ToastView {
@@ -20499,7 +20692,7 @@ class ToastView {
20499
20692
  text;
20500
20693
  toastId;
20501
20694
  constructor(renderer) {
20502
- this.renderable = new BoxRenderable15(renderer, {
20695
+ this.renderable = new BoxRenderable17(renderer, {
20503
20696
  position: "absolute",
20504
20697
  top: 1,
20505
20698
  right: 2,
@@ -20510,7 +20703,7 @@ class ToastView {
20510
20703
  borderStyle: "rounded",
20511
20704
  visible: false
20512
20705
  });
20513
- this.text = new TextRenderable15(renderer, { content: "", wrapMode: "none", selectable: false });
20706
+ this.text = new TextRenderable17(renderer, { content: "", wrapMode: "none", selectable: false });
20514
20707
  this.renderable.add(this.text);
20515
20708
  }
20516
20709
  update({ ui }) {
@@ -20521,9 +20714,9 @@ class ToastView {
20521
20714
  this.toastId = toast.id;
20522
20715
  this.renderable.borderColor = Theme.border.focus;
20523
20716
  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)
20717
+ this.text.content = new StyledText16([
20718
+ fg18(Theme.status.success)(`${TOAST_ICON} `),
20719
+ fg18(Theme.fg.primary)(toast.text)
20527
20720
  ]);
20528
20721
  }
20529
20722
  }
@@ -20982,6 +21175,9 @@ function answerApproval(store, decision, toolUseId) {
20982
21175
  if (target)
20983
21176
  requestAgent(store, { type: "agent.tool.approval.answered", toolUseId: target, decision });
20984
21177
  }
21178
+ function stopTask(store, taskId) {
21179
+ requestAgent(store, { type: "agent.task.stop.requested", taskId });
21180
+ }
20985
21181
  function cancelTurn(store) {
20986
21182
  requestAgent(store, { type: "agent.prompt.cancel.requested" });
20987
21183
  }
@@ -21463,7 +21659,6 @@ var readShownTheme = ({ theme, ui }) => (ui.picker?.kind === "theme" ? THEME_NAM
21463
21659
  var readShownBackground = ({ background, ui }) => ui.backgroundEditor?.color ?? background;
21464
21660
  async function startTui(client, options = {}) {
21465
21661
  const activeRenderer = options.renderer ?? await createCliRenderer({ exitOnCtrlC: false, useMouse: true });
21466
- activeRenderer.setTerminalTitle("Claudin");
21467
21662
  const store = new AppStore(client);
21468
21663
  const clipboard = options.clipboard ?? createHostClipboard();
21469
21664
  let syntaxStyle = createSyntaxStyle(activeRenderer);
@@ -21475,6 +21670,7 @@ async function startTui(client, options = {}) {
21475
21670
  const backgroundPanel = new BackgroundPanel(activeRenderer);
21476
21671
  const toastView = new ToastView(activeRenderer);
21477
21672
  const statusLine = new StatusLine(activeRenderer, () => cancelTurn(store));
21673
+ const taskPanel = new TaskPanel(activeRenderer, (taskId) => stopTask(store, taskId));
21478
21674
  const scrollback = new ScrollbackView(activeRenderer, {
21479
21675
  syntaxStyle,
21480
21676
  hooks: {
@@ -21509,7 +21705,7 @@ async function startTui(client, options = {}) {
21509
21705
  saveDraft(text);
21510
21706
  }
21511
21707
  });
21512
- const root = new BoxRenderable16(activeRenderer, {
21708
+ const root = new BoxRenderable18(activeRenderer, {
21513
21709
  width: "100%",
21514
21710
  height: "100%",
21515
21711
  flexDirection: "column",
@@ -21520,6 +21716,7 @@ async function startTui(client, options = {}) {
21520
21716
  scrollback,
21521
21717
  diffViewer,
21522
21718
  statusLine,
21719
+ taskPanel,
21523
21720
  popover,
21524
21721
  backgroundPanel,
21525
21722
  input,
@@ -21532,7 +21729,7 @@ async function startTui(client, options = {}) {
21532
21729
  activeRenderer.on("selection", (selection) => {
21533
21730
  copySelection(store, activeRenderer, clipboard, selection);
21534
21731
  });
21535
- for (const part of [header, statusLine, popover, backgroundPanel, input, infoBar]) {
21732
+ for (const part of [header, statusLine, taskPanel, popover, backgroundPanel, input, infoBar]) {
21536
21733
  part.renderable.onMouseScroll = ({ scroll }) => {
21537
21734
  if (scroll?.direction === "up")
21538
21735
  scrollback.scrollByLines(-1);
@@ -21564,6 +21761,7 @@ async function startTui(client, options = {}) {
21564
21761
  syncTheme(state);
21565
21762
  header.update(state);
21566
21763
  statusLine.update(state);
21764
+ taskPanel.update(state);
21567
21765
  toastView.update(state);
21568
21766
  infoBar.update(state);
21569
21767
  scrollback.update(state);
@@ -21589,7 +21787,10 @@ async function startTui(client, options = {}) {
21589
21787
  notifier.notify(event);
21590
21788
  if (event.type === "ui.exit.requested")
21591
21789
  activeRenderer.destroy();
21790
+ if (event.type === "ui.title.set")
21791
+ activeRenderer.setTerminalTitle(event.title);
21592
21792
  });
21793
+ store.emit({ type: "ui.title.set", title: "Claudin" });
21593
21794
  const editor = {
21594
21795
  read: () => input.text,
21595
21796
  replace: (text) => input.replaceText(text, text.length)
@@ -21611,7 +21812,8 @@ async function startTui(client, options = {}) {
21611
21812
  key.preventDefault();
21612
21813
  });
21613
21814
  const clock = setInterval(() => {
21614
- if (store.state.status !== "idle")
21815
+ const { status, tasks } = store.state;
21816
+ if (status !== "idle" || tasks.length > 0)
21615
21817
  store.emit({ type: "ui.clock.ticked", now: Date.now() });
21616
21818
  }, CLOCK_INTERVAL_MS);
21617
21819
  let lastRateLimitAt = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@couralabs/claudin",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claudin": "claudin.js"