claude-code-controller 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -102,6 +102,48 @@ interface PlainTextMessage {
102
102
  type: "plain_text";
103
103
  text: string;
104
104
  }
105
+ /** JSON data sent by Claude Code to the statusLine command via stdin. */
106
+ interface StatusLineData {
107
+ model?: {
108
+ id?: string;
109
+ display_name?: string;
110
+ };
111
+ session_id?: string;
112
+ context_window?: {
113
+ used_percentage?: number;
114
+ remaining_percentage?: number;
115
+ total_input_tokens?: number;
116
+ total_output_tokens?: number;
117
+ context_window_size?: number;
118
+ current_usage?: {
119
+ input_tokens?: number;
120
+ output_tokens?: number;
121
+ cache_creation_input_tokens?: number;
122
+ cache_read_input_tokens?: number;
123
+ };
124
+ };
125
+ cost?: {
126
+ total_cost_usd?: number;
127
+ total_duration_ms?: number;
128
+ total_api_duration_ms?: number;
129
+ total_lines_added?: number;
130
+ total_lines_removed?: number;
131
+ };
132
+ workspace?: {
133
+ current_dir?: string;
134
+ project_dir?: string;
135
+ };
136
+ cwd?: string;
137
+ agent?: {
138
+ name?: string;
139
+ };
140
+ vim?: {
141
+ mode?: string;
142
+ };
143
+ output_style?: {
144
+ name?: string;
145
+ };
146
+ }
105
147
  interface TeamConfig {
106
148
  name: string;
107
149
  description?: string;
@@ -172,6 +214,7 @@ interface ControllerEvents {
172
214
  "task:completed": [task: TaskFile];
173
215
  "agent:spawned": [agentName: string, pid: number];
174
216
  "agent:exited": [agentName: string, code: number | null];
217
+ "agent:statusline": [agentName: string, data: StatusLineData];
175
218
  error: [error: Error];
176
219
  }
177
220
  type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
@@ -326,6 +369,7 @@ declare class ClaudeCodeController extends EventEmitter<ControllerEvents> implem
326
369
  readonly tasks: TaskManager;
327
370
  private processes;
328
371
  private poller;
372
+ private statusLineWatcher;
329
373
  private log;
330
374
  private cwd;
331
375
  private claudeBinary;
@@ -424,6 +468,12 @@ declare class ClaudeCodeController extends EventEmitter<ControllerEvents> implem
424
468
  version: string | null;
425
469
  };
426
470
  private handlePollEvents;
471
+ /**
472
+ * Ensure the agent's cwd has a .claude/settings.local.json so the
473
+ * CLI skips the interactive workspace trust prompt.
474
+ * Also injects statusLine capture configuration.
475
+ */
476
+ private ensureWorkspaceTrusted;
427
477
  private ensureInitialized;
428
478
  }
429
479
 
