@lucascouts/claude-agent-acp-plus 0.25.3 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/acp-agent.js CHANGED
@@ -42,7 +42,8 @@ import { ACCOUNT_CONFIG_ID, accountForValue, accountInForce, createAccountConfig
42
42
  import { isUsageCommandText, structuredUsageMarkdown, syntheticLocalCommand, } from "./usage-markdown.js";
43
43
  export { DEFAULT_AGENT_ID, EFFORT_CONFIG_ID } from "./session-config-ids.js";
44
44
  import { MODE_CONFIG_ID, SessionModeManager } from "./session-mode.js";
45
- import { TASKS_META_KEY, TaskFeed, TaskFeedPublisher } from "./task-feed.js";
45
+ import { TASKS_CONTRACT_VERSION, TASKS_META_KEY, normaliseStatus, TailPoller, TaskFeed, TaskFeedPublisher, } from "./task-feed.js";
46
+ import { OutputUnavailable, outputPathFromBashResult, readOutput } from "./task-output.js";
46
47
  import { EscalationRegistry, GrantStore, createAskOperator, createGrantPreToolUseHook, createPermissionDeniedHook, } from "./classifier-escalation.js";
47
48
  export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
48
49
  const execFileAsync = promisify(execFile);
@@ -142,11 +143,37 @@ export const TASKS_STOP_METHOD = "_claude/tasks/stop";
142
143
  /** Move a foreground tool call's task to the background (story 012, R2.3).
143
144
  * Params {@link TasksBackgroundRequest}, result {@link TasksBackgroundResponse}. */
144
145
  export const TASKS_BACKGROUND_METHOD = "_claude/tasks/background";
145
- /** `agentCapabilities._meta` key advertising the task feed and its two
146
- * methods (R2.7). Zeo gates on this, never on the agent's name — the name is
146
+ /** Read the end of a task's output file (story 041, R2.1). Params
147
+ * {@link TasksOutputRequest}, result {@link TasksOutputResponse}. Version 2. */
148
+ export const TASKS_OUTPUT_METHOD = "_claude/tasks/output";
149
+ /** `agentCapabilities._meta` key advertising the task feed and its methods
150
+ * (R2.7). Zeo gates on this, never on the agent's name — the name is
147
151
  * `package.json`'s, and differs between this fork and the published mirror. */
148
152
  export const TASKS_CAPABILITY_KEY = TASKS_META_KEY;
149
- export const TASKS_CAPABILITY_VERSION = 1;
153
+ /** Version 2 (story 041) adds `hasOutput`, `tail` and `_claude/tasks/output`. */
154
+ export const TASKS_CAPABILITY_VERSION = TASKS_CONTRACT_VERSION;
155
+ /** Refresh the account quota windows of one session now, on demand. Params
156
+ * {@link UsageRefreshRequest}, result {@link UsageRefreshResponse}. The windows
157
+ * themselves arrive the way they always do — one `usage_update` per window,
158
+ * carrying `_meta["_claude/rateLimit"]` — before the response does. */
159
+ export const USAGE_REFRESH_METHOD = "_claude/usage/refresh";
160
+ /** `agentCapabilities._meta` key advertising {@link USAGE_REFRESH_METHOD}. A
161
+ * client gates on this, never on the agent's name (see
162
+ * {@link TASKS_CAPABILITY_KEY}). */
163
+ export const USAGE_CAPABILITY_KEY = "_claude/usage";
164
+ export const USAGE_CAPABILITY_VERSION = 1;
165
+ /** How old a shared quota sample may be and still answer an on-demand refresh.
166
+ *
167
+ * The client asks on every hover of the context ring, so this is the knob that
168
+ * trades freshness against cost. Five seconds is shorter than any change a
169
+ * person reading a tooltip could tell apart — neither the five-hour nor the
170
+ * weekly window moves measurably in that time — yet long enough that a burst
171
+ * of hovers, or several Zeo windows hovered in turn, costs one control request
172
+ * between them rather than one each. Much shorter than the idle poll's
173
+ * {@link sharedSampleMaxAgeMs}: a hover is someone asking "what is it NOW?",
174
+ * and a sample most of an interval old is exactly what the poll already
175
+ * showed them. */
176
+ export const USAGE_REFRESH_MAX_SAMPLE_AGE_MS = 5_000;
150
177
  /** The SDK's own text for a rejected control request, passed through to the
151
178
  * client as the internal error's message (R2.4). */
152
179
  function errorMessage(error) {
@@ -171,6 +198,18 @@ export function parseTasksStopRequest(params) {
171
198
  taskId: requireIdField(params, TASKS_STOP_METHOD, "taskId"),
172
199
  };
173
200
  }
201
+ /** Validate raw JSON-RPC params into a {@link TasksOutputRequest}. Extra keys —
202
+ * a `path` included — are ignored: only a file the adapter recorded is read
203
+ * (R2.5). */
204
+ export function parseTasksOutputRequest(params) {
205
+ const sessionId = requireIdField(params, TASKS_OUTPUT_METHOD, "sessionId");
206
+ const taskId = requireIdField(params, TASKS_OUTPUT_METHOD, "taskId");
207
+ const maxBytes = params && typeof params === "object" ? params.maxBytes : undefined;
208
+ if (typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
209
+ throw RequestError.invalidParams(undefined, `${TASKS_OUTPUT_METHOD} params require maxBytes, a positive integer`);
210
+ }
211
+ return { sessionId, taskId, maxBytes };
212
+ }
174
213
  /** Validate raw JSON-RPC params into a {@link TasksBackgroundRequest}. */
175
214
  export function parseTasksBackgroundRequest(params) {
176
215
  return {
@@ -178,6 +217,10 @@ export function parseTasksBackgroundRequest(params) {
178
217
  toolCallId: requireIdField(params, TASKS_BACKGROUND_METHOD, "toolCallId"),
179
218
  };
180
219
  }
220
+ /** Validate raw JSON-RPC params into a {@link UsageRefreshRequest}. */
221
+ export function parseUsageRefreshRequest(params) {
222
+ return { sessionId: requireIdField(params, USAGE_REFRESH_METHOD, "sessionId") };
223
+ }
181
224
  /** How often the account quota windows are refreshed while a session sits idle,
182
225
  * in milliseconds; `0` disables polling entirely.
183
226
  *
@@ -187,10 +230,17 @@ export function parseTasksBackgroundRequest(params) {
187
230
  * the old utilization, and the bar is wrong in the one direction that matters
188
231
  * (it under-reports how much is available) until the next turn ends.
189
232
  *
233
+ * Thirty seconds by default (sixty until the quota tooltip): the tooltip over
234
+ * the context ring shows these windows as numbers, so a reading up to a minute
235
+ * old was visible as one. The cost does not double with it — every adapter
236
+ * process on the desktop shares one sample per interval (quota-cache.ts), so
237
+ * it is still about one control request per interval per account. Hovering
238
+ * the ring asks for a fresher one on demand (`_claude/usage/refresh`).
239
+ *
190
240
  * The floor is not timidity: each tick costs one control request, and neither
191
241
  * the five-hour nor the seven-day window can move fast enough for a tighter
192
242
  * loop to show anything a 15-second one would miss. */
193
- const QUOTA_POLL_DEFAULT_MS = 60_000;
243
+ const QUOTA_POLL_DEFAULT_MS = 30_000;
194
244
  const QUOTA_POLL_FLOOR_MS = 15_000;
195
245
  /** Whether sessions ask the SDK for progress summaries on subagent tasks
196
246
  * (`CLAUDE_ACP_PROGRESS_SUMMARIES`). Only `1` or `true` turn it on; anything
@@ -855,6 +905,7 @@ export class ClaudeAcpAgent {
855
905
  promptQueueing: true,
856
906
  },
857
907
  [TASKS_CAPABILITY_KEY]: { version: TASKS_CAPABILITY_VERSION },
908
+ [USAGE_CAPABILITY_KEY]: { version: USAGE_CAPABILITY_VERSION },
858
909
  },
859
910
  promptCapabilities: {
860
911
  image: true,
@@ -1333,14 +1384,19 @@ export class ClaudeAcpAgent {
1333
1384
  */
1334
1385
  /** Fetch the account's quota windows and publish them to this session.
1335
1386
  *
1336
- * `allowCache` is the difference between the two callers. A turn ending and a
1387
+ * `cacheMaxAgeMs` is the difference between the callers. A turn ending and a
1337
1388
  * session being established are moments when THIS session's own consumption
1338
- * just changed, so they always fetch. The idle poll has no such reason: it is
1339
- * asking about an account, and every other adapter process on the desktop is
1340
- * asking the same question about the same account. It reads the shared sample
1341
- * instead -- see quota-cache.ts for why sharing, not tighter intervals, is
1342
- * what makes separate windows agree. */
1343
- async publishAccountUsage(sessionId, session, { allowCache = false } = {}) {
1389
+ * just changed, so they pass none and always fetch. The idle poll and the
1390
+ * on-demand refresh have no such reason: they are asking about an account,
1391
+ * and every other adapter process on the desktop is asking the same question
1392
+ * about the same account. They accept a shared sample up to that age instead
1393
+ * -- see quota-cache.ts for why sharing, not tighter intervals, is what makes
1394
+ * separate windows agree.
1395
+ *
1396
+ * Resolves `true` when at least one window was published, `false` when none
1397
+ * was: no report, a report with no plan windows (an API-key session), or a
1398
+ * session replaced while the request was in flight. Never rejects. */
1399
+ async publishAccountUsage(sessionId, session, { cacheMaxAgeMs } = {}) {
1344
1400
  try {
1345
1401
  // Not `getUsage`: this awful spelling is the only name the real `Query`
1346
1402
  // answers to, and it is written out here rather than hidden behind a
@@ -1354,9 +1410,7 @@ export class ClaudeAcpAgent {
1354
1410
  // A sample another process already paid for, when this call is allowed to
1355
1411
  // take one. `null` covers every failure the cache can have, so a broken
1356
1412
  // cache degrades to exactly the behaviour that existed before it.
1357
- const cached = allowCache
1358
- ? await readFreshLimits(sharedSampleMaxAgeMs(quotaPollIntervalMs()))
1359
- : null;
1413
+ const cached = cacheMaxAgeMs !== undefined ? await readFreshLimits(cacheMaxAgeMs) : null;
1360
1414
  const report = cached ??
1361
1415
  (await session.query.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET({
1362
1416
  skipBehaviors: true,
@@ -1371,7 +1425,7 @@ export class ClaudeAcpAgent {
1371
1425
  // flight (provider switch, a changed cwd on session/load). A successor
1372
1426
  // must not inherit its predecessor's windows.
1373
1427
  if (this.sessions[sessionId] !== session) {
1374
- return;
1428
+ return false;
1375
1429
  }
1376
1430
  // `usage_update` is the only carrier ACP gives this `_meta`, and it
1377
1431
  // requires both numbers. `contextUsedTokens` is the session-scoped mirror
@@ -1387,6 +1441,7 @@ export class ClaudeAcpAgent {
1387
1441
  // single report's bars disagree about the context they were measured in.
1388
1442
  const used = session.contextUsedTokens ?? 0;
1389
1443
  const size = session.contextWindowSize;
1444
+ let published = false;
1390
1445
  for (const window of this.accountUsageTracker(session).windowsFrom(report)) {
1391
1446
  await this.client.sessionUpdate({
1392
1447
  sessionId,
@@ -1397,10 +1452,13 @@ export class ClaudeAcpAgent {
1397
1452
  _meta: { "_claude/rateLimit": window },
1398
1453
  },
1399
1454
  });
1455
+ published = true;
1400
1456
  }
1457
+ return published;
1401
1458
  }
1402
1459
  catch (error) {
1403
1460
  this.logger.error(`Session ${sessionId}: structured usage report unavailable: ${error}`);
1461
+ return false;
1404
1462
  }
1405
1463
  }
1406
1464
  /** Start the idle refresh of the account quota windows for this session.
@@ -1421,6 +1479,7 @@ export class ClaudeAcpAgent {
1421
1479
  if (intervalMs === 0) {
1422
1480
  return;
1423
1481
  }
1482
+ const cacheMaxAgeMs = sharedSampleMaxAgeMs(intervalMs);
1424
1483
  // Closure-local rather than a Session field: it guards this timer alone, and
1425
1484
  // a slow report must not let ticks pile up into a queue of duplicate control
1426
1485
  // requests against a session that is already struggling to answer one.
@@ -1439,7 +1498,7 @@ export class ClaudeAcpAgent {
1439
1498
  inFlight = true;
1440
1499
  // `publishAccountUsage` never rejects — it logs and returns — so this
1441
1500
  // cannot become an unhandled rejection, and `finally` always re-opens.
1442
- void this.publishAccountUsage(sessionId, session, { allowCache: true }).finally(() => {
1501
+ void this.publishAccountUsage(sessionId, session, { cacheMaxAgeMs }).finally(() => {
1443
1502
  inFlight = false;
1444
1503
  });
1445
1504
  }, intervalMs);
@@ -1447,6 +1506,70 @@ export class ClaudeAcpAgent {
1447
1506
  timer.unref?.();
1448
1507
  session.accountUsageTimer = timer;
1449
1508
  }
1509
+ /**
1510
+ * `_claude/usage/refresh`: publish this session's quota windows now, on
1511
+ * demand, and answer whether any were published.
1512
+ *
1513
+ * The client asks on every hover of the context ring, so the cost is bounded
1514
+ * twice. A shared sample younger than {@link USAGE_REFRESH_MAX_SAMPLE_AGE_MS}
1515
+ * answers without a control request, and a refresh already in flight for this
1516
+ * session is joined rather than repeated — however fast the hovers come, one
1517
+ * session never has two of these requests queued.
1518
+ *
1519
+ * Skipped while a turn is in flight, answering `{ refreshed: false }`. The
1520
+ * report is read-only, but SDK control requests are SERIALIZED: an interrupt
1521
+ * queued behind a usage request waits for it (upstream #1216 measured
1522
+ * 0.7-1.1 s for the same queueing behind `getContextUsage`), and `cancel`
1523
+ * awaits its interrupt. A hover during a turn is the common case — people
1524
+ * watch the ring fill — so allowing it would put a round-trip in front of the
1525
+ * Stop button exactly when it is most likely to be pressed. Nothing is lost:
1526
+ * the turn's own end refreshes the windows (R1.2), and a live
1527
+ * `rate_limit_event` still lands mid-turn.
1528
+ *
1529
+ * Before the first turn a control request may not be serviced at all on older
1530
+ * CLIs (#886/#880); such a call simply waits, joined by every later hover,
1531
+ * alongside the establishment refresh that is already waiting the same way.
1532
+ *
1533
+ * Never rejects for a missing report: a fetch failure is logged by
1534
+ * `publishAccountUsage` and answers `false`, as does an API-key session,
1535
+ * which has no plan windows to publish. Only an unknown session is an error,
1536
+ * the same invalid-params error the `_claude/tasks` methods answer.
1537
+ */
1538
+ async refreshUsage(params) {
1539
+ const { sessionId } = params;
1540
+ const log = (outcome, reason) => this.logger.log(`[usage/refresh] method=${USAGE_REFRESH_METHOD} sessionId=${logValue(sessionId)} outcome=${outcome}` +
1541
+ (reason ? ` reason=${reason}` : ""));
1542
+ const session = Object.hasOwn(this.sessions, sessionId) ? this.sessions[sessionId] : undefined;
1543
+ if (!session) {
1544
+ log("error", "unknown_session");
1545
+ throw RequestError.invalidParams(undefined, `unknown session ${sessionId}`);
1546
+ }
1547
+ if (session.queryClosed) {
1548
+ log("skipped", "session_ended");
1549
+ return { refreshed: false };
1550
+ }
1551
+ let coalesced = true;
1552
+ let pending = session.usageRefreshInFlight;
1553
+ if (!pending) {
1554
+ if (session.turnQueue?.length || session.activeTurn) {
1555
+ log("skipped", "turn_in_flight");
1556
+ return { refreshed: false };
1557
+ }
1558
+ coalesced = false;
1559
+ pending = this.publishAccountUsage(sessionId, session, {
1560
+ cacheMaxAgeMs: USAGE_REFRESH_MAX_SAMPLE_AGE_MS,
1561
+ }).finally(() => {
1562
+ if (session.usageRefreshInFlight === pending) {
1563
+ session.usageRefreshInFlight = undefined;
1564
+ }
1565
+ });
1566
+ session.usageRefreshInFlight = pending;
1567
+ }
1568
+ // `publishAccountUsage` never rejects, so neither does this await.
1569
+ const refreshed = await pending;
1570
+ log(refreshed ? "published" : "nothing", coalesced ? "coalesced" : undefined);
1571
+ return { refreshed };
1572
+ }
1450
1573
  async publishGoalFromPrompt(sessionId, prompt, commandUuid) {
1451
1574
  const goalUpdate = goalUpdateFromPrompt(prompt);
1452
1575
  if (goalUpdate !== undefined) {
@@ -1520,6 +1643,39 @@ export class ClaudeAcpAgent {
1520
1643
  return {};
1521
1644
  });
1522
1645
  }
1646
+ /**
1647
+ * `_claude/tasks/output`: the end of a task's output file (story 041, R2.1–R2.7).
1648
+ * Serves only a path the feed recorded for its own task, and works on an
1649
+ * ended session too, as long as the session object exists. One log line,
1650
+ * never the content.
1651
+ */
1652
+ async taskOutput(params) {
1653
+ const { sessionId, taskId, maxBytes } = params;
1654
+ const log = (outcome, detail) => this.logger.log(`[tasks/output] method=${TASKS_OUTPUT_METHOD} sessionId=${logValue(sessionId)} taskId=${logValue(taskId)} ${detail} outcome=${outcome}`);
1655
+ const refuse = (error, reason) => {
1656
+ log("error", `reason=${logValue(reason)}`);
1657
+ throw error;
1658
+ };
1659
+ const session = Object.hasOwn(this.sessions, sessionId) ? this.sessions[sessionId] : undefined;
1660
+ if (!session) {
1661
+ return refuse(RequestError.invalidParams(undefined, `unknown session ${sessionId}`), "unknown_session");
1662
+ }
1663
+ const path = session.taskFeed?.outputPathFor(taskId) ?? null;
1664
+ if (path === null) {
1665
+ return refuse(RequestError.invalidParams(undefined, `task ${taskId} has no recorded output`), "no_recorded_output");
1666
+ }
1667
+ try {
1668
+ const result = await readOutput(path, maxBytes);
1669
+ log("ok", `bytes=${Buffer.byteLength(result.content, "utf8")} truncated=${result.truncated}`);
1670
+ return result;
1671
+ }
1672
+ catch (error) {
1673
+ if (error instanceof OutputUnavailable) {
1674
+ return refuse(RequestError.internalError(undefined, `output of task ${taskId} is no longer available (${error.code})`), error.code);
1675
+ }
1676
+ return refuse(RequestError.internalError(undefined, logValue(errorMessage(error))), "unexpected");
1677
+ }
1678
+ }
1523
1679
  /** `_claude/tasks/background`: background the task a foreground tool call
1524
1680
  * started (R2.3, R2.4). `false` means the id matched no foreground task. */
1525
1681
  async backgroundTask(params) {
@@ -3114,7 +3270,14 @@ export class ClaudeAcpAgent {
3114
3270
  // The task settled — no further tool calls can originate
3115
3271
  // from it, so its registry entry can be dropped.
3116
3272
  session.liveBackgroundTasks.delete(message.task_id);
3117
- session.taskFeedPublisher?.notify(session.taskFeed.onNotification(message));
3273
+ if (session.taskFeed && session.taskFeedPublisher) {
3274
+ const readable = await endingOutputReadable(session, message.task_id, message.output_file);
3275
+ const change = session.taskFeed.onNotification(message);
3276
+ if (change === "terminal" && readable !== null) {
3277
+ session.taskFeed.setTail(message.task_id, null, readable);
3278
+ }
3279
+ session.taskFeedPublisher.notify(change);
3280
+ }
3118
3281
  break;
3119
3282
  case "task_updated":
3120
3283
  // terminal-status task_updated patch and a (deduplicated)
@@ -3127,7 +3290,19 @@ export class ClaudeAcpAgent {
3127
3290
  message.patch.status === "killed") {
3128
3291
  session.liveBackgroundTasks.delete(message.task_id);
3129
3292
  }
3130
- session.taskFeedPublisher?.notify(session.taskFeed.onUpdated(message));
3293
+ if (session.taskFeed && session.taskFeedPublisher) {
3294
+ // The CLI queues the terminal patch BEFORE the
3295
+ // task_notification, so this is usually where a shell ends
3296
+ // and its output check has to happen (story 041, R1.7).
3297
+ const readable = normaliseStatus(message.patch.status) !== null
3298
+ ? await endingOutputReadable(session, message.task_id)
3299
+ : null;
3300
+ const change = session.taskFeed.onUpdated(message);
3301
+ if (change === "terminal" && readable !== null) {
3302
+ session.taskFeed.setTail(message.task_id, null, readable);
3303
+ }
3304
+ session.taskFeedPublisher.notify(change);
3305
+ }
3131
3306
  break;
3132
3307
  case "worker_shutting_down":
3133
3308
  // Defer until stream end. The announcement is durable and may be
@@ -4249,6 +4424,9 @@ export class ClaudeAcpAgent {
4249
4424
  content = message.message.content;
4250
4425
  }
4251
4426
  const acceptedPlanToolUseId = observeExitPlanToolResults(message, content, session);
4427
+ // Before toAcpNotifications, which prunes the tool_use cache entry
4428
+ // that says the result is a Bash call's.
4429
+ observeBackgroundBashResult(message, content, session);
4252
4430
  for (const notification of toAcpNotifications(content, message.message.role, params.sessionId, session.toolUseCache, this.client, this.logger, {
4253
4431
  clientCapabilities: this.clientCapabilities,
4254
4432
  parentToolUseId: message.parent_tool_use_id,
@@ -4764,6 +4942,9 @@ export class ClaudeAcpAgent {
4764
4942
  * query recreate is the one exit that keeps the feed, and does not come
4765
4943
  * through here. */
4766
4944
  retireTaskFeed(session) {
4945
+ // Before the publisher: a tick landing after the flush would publish a
4946
+ // tail into a feed whose session is gone (story 041).
4947
+ session.tailPoller?.dispose();
4767
4948
  const publisher = session.taskFeedPublisher;
4768
4949
  if (!publisher)
4769
4950
  return;
@@ -4783,7 +4964,8 @@ export class ClaudeAcpAgent {
4783
4964
  sessionId,
4784
4965
  update: { sessionUpdate: "session_info_update", _meta: { [TASKS_META_KEY]: snapshot } },
4785
4966
  }), undefined, (error) => this.logger.error(`[tasks/feed] sessionId=${logValue(sessionId)} outcome=send_failed reason=${logValue(errorMessage(error))}`));
4786
- return { taskFeed, taskFeedPublisher };
4967
+ const tailPoller = new TailPoller(taskFeed, taskFeedPublisher, undefined, undefined, (line) => this.logger.log(line), sessionId);
4968
+ return { taskFeed, taskFeedPublisher, tailPoller };
4787
4969
  }
4788
4970
  /** Cleanly tear down a session: cancel in-flight work, release stream
4789
4971
  * resources, and remove it from the session map. */
@@ -8646,7 +8828,9 @@ export function runAcp(logger) {
8646
8828
  .onNotification(methods.agent.session.cancel, (ctx) => agent.cancel(ctx.params))
8647
8829
  .onRequest(STEER_METHOD, { parse: parseSteerRequest }, (ctx) => agent.steer(ctx.params))
8648
8830
  .onRequest(TASKS_STOP_METHOD, { parse: parseTasksStopRequest }, (ctx) => agent.stopTask(ctx.params))
8831
+ .onRequest(TASKS_OUTPUT_METHOD, { parse: parseTasksOutputRequest }, (ctx) => agent.taskOutput(ctx.params))
8649
8832
  .onRequest(TASKS_BACKGROUND_METHOD, { parse: parseTasksBackgroundRequest }, (ctx) => agent.backgroundTask(ctx.params))
8833
+ .onRequest(USAGE_REFRESH_METHOD, { parse: parseUsageRefreshRequest }, (ctx) => agent.refreshUsage(ctx.params))
8650
8834
  .onRequest(GOAL_CONTROL_METHOD, { parse: parseGoalRequest }, (ctx) => agent.goal(ctx.params))
8651
8835
  .connect(stream);
8652
8836
  agent = new ClaudeAcpAgent(new ClientConnection(connection.client), logger);
@@ -8821,3 +9005,58 @@ function getMatchingModelUsage(modelUsage, currentModel) {
8821
9005
  return { key: bestKey, usage: modelUsage[bestKey] };
8822
9006
  }
8823
9007
  }
9008
+ /**
9009
+ * Story 041 (R1.1, R1.6): a backgrounded Bash call's result names the file the
9010
+ * CLI writes its output to. When the structured `BashOutput` carries
9011
+ * `backgroundTaskId` and the text names exactly `<that id>.output` by an
9012
+ * absolute path, the path is recorded for the task's tail. Anything else
9013
+ * records nothing, so the task gets no tail. The rendered tool content is not
9014
+ * touched.
9015
+ */
9016
+ function observeBackgroundBashResult(message, content, session) {
9017
+ if (message.type !== "user" || !session.taskFeed || !Array.isArray(content))
9018
+ return;
9019
+ const result = message.tool_use_result;
9020
+ if (typeof result !== "object" || result === null || !("backgroundTaskId" in result))
9021
+ return;
9022
+ const taskId = result.backgroundTaskId;
9023
+ if (typeof taskId !== "string" || taskId === "")
9024
+ return;
9025
+ const blocks = content.filter((c) => typeof c === "object" && c !== null && c.type === "tool_result");
9026
+ // `tool_use_result` describes "the" tool_result of its message: only one.
9027
+ if (blocks.length !== 1)
9028
+ return;
9029
+ const [block] = blocks;
9030
+ if (session.toolUseCache[block.tool_use_id]?.name !== "Bash")
9031
+ return;
9032
+ const text = typeof block.content === "string"
9033
+ ? block.content
9034
+ : Array.isArray(block.content)
9035
+ ? block.content
9036
+ .filter((c) => typeof c === "object" &&
9037
+ c !== null &&
9038
+ c.type === "text" &&
9039
+ typeof c.text === "string")
9040
+ .map((c) => c.text)
9041
+ .join("\n")
9042
+ : "";
9043
+ const path = outputPathFromBashResult(text, taskId);
9044
+ if (path === null)
9045
+ return;
9046
+ session.taskFeedPublisher?.notify(session.taskFeed.onOutputPath(taskId, path));
9047
+ session.tailPoller?.kick();
9048
+ }
9049
+ /**
9050
+ * Story 041 (R1.7): whether an ending shell task's output file reads, checked
9051
+ * BEFORE the ending is folded into the feed — once the feed holds the ending,
9052
+ * any publish (a throttled trailer, a poller tick) could send it while the
9053
+ * check is still pending, and with `hasOutput: false`. Returns null without
9054
+ * awaiting anything when there is no file to check, so every other ending
9055
+ * keeps its synchronous place in the SDK message loop.
9056
+ */
9057
+ async function endingOutputReadable(session, taskId, outputFile) {
9058
+ const path = session.taskFeed?.endingOutputPath(taskId, outputFile) ?? null;
9059
+ if (path === null || !session.tailPoller)
9060
+ return null;
9061
+ return session.tailPoller.checkOutput(path);
9062
+ }
@@ -27,10 +27,21 @@
27
27
  *
28
28
  * Neither class ever copies a command line (`SDKTaskStartedMessage.prompt`)
29
29
  * into an entry or a log line.
30
+ *
31
+ * Version 2 (story 041) adds `hasOutput` and `tail`. A shell task's output
32
+ * path — named by the Bash result while it runs, by `task_notification` when
33
+ * it ends — is kept beside the table, never on the wire. Once an ended shell
34
+ * task leaves the table, its path moves to a bounded registry so
35
+ * `_claude/tasks/output` can still serve it.
30
36
  */
31
37
  import type { SDKTaskNotificationMessage, SDKTaskProgressMessage, SDKTaskStartedMessage, SDKTaskUpdatedMessage } from "@anthropic-ai/claude-agent-sdk";
32
38
  /** The `session_info_update._meta` key the snapshot is published under. */
33
39
  export declare const TASKS_META_KEY = "_claude/tasks";
40
+ /** The `_claude/tasks` contract version advertised at `initialize`. Version 2
41
+ * adds `hasOutput`, `tail` and `_claude/tasks/output`. */
42
+ export declare const TASKS_CONTRACT_VERSION = 2;
43
+ /** Ended shell outputs still servable per session, most recent kept. */
44
+ export declare const ENDED_OUTPUTS_MAX = 256;
34
45
  export type TaskStatus = "running" | "completed" | "failed" | "stopped" | "interrupted";
35
46
  export type TaskUsage = {
36
47
  tokens: number;
@@ -51,6 +62,10 @@ export type TaskEntry = {
51
62
  usage: TaskUsage | null;
52
63
  lastTool: string | null;
53
64
  summary: string | null;
65
+ /** True when `_claude/tasks/output` can serve this task (version 2). */
66
+ hasOutput: boolean;
67
+ /** A running shell's last lines, sanitized; null otherwise (version 2). */
68
+ tail: string[] | null;
54
69
  };
55
70
  export type TasksSnapshot = {
56
71
  tasks: TaskEntry[];
@@ -97,6 +112,10 @@ export declare class TaskFeed {
97
112
  private readonly held;
98
113
  /** Shell task ids labelled by their tool call: a patch never relabels them. */
99
114
  private readonly toolDescribed;
115
+ /** Shell task id → its output file, while the task is in the table. */
116
+ private readonly outputs;
117
+ /** Ended shell outputs that left the table, oldest first; see `ENDED_OUTPUTS_MAX`. */
118
+ private readonly endedOutputs;
100
119
  constructor(clock: () => number, log?: TaskFeedLog, sessionId?: string);
101
120
  /**
102
121
  * `toolDescription` is the spawning tool call's own `description` input. A
@@ -126,6 +145,33 @@ export declare class TaskFeed {
126
145
  /** The CLI process restarted: whatever was running did not survive it. A
127
146
  * held shell task was never shown, so it is dropped rather than ended. */
128
147
  onProcessRestart(): TaskChange;
148
+ /**
149
+ * Records the output file a Bash result named for a known shell task. Any
150
+ * other task, or an unknown id, is ignored: only a shell writes an output
151
+ * file, and only one the adapter recorded is ever read (R2.5).
152
+ */
153
+ onOutputPath(taskId: string, path: string): TaskChange;
154
+ /**
155
+ * Sets a shell task's tail and whether its output reads. A running task
156
+ * keeps `lines`; an ended one keeps none, so this only confirms its output.
157
+ * Returns `"none"` when nothing changed, so an idle file publishes nothing.
158
+ */
159
+ setTail(taskId: string, lines: string[] | null, readable: boolean): TaskChange;
160
+ /**
161
+ * The file the ending's existence check reads (R1.7), or null when there is
162
+ * nothing to check: `taskId` must be a running shell in the table. A non-empty
163
+ * `output_file` from the ending (the CLI's own) wins over the path the Bash result named.
164
+ * The caller checks BEFORE folding the ending in, so the snapshot that
165
+ * carries the ending already says whether the output can be served.
166
+ */
167
+ endingOutputPath(taskId: string, outputFile?: string): string | null;
168
+ /** Running shell tasks with a recorded output file: the tail poller's work. */
169
+ runningOutputs(): {
170
+ taskId: string;
171
+ path: string;
172
+ }[];
173
+ /** The output file `_claude/tasks/output` may read for `taskId`, or null. */
174
+ outputPathFor(taskId: string): string | null;
129
175
  /** True when the feed holds `taskId` as running — the stop request's gate. */
130
176
  isRunning(taskId: string): boolean;
131
177
  /** Running tasks plus ended tasks not yet published, oldest start first.
@@ -148,6 +194,8 @@ export declare class TaskFeed {
148
194
  * the tool call is never replaced. */
149
195
  private patch;
150
196
  private end;
197
+ /** Moves a task that left the table to the ended registry, newest last. */
198
+ private retireOutput;
151
199
  }
152
200
  export type PublisherTimers = {
153
201
  now: () => number;
@@ -192,4 +240,47 @@ export declare class TaskFeedPublisher {
192
240
  private armHold;
193
241
  private armReconcile;
194
242
  }
243
+ /** The longest the ending's existence check may hold the SDK message loop. */
244
+ export declare const ENDING_CHECK_TIMEOUT_MS = 2000;
245
+ /** How often the tail poller looks at running shells' output files (R1.4). */
246
+ export declare const TAIL_POLL_INTERVAL_MS = 1000;
247
+ export type PollerTimers = {
248
+ setInterval: (fn: () => void, ms: number) => unknown;
249
+ clearInterval: (handle: any) => void;
250
+ };
251
+ /**
252
+ * Publishes a running shell task's tail (story 041, R1.4, R1.5). One interval
253
+ * while any shell output is known; each tick `stat`s every file and reads the
254
+ * tail only when its size changed, shrinking included. A read error publishes
255
+ * `tail: null, hasOutput: false` and logs once per task until the file reads
256
+ * again. A tick never overlaps the previous one, and no rejection escapes it:
257
+ * the interval runs outside every caller that could handle one, and Node exits
258
+ * on an unhandled rejection.
259
+ */
260
+ export declare class TailPoller {
261
+ private readonly feed;
262
+ private readonly publisher;
263
+ private readonly timers;
264
+ private readonly readTailFn;
265
+ private readonly log;
266
+ private readonly sessionId;
267
+ private interval;
268
+ private busy;
269
+ private closed;
270
+ /** task id → the file size the last published tail was read at. */
271
+ private readonly sizes;
272
+ /** Task ids whose file failed to read: logged once, until it reads again. */
273
+ private readonly failing;
274
+ constructor(feed: TaskFeed, publisher: TaskFeedPublisher, timers?: PollerTimers, readTailFn?: (path: string) => Promise<string[]>, log?: (line: string) => void, sessionId?: string);
275
+ /** Arms the interval when there is output to watch. Called after a path is recorded. */
276
+ kick(): void;
277
+ /**
278
+ * The ending's existence check (R1.7): true when `path` reads as a regular
279
+ * file within `ENDING_CHECK_TIMEOUT_MS`. Never rejects.
280
+ */
281
+ checkOutput(path: string): Promise<boolean>;
282
+ dispose(): void;
283
+ private stop;
284
+ private tick;
285
+ }
195
286
  //# sourceMappingURL=task-feed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-feed.d.ts","sourceRoot":"","sources":["../src/task-feed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,gCAAgC,CAAC;AAExC,2EAA2E;AAC3E,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAC;AAExF,MAAM,MAAM,SAAS,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAEnD,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;AAEnE,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;kEAEkE;AAClE,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AAExC,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,OAAQ,CAAC;AAEzC,MAAM,MAAM,WAAW,GAAG;IACxB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,oEAAoE;IACpE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAeF;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,IAAI,CAavC;AAmBD,qBAAa,QAAQ;IAkBjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAnB5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,kEAAkE;IAClE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAGhC,KAAK,EAAE,MAAM,MAAM,EACnB,GAAG,GAAE,WAAgB,EACrB,SAAS,SAAK;IAGjC;;;;OAIG;IACH,SAAS,CAAC,GAAG,EAAE,qBAAqB,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,UAAU;IAkC3E,8DAA8D;IAC9D,UAAU,IAAI,UAAU;IAYxB,oFAAoF;IACpF,WAAW,IAAI,MAAM,GAAG,IAAI;IAS5B,UAAU,CAAC,GAAG,EAAE,sBAAsB,GAAG,UAAU;IAWnD,SAAS,CAAC,GAAG,EAAE,qBAAqB,GAAG,UAAU;IA2BjD,cAAc,CAAC,GAAG,EAAE,0BAA0B,GAAG,UAAU;IAa3D;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,UAAU;IAWjD,eAAe,IAAI,MAAM,GAAG,IAAI;IAMhC,8EAA8E;IAC9E,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU;IAiBlC;+EAC2E;IAC3E,gBAAgB,IAAI,UAAU;IAW9B,8EAA8E;IAC9E,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlC;8CAC0C;IAC1C,QAAQ,IAAI;QAAE,QAAQ,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE;IAU3D;mFAC+E;IAC/E,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;IAYvC,OAAO,CAAC,OAAO;IASf;;;;kCAI8B;IAC9B,OAAO,CAAC,KAAK;IAMb;2CACuC;IACvC,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,GAAG;CAKZ;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACpD,YAAY,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CACrC,CAAC;AAQF,qBAAa,iBAAiB;IAqB1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAvB9B,OAAO,CAAC,UAAU,CAA4B;IAC9C;kEAC8D;IAC9D,OAAO,CAAC,QAAQ,CAAS;IACzB;iCAC6B;IAC7B,OAAO,CAAC,MAAM,CAAsB;IACpC,0EAA0E;IAC1E,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,WAAW,CAAuB;IAC1C,4EAA4E;IAC5E,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB;2EACuE;IACvE,OAAO,CAAC,aAAa,CAAS;gBAGX,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,EAChD,MAAM,GAAE,eAA4B,EACpC,WAAW,GAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAe;IAGnE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAchC,OAAO,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IAYhD,OAAO,CAAC,UAAU;IAgClB,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,GAAG;IAOX,OAAO,CAAC,aAAa;IAKrB;;+CAE2C;IAC3C,OAAO,CAAC,OAAO;IA4Bf,OAAO,CAAC,YAAY;CAerB"}
1
+ {"version":3,"file":"task-feed.d.ts","sourceRoot":"","sources":["../src/task-feed.ts"],"names":[],"mappings":"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"}