@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/README.md +17 -4
- package/dist/acp-agent.d.ts +109 -10
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +259 -20
- package/dist/task-feed.d.ts +91 -0
- package/dist/task-feed.d.ts.map +1 -1
- package/dist/task-feed.js +260 -2
- package/dist/task-output.d.ts +32 -0
- package/dist/task-output.d.ts.map +1 -0
- package/dist/task-output.js +135 -0
- package/package.json +1 -1
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
|
-
/**
|
|
146
|
-
*
|
|
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
|
-
|
|
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 =
|
|
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
|
-
* `
|
|
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
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
|
|
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 =
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/task-feed.d.ts
CHANGED
|
@@ -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
|
package/dist/task-feed.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-feed.d.ts","sourceRoot":"","sources":["../src/task-feed.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"task-feed.d.ts","sourceRoot":"","sources":["../src/task-feed.ts"],"names":[],"mappings":"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"}
|