@@ -557,6 +607,7 @@ interface PlanRequestInfo {
557
607
  interface AgentEvents {
558
608
  message: [text: string];
559
609
  idle: [];
610
+ statusline: [data: StatusLineData];
560
611
  permission: [request: PermissionRequestInfo];
561
612
  plan: [request: PlanRequestInfo];
562
613
  exit: [code: number | null];
@@ -663,4 +714,4 @@ declare const claude: typeof claudeCall & {
663
714
  session: (opts?: SessionOptions) => Promise<Session>;
664
715
  };
665
716
 
666
- export { Agent as A, type TaskFile as B, ClaudeCodeController as C, TaskManager as D, type TaskStatus as E, type TeamConfig as F, TeamManager as G, type TeamMember as H, type InboxMessage as I, claude as J, type Logger as L, type PermissionMode as P, type ReceiveOptions as R, type StructuredMessage as S, type TaskAssignmentMessage as T, type LogLevel as a, type AgentEvents as b, AgentHandle as c, type AgentType as d, type AskOptions as e, type ClaudeOptions as f, type ControllerEvents as g, type ControllerOptions as h, type IdleNotificationMessage as i, type PermissionPreset as j, type PermissionRequestInfo as k, type PermissionRequestMessage as l, type PermissionResponseMessage as m, type PlainTextMessage as n, type PlanApprovalRequestMessage as o, type PlanApprovalResponseMessage as p, type PlanRequestInfo as q, type SandboxPermissionRequestMessage as r, type SandboxPermissionResponseMessage as s, Session as t, type SessionAgentOptions as u, type SessionOptions as v, type ShutdownApprovedMessage as w, type ShutdownRequestMessage as x, type SpawnAgentOptions as y, type TaskCompletedMessage as z };
717
+ export { Agent as A, type TaskCompletedMessage as B, ClaudeCodeController as C, type TaskFile as D, TaskManager as E, type TaskStatus as F, type TeamConfig as G, TeamManager as H, type InboxMessage as I, type TeamMember as J, claude as K, type Logger as L, type PermissionMode as P, type ReceiveOptions as R, type StructuredMessage as S, type TaskAssignmentMessage as T, type StatusLineData as a, type LogLevel as b, type AgentEvents as c, AgentHandle as d, type AgentType as e, type AskOptions as f, type ClaudeOptions as g, type ControllerEvents as h, type ControllerOptions as i, type IdleNotificationMessage as j, type PermissionPreset as k, type PermissionRequestInfo as l, type PermissionRequestMessage as m, type PermissionResponseMessage as n, type PlainTextMessage as o, type PlanApprovalRequestMessage as p, type PlanApprovalResponseMessage as q, type PlanRequestInfo as r, type SandboxPermissionRequestMessage as s, type SandboxPermissionResponseMessage as t, Session as u, type SessionAgentOptions as v, type SessionOptions as w, type ShutdownApprovedMessage as x, type ShutdownRequestMessage as y, type SpawnAgentOptions as z };
@@ -102,6 +102,48 @@ interface PlainTextMessage {
102
102
  type: "plain_text";
103
103
  text: string;
104
104
  }
105
+ /** JSON data sent by Claude Code to the statusLine command via stdin. */
106
+ interface StatusLineData {
107
+ model?: {
108
+ id?: string;
109
+ display_name?: string;
110
+ };
111
+ session_id?: string;
112
+ context_window?: {
113
+ used_percentage?: number;
114
+ remaining_percentage?: number;
115
+ total_input_tokens?: number;
116
+ total_output_tokens?: number;
117
+ context_window_size?: number;
118
+ current_usage?: {
119
+ input_tokens?: number;
120
+ output_tokens?: number;
121
+ cache_creation_input_tokens?: number;
122
+ cache_read_input_tokens?: number;
123
+ };
124
+ };
125
+ cost?: {
126
+ total_cost_usd?: number;
127
+ total_duration_ms?: number;
128
+ total_api_duration_ms?: number;
129
+ total_lines_added?: number;
130
+ total_lines_removed?: number;
131
+ };
132
+ workspace?: {
133
+ current_dir?: string;
134
+ project_dir?: string;
135
+ };
136
+ cwd?: string;
137
+ agent?: {
138
+ name?: string;
139
+ };
140
+ vim?: {
141
+ mode?: string;
142
+ };
143
+ output_style?: {
144
+ name?: string;
145
+ };
146
+ }
105
147
  interface TeamConfig {
106
148
  name: string;
107
149
  description?: string;
@@ -172,6 +214,7 @@ interface ControllerEvents {
172
214
  "task:completed": [task: TaskFile];
173
215
  "agent:spawned": [agentName: string, pid: number];
174
216
  "agent:exited": [agentName: string, code: number | null];
217
+ "agent:statusline": [agentName: string, data: StatusLineData];
175
218
  error: [error: Error];
176
219
  }
177
220
  type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
@@ -326,6 +369,7 @@ declare class ClaudeCodeController extends EventEmitter<ControllerEvents> implem
326
369
  readonly tasks: TaskManager;
327
370
  private processes;
328
371
  private poller;
372
+ private statusLineWatcher;
329
373
  private log;
330
374
  private cwd;
331
375
  private claudeBinary;
@@ -424,6 +468,12 @@ declare class ClaudeCodeController extends EventEmitter<ControllerEvents> implem
424
468
  version: string | null;
425
469
  };
426
470
  private handlePollEvents;
471
+ /**
472
+ * Ensure the agent's cwd has a .claude/settings.local.json so the
473
+ * CLI skips the interactive workspace trust prompt.
474
+ * Also injects statusLine capture configuration.
475
+ */
476
+ private ensureWorkspaceTrusted;
427
477
  private ensureInitialized;
428
478
  }
429
479
 
@@ -557,6 +607,7 @@ interface PlanRequestInfo {
557
607
  interface AgentEvents {
558
608
  message: [text: string];
559
609
  idle: [];
610
+ statusline: [data: StatusLineData];
560
611
  permission: [request: PermissionRequestInfo];
561
612
  plan: [request: PlanRequestInfo];
562
613
  exit: [code: number | null];
@@ -663,4 +714,4 @@ declare const claude: typeof claudeCall & {
663
714
  session: (opts?: SessionOptions) => Promise<Session>;
664
715
  };
665
716
 
666
- export { Agent as A, type TaskFile as B, ClaudeCodeController as C, TaskManager as D, type TaskStatus as E, type TeamConfig as F, TeamManager as G, type TeamMember as H, type InboxMessage as I, claude as J, type Logger as L, type PermissionMode as P, type ReceiveOptions as R, type StructuredMessage as S, type TaskAssignmentMessage as T, type LogLevel as a, type AgentEvents as b, AgentHandle as c, type AgentType as d, type AskOptions as e, type ClaudeOptions as f, type ControllerEvents as g, type ControllerOptions as h, type IdleNotificationMessage as i, type PermissionPreset as j, type PermissionRequestInfo as k, type PermissionRequestMessage as l, type PermissionResponseMessage as m, type PlainTextMessage as n, type PlanApprovalRequestMessage as o, type PlanApprovalResponseMessage as p, type PlanRequestInfo as q, type SandboxPermissionRequestMessage as r, type SandboxPermissionResponseMessage as s, Session as t, type SessionAgentOptions as u, type SessionOptions as v, type ShutdownApprovedMessage as w, type ShutdownRequestMessage as x, type SpawnAgentOptions as y, type TaskCompletedMessage as z };
717
+ export { Agent as A, type TaskCompletedMessage as B, ClaudeCodeController as C, type TaskFile as D, TaskManager as E, type TaskStatus as F, type TeamConfig as G, TeamManager as H, type InboxMessage as I, type TeamMember as J, claude as K, type Logger as L, type PermissionMode as P, type ReceiveOptions as R, type StructuredMessage as S, type TaskAssignmentMessage as T, type StatusLineData as a, type LogLevel as b, type AgentEvents as c, AgentHandle as d, type AgentType as e, type AskOptions as f, type ClaudeOptions as g, type ControllerEvents as h, type ControllerOptions as i, type IdleNotificationMessage as j, type PermissionPreset as k, type PermissionRequestInfo as l, type PermissionRequestMessage as m, type PermissionResponseMessage as n, type PlainTextMessage as o, type PlanApprovalRequestMessage as p, type PlanApprovalResponseMessage as q, type PlanRequestInfo as r, type SandboxPermissionRequestMessage as s, type SandboxPermissionResponseMessage as t, Session as u, type SessionAgentOptions as v, type SessionOptions as w, type ShutdownApprovedMessage as x, type ShutdownRequestMessage as y, type SpawnAgentOptions as z };
package/dist/index.cjs CHANGED
@@ -26,8 +26,11 @@ __export(index_exports, {
26
26
  InboxPoller: () => InboxPoller,
27
27
  ProcessManager: () => ProcessManager,
28
28
  Session: () => Session,
29
+ StatusLineWatcher: () => StatusLineWatcher,
29
30
  TaskManager: () => TaskManager,
30
31
  TeamManager: () => TeamManager,
32
+ buildStatusLineCommand: () => buildStatusLineCommand,
33
+ buildStatusLineSettings: () => buildStatusLineSettings,
31
34
  claude: () => claude,
32
35
  createLogger: () => createLogger,
33
36
  inboxPath: () => inboxPath,
@@ -36,6 +39,8 @@ __export(index_exports, {
36
39
  readInbox: () => readInbox,
37
40
  readUnread: () => readUnread,
38
41
  silentLogger: () => silentLogger,
42
+ statusLineDir: () => statusLineDir,
43
+ statusLineLogPath: () => statusLineLogPath,
39
44
  taskPath: () => taskPath,
40
45
  tasksBaseDir: () => tasksBaseDir,
41
46
  tasksDir: () => tasksDir,
@@ -47,13 +52,15 @@ __export(index_exports, {
47
52
  module.exports = __toCommonJS(index_exports);
48
53
 
49
54
  // src/claude.ts
50
- var import_node_events2 = require("events");
55
+ var import_node_events3 = require("events");
51
56
  var import_node_crypto3 = require("crypto");
52
57
 
53
58
  // src/controller.ts
54
- var import_node_events = require("events");
59
+ var import_node_events2 = require("events");
55
60
  var import_node_child_process3 = require("child_process");
56
61
  var import_node_crypto2 = require("crypto");
62
+ var import_node_fs5 = require("fs");
63
+ var import_node_path4 = require("path");
57
64
 
58
65
  // src/team-manager.ts
59
66
  var import_promises = require("fs/promises");
@@ -386,6 +393,8 @@ if pid == 0:
386
393
  else:
387
394
  signal.signal(signal.SIGTERM, lambda *a: (os.kill(pid, signal.SIGTERM), sys.exit(0)))
388
395
  signal.signal(signal.SIGINT, lambda *a: (os.kill(pid, signal.SIGTERM), sys.exit(0)))
396
+ buf = b""
397
+ trust_sent = False
389
398
  try:
390
399
  while True:
391
400
  r, _, _ = select.select([fd, 0], [], [], 1.0)
@@ -395,6 +404,12 @@ else:
395
404
  if not data:
396
405
  break
397
406
  os.write(1, data)
407
+ if not trust_sent:
408
+ buf += data
409
+ if b"Yes" in buf and b"trust" in buf:
410
+ os.write(fd, b"\\r")
411
+ trust_sent = True
412
+ buf = b""
398
413
  except OSError:
399
414
  break
400
415
  if 0 in r:
@@ -750,6 +765,135 @@ var silentLogger = {
750
765
  }
751
766
  };
752
767
 
768
+ // src/statusline-capture.ts
769
+ var import_node_events = require("events");
770
+ var import_node_fs4 = require("fs");
771
+ var import_node_path3 = require("path");
772
+ function statusLineDir(teamName) {
773
+ return (0, import_node_path3.join)(teamDir(teamName), "statusline");
774
+ }
775
+ function statusLineLogPath(teamName, agentName) {
776
+ return (0, import_node_path3.join)(statusLineDir(teamName), `${agentName}.jsonl`);
777
+ }
778
+ function buildStatusLineCommand(logFilePath) {
779
+ const escapedPath = logFilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
780
+ return [
781
+ "python3 -c",
782
+ `'import sys,json;`,
783
+ `d=json.load(sys.stdin);`,
784
+ `open("${escapedPath}","a").write(json.dumps(d)+"\\n");`,
785
+ `print(d.get("model",{}).get("display_name",""))'`
786
+ ].join(" ");
787
+ }
788
+ function buildStatusLineSettings(logFilePath) {
789
+ return {
790
+ statusLine: {
791
+ type: "command",
792
+ command: buildStatusLineCommand(logFilePath)
793
+ }
794
+ };
795
+ }
796
+ var StatusLineWatcher = class extends import_node_events.EventEmitter {
797
+ teamName;
798
+ log;
799
+ watchers = /* @__PURE__ */ new Map();
800
+ fileOffsets = /* @__PURE__ */ new Map();
801
+ stopped = false;
802
+ constructor(teamName, logger) {
803
+ super();
804
+ this.teamName = teamName;
805
+ this.log = logger;
806
+ }
807
+ /**
808
+ * Ensure the statusline directory exists.
809
+ */
810
+ ensureDir() {
811
+ const dir = statusLineDir(this.teamName);
812
+ if (!(0, import_node_fs4.existsSync)(dir)) {
813
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
814
+ }
815
+ }
816
+ /**
817
+ * Start watching a specific agent's statusLine log file.
818
+ */
819
+ watchAgent(agentName) {
820
+ if (this.stopped) return;
821
+ const filePath = statusLineLogPath(this.teamName, agentName);
822
+ if (!(0, import_node_fs4.existsSync)(filePath)) {
823
+ (0, import_node_fs4.writeFileSync)(filePath, "");
824
+ }
825
+ try {
826
+ const stats = (0, import_node_fs4.readFileSync)(filePath);
827
+ this.fileOffsets.set(filePath, stats.length);
828
+ } catch {
829
+ this.fileOffsets.set(filePath, 0);
830
+ }
831
+ try {
832
+ const watcher = (0, import_node_fs4.watch)(filePath, (eventType) => {
833
+ if (eventType === "change") {
834
+ this.readNewLines(agentName, filePath);
835
+ }
836
+ });
837
+ this.watchers.set(agentName, watcher);
838
+ this.log.debug(`Watching statusLine for agent "${agentName}" at ${filePath}`);
839
+ } catch (err) {
840
+ this.log.error(`Failed to watch statusLine for "${agentName}": ${err}`);
841
+ }
842
+ }
843
+ /**
844
+ * Stop watching a specific agent.
845
+ */
846
+ unwatchAgent(agentName) {
847
+ const watcher = this.watchers.get(agentName);
848
+ if (watcher) {
849
+ watcher.close();
850
+ this.watchers.delete(agentName);
851
+ }
852
+ const filePath = statusLineLogPath(this.teamName, agentName);
853
+ this.fileOffsets.delete(filePath);
854
+ }
855
+ /**
856
+ * Stop all watchers.
857
+ */
858
+ stop() {
859
+ this.stopped = true;
860
+ for (const [, watcher] of this.watchers) {
861
+ watcher.close();
862
+ }
863
+ this.watchers.clear();
864
+ this.fileOffsets.clear();
865
+ }
866
+ /**
867
+ * Read new lines appended to the log file since last read.
868
+ */
869
+ readNewLines(agentName, filePath) {
870
+ try {
871
+ const content = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
872
+ const offset = this.fileOffsets.get(filePath) ?? 0;
873
+ const newContent = content.slice(offset);
874
+ this.fileOffsets.set(filePath, content.length);
875
+ if (!newContent.trim()) return;
876
+ const lines = newContent.trim().split("\n");
877
+ for (const line of lines) {
878
+ if (!line.trim()) continue;
879
+ try {
880
+ const data = JSON.parse(line);
881
+ const event = {
882
+ agentName,
883
+ data,
884
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
885
+ };
886
+ this.emit("update", event);
887
+ } catch (parseErr) {
888
+ this.log.debug(`Failed to parse statusLine JSON: ${line}`);
889
+ }
890
+ }
891
+ } catch (err) {
892
+ this.log.debug(`Error reading statusLine file: ${err}`);
893
+ }
894
+ }
895
+ };
896
+
753
897
  // src/controller.ts
754
898
  var PROTOCOL_ONLY_TYPES = /* @__PURE__ */ new Set([
755
899
  "shutdown_approved",
@@ -769,12 +913,13 @@ var AGENT_COLORS = [
769
913
  "#FF69B4",
770
914
  "#7B68EE"
771
915
  ];
772
- var ClaudeCodeController = class extends import_node_events.EventEmitter {
916
+ var ClaudeCodeController = class extends import_node_events2.EventEmitter {
773
917
  teamName;
774
918
  team;
775
919
  tasks;
776
920
  processes;
777
921
  poller;
922
+ statusLineWatcher;
778
923
  log;
779
924
  cwd;
780
925
  claudeBinary;
@@ -796,7 +941,11 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
796
941
  "controller",
797
942
  this.log
798
943
  );
944
+ this.statusLineWatcher = new StatusLineWatcher(this.teamName, this.log);
799
945
  this.poller.onMessages((events) => this.handlePollEvents(events));
946
+ this.statusLineWatcher.on("update", (event) => {
947
+ this.emit("agent:statusline", event.agentName, event.data);
948
+ });
800
949
  }
801
950
  // ─── Lifecycle ───────────────────────────────────────────────────────
802
951
  /**
@@ -807,6 +956,7 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
807
956
  if (this.initialized) return this;
808
957
  await this.team.create({ cwd: this.cwd });
809
958
  await this.tasks.init();
959
+ this.statusLineWatcher.ensureDir();
810
960
  this.poller.start();
811
961
  this.initialized = true;
812
962
  this.log.info(
@@ -847,6 +997,7 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
847
997
  }
848
998
  await this.processes.killAll();
849
999
  this.poller.stop();
1000
+ this.statusLineWatcher.stop();
850
1001
  await this.team.destroy();
851
1002
  this.initialized = false;
852
1003
  this.log.info("Controller shut down");
@@ -875,6 +1026,8 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
875
1026
  subscriptions: []
876
1027
  };
877
1028
  await this.team.addMember(member);
1029
+ this.ensureWorkspaceTrusted(cwd, opts.name);
1030
+ this.statusLineWatcher.watchAgent(opts.name);
878
1031
  const env = Object.keys(this.defaultEnv).length > 0 || opts.env ? { ...this.defaultEnv, ...opts.env } : void 0;
879
1032
  const proc = this.processes.spawn({
880
1033
  teamName: this.teamName,
@@ -1081,6 +1234,7 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
1081
1234
  * Kill a specific agent.
1082
1235
  */
1083
1236
  async killAgent(name) {
1237
+ this.statusLineWatcher.unwatchAgent(name);
1084
1238
  await this.processes.kill(name);
1085
1239
  await this.team.removeMember(name);
1086
1240
  }
@@ -1142,6 +1296,32 @@ var ClaudeCodeController = class extends import_node_events.EventEmitter {
1142
1296
  }
1143
1297
  }
1144
1298
  }
1299
+ /**
1300
+ * Ensure the agent's cwd has a .claude/settings.local.json so the
1301
+ * CLI skips the interactive workspace trust prompt.
1302
+ * Also injects statusLine capture configuration.
1303
+ */
1304
+ ensureWorkspaceTrusted(cwd, agentName) {
1305
+ const claudeDir = (0, import_node_path4.join)(cwd, ".claude");
1306
+ const settingsPath = (0, import_node_path4.join)(claudeDir, "settings.local.json");
1307
+ (0, import_node_fs5.mkdirSync)(claudeDir, { recursive: true });
1308
+ let settings = {};
1309
+ if ((0, import_node_fs5.existsSync)(settingsPath)) {
1310
+ try {
1311
+ settings = JSON.parse((0, import_node_fs5.readFileSync)(settingsPath, "utf-8"));
1312
+ } catch {
1313
+ settings = {};
1314
+ }
1315
+ }
1316
+ if (agentName && !settings.statusLine) {
1317
+ const logPath = statusLineLogPath(this.teamName, agentName);
1318
+ const statusLineSettings = buildStatusLineSettings(logPath);
1319
+ settings = { ...settings, ...statusLineSettings };
1320
+ this.log.debug(`Injected statusLine capture for "${agentName}"`);
1321
+ }
1322
+ (0, import_node_fs5.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1323
+ this.log.debug(`Updated ${settingsPath}`);
1324
+ }
1145
1325
  ensureInitialized() {
1146
1326
  if (!this.initialized) {
1147
1327
  throw new Error(
@@ -1226,7 +1406,7 @@ function waitForReady(controller, agentName, timeoutMs = 15e3) {
1226
1406
  controller.on("agent:exited", onExit);
1227
1407
  });
1228
1408
  }
1229
- var Agent = class _Agent extends import_node_events2.EventEmitter {
1409
+ var Agent = class _Agent extends import_node_events3.EventEmitter {
1230
1410
  controller;
1231
1411
  handle;
1232
1412
  ownsController;
@@ -1319,14 +1499,22 @@ var Agent = class _Agent extends import_node_events2.EventEmitter {
1319
1499
  this.ensureNotDisposed();
1320
1500
  const timeout = opts?.timeout ?? 12e4;
1321
1501
  const responsePromise = new Promise((resolve, reject) => {
1502
+ let gotMessage = false;
1322
1503
  const timer = setTimeout(() => {
1323
1504
  cleanup();
1324
1505
  reject(new Error(`Timeout (${timeout}ms) waiting for response`));
1325
1506
  }, timeout);
1326
1507
  const onMsg = (text) => {
1508
+ gotMessage = true;
1327
1509
  cleanup();
1328
1510
  resolve(text);
1329
1511
  };
1512
+ const onIdle = () => {
1513
+ if (!gotMessage) {
1514
+ cleanup();
1515
+ resolve("");
1516
+ }
1517
+ };
1330
1518
  const onExit = (code) => {
1331
1519
  cleanup();
1332
1520
  reject(new Error(`Agent exited (code=${code}) before responding`));
@@ -1334,9 +1522,11 @@ var Agent = class _Agent extends import_node_events2.EventEmitter {
1334
1522
  const cleanup = () => {
1335
1523
  clearTimeout(timer);
1336
1524
  this.removeListener("message", onMsg);
1525
+ this.removeListener("idle", onIdle);
1337
1526
  this.removeListener("exit", onExit);
1338
1527
  };
1339
1528
  this.on("message", onMsg);
1529
+ this.on("idle", onIdle);
1340
1530
  this.on("exit", onExit);
1341
1531
  });
1342
1532
  const wrapped = `${question}
@@ -1355,14 +1545,22 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1355
1545
  this.ensureNotDisposed();
1356
1546
  const timeout = opts?.timeout ?? 12e4;
1357
1547
  return new Promise((resolve, reject) => {
1548
+ let gotMessage = false;
1358
1549
  const timer = setTimeout(() => {
1359
1550
  cleanup();
1360
1551
  reject(new Error(`Timeout (${timeout}ms) waiting for response`));
1361
1552
  }, timeout);
1362
1553
  const onMsg = (text) => {
1554
+ gotMessage = true;
1363
1555
  cleanup();
1364
1556
  resolve(text);
1365
1557
  };
1558
+ const onIdle = () => {
1559
+ if (!gotMessage) {
1560
+ cleanup();
1561
+ resolve("");
1562
+ }
1563
+ };
1366
1564
  const onExit = (code) => {
1367
1565
  cleanup();
1368
1566
  reject(new Error(`Agent exited (code=${code}) before responding`));
@@ -1370,9 +1568,11 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1370
1568
  const cleanup = () => {
1371
1569
  clearTimeout(timer);
1372
1570
  this.removeListener("message", onMsg);
1571
+ this.removeListener("idle", onIdle);
1373
1572
  this.removeListener("exit", onExit);
1374
1573
  };
1375
1574
  this.on("message", onMsg);
1575
+ this.on("idle", onIdle);
1376
1576
  this.on("exit", onExit);
1377
1577
  });
1378
1578
  }
@@ -1406,6 +1606,9 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1406
1606
  const onIdle = (name, _details) => {
1407
1607
  if (name === agentName) this.emit("idle");
1408
1608
  };
1609
+ const onStatusLine = (name, data) => {
1610
+ if (name === agentName) this.emit("statusline", data);
1611
+ };
1409
1612
  const onPermission = (name, parsed) => {
1410
1613
  if (name !== agentName) return;
1411
1614
  let handled = false;
@@ -1454,6 +1657,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1454
1657
  };
1455
1658
  this.controller.on("message", onMessage);
1456
1659
  this.controller.on("idle", onIdle);
1660
+ this.controller.on("agent:statusline", onStatusLine);
1457
1661
  this.controller.on("permission:request", onPermission);
1458
1662
  this.controller.on("plan:approval_request", onPlan);
1459
1663
  this.controller.on("agent:exited", onExit);
@@ -1461,6 +1665,7 @@ IMPORTANT: You MUST send your complete answer back using the SendMessage tool. D
1461
1665
  this.boundListeners = [
1462
1666
  { event: "message", fn: onMessage },
1463
1667
  { event: "idle", fn: onIdle },
1668
+ { event: "agent:statusline", fn: onStatusLine },
1464
1669
  { event: "permission:request", fn: onPermission },
1465
1670
  { event: "plan:approval_request", fn: onPlan },
1466
1671
  { event: "agent:exited", fn: onExit },
@@ -1607,8 +1812,11 @@ var claude = Object.assign(claudeCall, {
1607
1812
  InboxPoller,
1608
1813
  ProcessManager,
1609
1814
  Session,
1815
+ StatusLineWatcher,
1610
1816
  TaskManager,
1611
1817
  TeamManager,
1818
+ buildStatusLineCommand,
1819
+ buildStatusLineSettings,
1612
1820
  claude,
1613
1821
  createLogger,
1614
1822
  inboxPath,
@@ -1617,6 +1825,8 @@ var claude = Object.assign(claudeCall, {
1617
1825
  readInbox,
1618
1826
  readUnread,
1619
1827
  silentLogger,
1828
+ statusLineDir,
1829
+ statusLineLogPath,
1620
1830
  taskPath,
1621
1831
  tasksBaseDir,
1622
1832
  tasksDir,