@khorsheed/dsh-local-agent-claude-code 0.2.0-rc.3 → 0.2.1
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/CHANGELOG.md +25 -1
- package/README.en.md +24 -112
- package/README.i18n.yaml +2 -2
- package/README.md +24 -108
- package/lib/client.js +157 -72
- package/lib/index.js +531 -186
- package/lib/types/claude-cli-provider.d.ts +73 -2
- package/lib/types/claude-cli-provider.js +330 -97
- package/lib/types/client/SettingsCard.js +11 -3
- package/lib/types/client/icons.d.ts +1 -1
- package/lib/types/client/index.js +2 -2
- package/lib/types/endpoint-auth.js +17 -1
- package/lib/types/index.js +4 -2
- package/lib/types/live-driver.d.ts +0 -5
- package/lib/types/live-driver.js +259 -101
- package/lib/types/model-broker.js +44 -2
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -192,6 +192,20 @@ function servedEfforts(effort) {
|
|
|
192
192
|
return levels.length === 0 ? void 0 : levels;
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
|
+
* The effort block's home in a served-model entry. Anthropic's current
|
|
196
|
+
* /v1/models shape nests it under `capabilities.effort` (verified against the
|
|
197
|
+
* openproxy relay 2026-10-22: `capabilities.effort.<level>.supported`); the
|
|
198
|
+
* flat top-level `effort` the parser was written against still wins when
|
|
199
|
+
* present, so either shape answers.
|
|
200
|
+
*/
|
|
201
|
+
function servedEffortsOf(record) {
|
|
202
|
+
const direct = servedEfforts(record["effort"]);
|
|
203
|
+
if (direct !== void 0) return direct;
|
|
204
|
+
const capabilities = record["capabilities"];
|
|
205
|
+
if (typeof capabilities !== "object" || capabilities === null) return void 0;
|
|
206
|
+
return servedEfforts(capabilities["effort"]);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
195
209
|
* `GET <baseUrl>/v1/models` with the resolved credential's auth header(s) —
|
|
196
210
|
* the token-free endpoint probe shared by the `/claude-code check` verdict
|
|
197
211
|
* and the model catalog. Anthropic's own API serves this route with
|
|
@@ -237,7 +251,7 @@ async function fetchEndpointModels(endpoint, fetchImpl, signal) {
|
|
|
237
251
|
if (typeof entry !== "object" || entry === null) continue;
|
|
238
252
|
const record = entry;
|
|
239
253
|
if (typeof record.id !== "string" || record.id === "") continue;
|
|
240
|
-
const efforts =
|
|
254
|
+
const efforts = servedEffortsOf(record);
|
|
241
255
|
models.push({
|
|
242
256
|
id: record.id,
|
|
243
257
|
...typeof record.display_name === "string" && record.display_name !== "" ? { displayName: record.display_name } : {},
|
|
@@ -528,8 +542,9 @@ var ClaudeControlRequests = class {
|
|
|
528
542
|
* Fold: the resident stream speaks the exec path's exact event vocabulary
|
|
529
543
|
* (`system`/`assistant`/`user`/`result`), so each turn folds through the
|
|
530
544
|
* shared `ClaudeStreamParser` with the exec live mirror's hold-back rule (the
|
|
531
|
-
* volatile last line waits for `result`, which carries the round's usage
|
|
532
|
-
*
|
|
545
|
+
* volatile last line waits for `result`, which carries the round's usage, and
|
|
546
|
+
* an unresulted tool line holds the fold until its result merges in place —
|
|
547
|
+
* the native lifecycle folds as one closed step). Every line folds 1:1. The always-enabled
|
|
533
548
|
* `--include-partial-messages` adds incremental output on top: each `stream_event` partial accumulates into its kind's
|
|
534
549
|
* stream (native message id + block index, with ordered legacy fallback),
|
|
535
550
|
* which reserves a (turn, step) at its first delta and
|
|
@@ -570,11 +585,6 @@ const DEFAULT_LIVE_CHANNEL_RETRY_MS = 3e5;
|
|
|
570
585
|
const RECLAIM_EOF_GRACE_MS = 1e3;
|
|
571
586
|
/** Default minimum interval between one streaming kind's snapshot messages. */
|
|
572
587
|
const DEFAULT_SNAPSHOT_MIN_INTERVAL_MS = LIVE_FLUSH_INTERVAL_MS;
|
|
573
|
-
/**
|
|
574
|
-
* The stream-json channel could not come up (spawn failure or the first
|
|
575
|
-
* turn's init never arrived). The provider catches exactly this and falls
|
|
576
|
-
* back to the exec one-shot; any other error is a real round failure.
|
|
577
|
-
*/
|
|
578
588
|
var LiveChannelUnavailableError = class extends Error {
|
|
579
589
|
constructor(message) {
|
|
580
590
|
super(message);
|
|
@@ -927,6 +937,7 @@ var ClaudeLiveDriver = class {
|
|
|
927
937
|
graceMs: DEFAULT_DISPOSE_GRACE_MS,
|
|
928
938
|
env: delegationEnv({
|
|
929
939
|
CLAUDE_CONFIG_DIR: spec.homeDir,
|
|
940
|
+
...member === void 0 ? {} : { MCP_TOOL_TIMEOUT: MEMBER_TOOL_TIMEOUT_MS },
|
|
930
941
|
...spec.configuration === void 0 ? {} : { CLAUDE_CODE_EFFORT_LEVEL: spec.configuration.effort ?? "auto" },
|
|
931
942
|
...endpointAuthEnv(endpoint)
|
|
932
943
|
})
|
|
@@ -1022,27 +1033,22 @@ var ClaudeLiveDriver = class {
|
|
|
1022
1033
|
*/
|
|
1023
1034
|
let roundModel;
|
|
1024
1035
|
let roundCliVersion;
|
|
1025
|
-
/** Whether a folded line already
|
|
1036
|
+
/** Whether a folded line already carried usage (per-call inline, or the round's total as the fallback — no backfill event exists). */
|
|
1026
1037
|
let usageCarried = false;
|
|
1027
1038
|
/**
|
|
1028
|
-
* The
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1034
|
-
|
|
1035
|
-
const reservedSteps = [];
|
|
1036
|
-
/**
|
|
1037
|
-
* The streaming kinds seen this round by stream key:
|
|
1038
|
-
* deltas accumulate into throttled snapshot assistant/messages appended
|
|
1039
|
-
* at the kind's reserved (turn, step) — the host folds repeated settles
|
|
1040
|
-
* at one coordinate into one live-updating chat node, which is the only
|
|
1041
|
-
* streaming channel left after 0.1.5 retired the durable per-chunk event.
|
|
1042
|
-
* The kind's completed line folds at the same step and finalizes it; an
|
|
1043
|
-
* entry whose completion never arrives is force-finalized at settle.
|
|
1039
|
+
* The streaming blocks seen this round by stream key: deltas accumulate
|
|
1040
|
+
* into throttled snapshots at the block's step once it may open in the
|
|
1041
|
+
* log (previewed live before that) — the host folds repeated settles at
|
|
1042
|
+
* one coordinate into one live-updating chat node. The block's completed
|
|
1043
|
+
* line folds at the same step and finalizes it; an entry whose
|
|
1044
|
+
* completion never arrives is force-finalized at settle. A tool that
|
|
1045
|
+
* completes mid-stream folds after the stream (chronological order).
|
|
1044
1046
|
*/
|
|
1045
1047
|
const streams = /* @__PURE__ */ new Map();
|
|
1048
|
+
/** Provisional stream keys (live flush and preview identity), never log steps. */
|
|
1049
|
+
let streamKeys = 0;
|
|
1050
|
+
/** Line indexes already written (a line may fold ahead of one it chronologically precedes). */
|
|
1051
|
+
const folded = /* @__PURE__ */ new Set();
|
|
1046
1052
|
/** The stream deltas currently accumulate into (kinds stream sequentially). */
|
|
1047
1053
|
let activeStream;
|
|
1048
1054
|
let persistQueue = Promise.resolve();
|
|
@@ -1072,6 +1078,34 @@ var ClaudeLiveDriver = class {
|
|
|
1072
1078
|
};
|
|
1073
1079
|
let messageKey;
|
|
1074
1080
|
let legacyMessage = 0;
|
|
1081
|
+
const callUsage = /* @__PURE__ */ new Map();
|
|
1082
|
+
const openCalls = /* @__PURE__ */ new Set();
|
|
1083
|
+
let currentCall;
|
|
1084
|
+
const endCall = (usage) => {
|
|
1085
|
+
if (currentCall === void 0) return;
|
|
1086
|
+
if (typeof usage === "object" && usage !== null) {
|
|
1087
|
+
const merged = { ...callUsage.get(currentCall) };
|
|
1088
|
+
for (const [key, value] of Object.entries(usage)) if (value !== null && value !== void 0) merged[key] = value;
|
|
1089
|
+
callUsage.set(currentCall, merged);
|
|
1090
|
+
}
|
|
1091
|
+
openCalls.delete(currentCall);
|
|
1092
|
+
};
|
|
1093
|
+
/**
|
|
1094
|
+
* A line's usage when its call is known: `skip` on every line but the
|
|
1095
|
+
* call's last, the call's final usage (or, without stream events, the
|
|
1096
|
+
* line's own report) on the last; `null` for an id-less line.
|
|
1097
|
+
*/
|
|
1098
|
+
const callLineUsage = (index) => {
|
|
1099
|
+
const id = parser.lines[index]?.messageId;
|
|
1100
|
+
if (id === void 0) return null;
|
|
1101
|
+
let last = -1;
|
|
1102
|
+
parser.lines.forEach((line, position) => {
|
|
1103
|
+
if (line.messageId === id) last = position;
|
|
1104
|
+
});
|
|
1105
|
+
if (index !== last) return { skip: true };
|
|
1106
|
+
const raw = callUsage.get(id);
|
|
1107
|
+
return { usage: raw !== void 0 ? usageFromClaude(raw) : parser.lines[index].usage };
|
|
1108
|
+
};
|
|
1075
1109
|
const streamKey = (kind, index) => `${messageKey ?? `legacy-${turn}-${legacyMessage}`}:${typeof index === "number" ? index : kind}`;
|
|
1076
1110
|
/**
|
|
1077
1111
|
* Append one snapshot of a streaming kind at its reserved (turn, step).
|
|
@@ -1082,6 +1116,27 @@ var ClaudeLiveDriver = class {
|
|
|
1082
1116
|
*/
|
|
1083
1117
|
const streamPublisher = localAgent?.liveStreams === void 0 ? void 0 : new LiveStreamPublisher(localAgent.liveStreams, childSession, turn, persist, (error) => this.ctx.logger.warn(`live checkpoint failed: ${String(error)}`));
|
|
1084
1118
|
const liveFlush = new LiveFlush((error) => this.ctx.logger.warn(`live mirror flush failed: ${String(error)}`), this.config.snapshotMinIntervalMs ?? DEFAULT_SNAPSHOT_MIN_INTERVAL_MS);
|
|
1119
|
+
let lastStep = 0;
|
|
1120
|
+
let openStep;
|
|
1121
|
+
const openStreamStep = (stream) => {
|
|
1122
|
+
lastStep += 1;
|
|
1123
|
+
childSession.append("step/start", {
|
|
1124
|
+
turn,
|
|
1125
|
+
step: lastStep
|
|
1126
|
+
});
|
|
1127
|
+
stream.durable = lastStep;
|
|
1128
|
+
stream.opened = true;
|
|
1129
|
+
openStep = lastStep;
|
|
1130
|
+
};
|
|
1131
|
+
const closeOpenStep = () => {
|
|
1132
|
+
if (openStep === void 0) return;
|
|
1133
|
+
childSession.append("step/end", {
|
|
1134
|
+
turn,
|
|
1135
|
+
step: openStep
|
|
1136
|
+
});
|
|
1137
|
+
openStep = void 0;
|
|
1138
|
+
};
|
|
1139
|
+
const canOpenStream = (stream) => openStep === void 0 && mirrored >= stream.after && streams.keys().next().value === stream.itemId;
|
|
1085
1140
|
const pendingStreamArrival = /* @__PURE__ */ new Map();
|
|
1086
1141
|
const appendStreamSnapshot = (stream, force, interrupted, withUsage = false, final = false) => {
|
|
1087
1142
|
if (stream.text.trim() === "") return;
|
|
@@ -1094,30 +1149,30 @@ var ClaudeLiveDriver = class {
|
|
|
1094
1149
|
liveFlush.cancel(stream.step);
|
|
1095
1150
|
const receivedAt = pendingStreamArrival.get(stream.step) ?? now;
|
|
1096
1151
|
pendingStreamArrival.delete(stream.step);
|
|
1097
|
-
if (
|
|
1098
|
-
if (
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1152
|
+
if (!stream.opened) {
|
|
1153
|
+
if (canOpenStream(stream)) openStreamStep(stream);
|
|
1154
|
+
else if (!final) {
|
|
1155
|
+
streamPublisher?.preview(stream.step, stream.kind, stream.text, {
|
|
1156
|
+
itemId: stream.itemId,
|
|
1157
|
+
receivedAt
|
|
1102
1158
|
});
|
|
1103
|
-
|
|
1159
|
+
return;
|
|
1160
|
+
} else {
|
|
1161
|
+
closeOpenStep();
|
|
1162
|
+
openStreamStep(stream);
|
|
1104
1163
|
}
|
|
1105
|
-
|
|
1164
|
+
}
|
|
1165
|
+
if (streamPublisher !== void 0 && !final) {
|
|
1166
|
+
streamPublisher.update(stream.durable, stream.kind, stream.text, { receivedAt });
|
|
1106
1167
|
stream.lastSnapshotAt = now;
|
|
1107
1168
|
stream.lastSnapshotLen = stream.text.length;
|
|
1108
1169
|
return;
|
|
1109
1170
|
}
|
|
1110
|
-
if (!stream.opened) {
|
|
1111
|
-
childSession.append("step/start", {
|
|
1112
|
-
turn,
|
|
1113
|
-
step: stream.step
|
|
1114
|
-
});
|
|
1115
|
-
stream.opened = true;
|
|
1116
|
-
}
|
|
1117
1171
|
const usage = parser.usage;
|
|
1172
|
+
const totalFallback = withUsage && !usageCarried && usage !== void 0 && !parser.lines.some((line) => line.usage !== void 0);
|
|
1118
1173
|
childSession.append("assistant/message", {
|
|
1119
1174
|
turn,
|
|
1120
|
-
step: stream.
|
|
1175
|
+
step: stream.durable,
|
|
1121
1176
|
message: assistantEvent([stream.kind === "think" ? {
|
|
1122
1177
|
type: "reasoning",
|
|
1123
1178
|
text: stream.text
|
|
@@ -1126,19 +1181,26 @@ var ClaudeLiveDriver = class {
|
|
|
1126
1181
|
text: stream.text
|
|
1127
1182
|
}]),
|
|
1128
1183
|
stream: [],
|
|
1129
|
-
...
|
|
1184
|
+
...totalFallback ? { usage } : {},
|
|
1130
1185
|
...interrupted ? { interrupted: true } : {}
|
|
1131
1186
|
}, { surfaceOp: "append" });
|
|
1132
|
-
if (
|
|
1187
|
+
if (totalFallback) usageCarried = true;
|
|
1133
1188
|
stream.lastSnapshotAt = now;
|
|
1134
1189
|
stream.lastSnapshotLen = stream.text.length;
|
|
1135
1190
|
persist();
|
|
1136
1191
|
};
|
|
1192
|
+
/** Close a stream's live presentation once its final text is in the log. */
|
|
1193
|
+
const finishStream = (stream) => {
|
|
1194
|
+
liveFlush.cancel(stream.step);
|
|
1195
|
+
streams.delete(stream.itemId);
|
|
1196
|
+
if (activeStream === stream.itemId) activeStream = void 0;
|
|
1197
|
+
streamPublisher?.finish(stream.step);
|
|
1198
|
+
if (stream.durable !== void 0) streamPublisher?.finish(stream.durable);
|
|
1199
|
+
};
|
|
1137
1200
|
/**
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
* completion still folds at its own reserved step when it lands).
|
|
1201
|
+
* Track one new streaming block: its provisional key and how many lines
|
|
1202
|
+
* had completed when it began. A different block's deltas force one
|
|
1203
|
+
* freshness snapshot of the current stream.
|
|
1142
1204
|
*/
|
|
1143
1205
|
const reserveStream = (itemId, kind) => {
|
|
1144
1206
|
if (activeStream !== void 0 && activeStream !== itemId) {
|
|
@@ -1148,12 +1210,12 @@ var ClaudeLiveDriver = class {
|
|
|
1148
1210
|
}
|
|
1149
1211
|
let stream = streams.get(itemId);
|
|
1150
1212
|
if (stream === void 0) {
|
|
1151
|
-
|
|
1152
|
-
reservedSteps.push(step);
|
|
1213
|
+
streamKeys += 1;
|
|
1153
1214
|
stream = {
|
|
1154
1215
|
itemId,
|
|
1155
1216
|
kind,
|
|
1156
|
-
step,
|
|
1217
|
+
step: streamKeys,
|
|
1218
|
+
after: parser.lines.length,
|
|
1157
1219
|
text: "",
|
|
1158
1220
|
lastSnapshotAt: 0,
|
|
1159
1221
|
lastSnapshotLen: 0,
|
|
@@ -1163,60 +1225,90 @@ var ClaudeLiveDriver = class {
|
|
|
1163
1225
|
}
|
|
1164
1226
|
activeStream = itemId;
|
|
1165
1227
|
};
|
|
1166
|
-
/** The
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1228
|
+
/** The stream a completed line finalizes, if it was streamed. */
|
|
1229
|
+
const streamOfLine = (line) => line === void 0 || line.kind === "tool" ? void 0 : (line.streamId === void 0 ? void 0 : streams.get(line.streamId)) ?? [...streams.values()].find((candidate) => candidate.kind === line.kind && (line.streamId === void 0 || candidate.itemId.startsWith("legacy-")));
|
|
1230
|
+
/** Write parser.lines[index] at the next step(s). */
|
|
1231
|
+
const foldLine = (index, lineUsage) => {
|
|
1232
|
+
const line = parser.lines[index];
|
|
1233
|
+
const perCall = callLineUsage(index);
|
|
1234
|
+
const effectiveUsage = perCall === null ? line.usage ?? lineUsage : "skip" in perCall ? void 0 : perCall.usage ?? lineUsage;
|
|
1235
|
+
if (effectiveUsage !== void 0) usageCarried = true;
|
|
1236
|
+
const stream = streamOfLine(line);
|
|
1237
|
+
if (stream !== void 0) {
|
|
1238
|
+
if (!stream.opened) {
|
|
1239
|
+
closeOpenStep();
|
|
1240
|
+
openStreamStep(stream);
|
|
1241
|
+
}
|
|
1242
|
+
const blocks = stream.kind === "think" ? [{
|
|
1243
|
+
type: "reasoning",
|
|
1244
|
+
text: line.kind === "tool" ? "" : line.text
|
|
1245
|
+
}] : [{
|
|
1246
|
+
type: "text",
|
|
1247
|
+
text: line.kind === "tool" ? "" : line.text
|
|
1248
|
+
}];
|
|
1249
|
+
childSession.append("assistant/message", {
|
|
1250
|
+
turn,
|
|
1251
|
+
step: stream.durable,
|
|
1252
|
+
message: assistantEvent(blocks),
|
|
1253
|
+
stream: [],
|
|
1254
|
+
...effectiveUsage === void 0 ? {} : { usage: effectiveUsage }
|
|
1255
|
+
}, { surfaceOp: "append" });
|
|
1256
|
+
finishStream(stream);
|
|
1257
|
+
closeOpenStep();
|
|
1258
|
+
} else {
|
|
1259
|
+
closeOpenStep();
|
|
1260
|
+
lastStep += 1;
|
|
1261
|
+
const { usage: _snapshot, ...bare } = line;
|
|
1262
|
+
appendClaudeTranscriptLine(childSession, turn, lastStep, bare, effectiveUsage);
|
|
1263
|
+
}
|
|
1264
|
+
folded.add(index);
|
|
1265
|
+
localAgent?.reportRunProgress(childSession.id, {
|
|
1266
|
+
kind: "delta",
|
|
1267
|
+
text: claudeLineText(line)
|
|
1268
|
+
});
|
|
1172
1269
|
};
|
|
1173
|
-
/**
|
|
1174
|
-
|
|
1270
|
+
/**
|
|
1271
|
+
* Mirror the completed lines before `upto` in chronological order; the
|
|
1272
|
+
* last line is held back until `result`. A line waits behind every stream
|
|
1273
|
+
* that began before it completed (that stream's line folds first, or the
|
|
1274
|
+
* mirror stops while it still streams). A tool line whose result the
|
|
1275
|
+
* stream has not recorded yet HOLDS the fold mid-round: the fold writes
|
|
1276
|
+
* the native lifecycle as one closed step (the released-log reader
|
|
1277
|
+
* resolves every advertised call before step/end), and the result merges
|
|
1278
|
+
* into the call's line in place, so a later pass retries from here. The
|
|
1279
|
+
* settle flushes (`settle: true` — the turn's result event, or the final
|
|
1280
|
+
* flush after a cancel/failure) never hold: a result that will never
|
|
1281
|
+
* arrive folds as the interrupted placeholder.
|
|
1282
|
+
*/
|
|
1283
|
+
const mirrorUpTo = (upto, withUsage, settle = false) => {
|
|
1175
1284
|
let usageIndex = -1;
|
|
1176
|
-
if (withUsage) {
|
|
1285
|
+
if (withUsage && !parser.lines.some((line) => line.usage !== void 0)) {
|
|
1177
1286
|
for (let index = 0; index < parser.lines.length; index += 1) if (parser.lines[index]?.kind !== "tool") usageIndex = index;
|
|
1178
1287
|
}
|
|
1179
|
-
|
|
1180
|
-
const line = parser.lines[index];
|
|
1181
|
-
if (line === void 0) continue;
|
|
1288
|
+
const usageFor = (index) => {
|
|
1182
1289
|
const lineUsage = withUsage && index === usageIndex ? parser.usage : void 0;
|
|
1183
1290
|
if (lineUsage !== void 0) usageCarried = true;
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
step: stream.step,
|
|
1204
|
-
message: assistantEvent(blocks),
|
|
1205
|
-
stream: [],
|
|
1206
|
-
...lineUsage === void 0 ? {} : { usage: lineUsage }
|
|
1207
|
-
}, { surfaceOp: "append" });
|
|
1208
|
-
streamPublisher?.finish(stream.step);
|
|
1209
|
-
childSession.append("step/end", {
|
|
1210
|
-
turn,
|
|
1211
|
-
step: stream.step
|
|
1212
|
-
});
|
|
1213
|
-
} else appendClaudeTranscriptLine(childSession, turn, foldStep(index), line, lineUsage);
|
|
1214
|
-
mirrored = index + 1;
|
|
1215
|
-
localAgent?.reportRunProgress(childSession.id, {
|
|
1216
|
-
kind: "delta",
|
|
1217
|
-
text: claudeLineText(line)
|
|
1218
|
-
});
|
|
1291
|
+
return lineUsage;
|
|
1292
|
+
};
|
|
1293
|
+
outer: for (let index = mirrored; index < upto; index += 1) {
|
|
1294
|
+
if (folded.has(index)) continue;
|
|
1295
|
+
const line = parser.lines[index];
|
|
1296
|
+
if (line === void 0) continue;
|
|
1297
|
+
if (!settle && line.kind === "tool" && line.result === void 0) break;
|
|
1298
|
+
if (!settle && line.messageId !== void 0 && openCalls.has(line.messageId)) break;
|
|
1299
|
+
const own = streamOfLine(line);
|
|
1300
|
+
const all = [...streams.values()];
|
|
1301
|
+
const before = own === void 0 ? all.filter((stream) => stream.after <= index) : all.slice(0, all.indexOf(own));
|
|
1302
|
+
for (const earlier of before) {
|
|
1303
|
+
const at = parser.lines.findIndex((candidate, position) => position > index && position < upto && !folded.has(position) && streamOfLine(candidate) === earlier);
|
|
1304
|
+
if (at < 0) break outer;
|
|
1305
|
+
const atCall = parser.lines[at]?.messageId;
|
|
1306
|
+
if (!settle && atCall !== void 0 && openCalls.has(atCall)) break outer;
|
|
1307
|
+
foldLine(at, usageFor(at));
|
|
1308
|
+
}
|
|
1309
|
+
foldLine(index, usageFor(index));
|
|
1219
1310
|
}
|
|
1311
|
+
while (folded.has(mirrored)) mirrored += 1;
|
|
1220
1312
|
if (upto > 0) persist();
|
|
1221
1313
|
};
|
|
1222
1314
|
let resolveResult;
|
|
@@ -1232,6 +1324,13 @@ var ClaudeLiveDriver = class {
|
|
|
1232
1324
|
/** The round's event sink (installed on the runtime while it runs). */
|
|
1233
1325
|
const onEvent = (event) => {
|
|
1234
1326
|
const type = event["type"];
|
|
1327
|
+
if (roundSettled) {
|
|
1328
|
+
if (type === "result") {
|
|
1329
|
+
awaitingResult = false;
|
|
1330
|
+
resolveResult(event);
|
|
1331
|
+
}
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1235
1334
|
if (type === "system" && typeof event["session_id"] === "string") {
|
|
1236
1335
|
if (runtime !== void 0 && runtime.sessionId === void 0) {
|
|
1237
1336
|
runtime.sessionId = event["session_id"];
|
|
@@ -1244,7 +1343,7 @@ var ClaudeLiveDriver = class {
|
|
|
1244
1343
|
if (type === "result") {
|
|
1245
1344
|
awaitingResult = false;
|
|
1246
1345
|
parser.push(JSON.stringify(event) + "\n");
|
|
1247
|
-
mirrorUpTo(parser.lines.length, true);
|
|
1346
|
+
mirrorUpTo(parser.lines.length, true, true);
|
|
1248
1347
|
resolveResult(event);
|
|
1249
1348
|
return;
|
|
1250
1349
|
}
|
|
@@ -1253,6 +1352,17 @@ var ClaudeLiveDriver = class {
|
|
|
1253
1352
|
if (inner?.["type"] === "message_start") {
|
|
1254
1353
|
const message = inner["message"];
|
|
1255
1354
|
messageKey = typeof message?.["id"] === "string" ? message["id"] : `legacy-${turn}-${++legacyMessage}`;
|
|
1355
|
+
currentCall = typeof message?.["id"] === "string" ? message["id"] : void 0;
|
|
1356
|
+
if (currentCall !== void 0) {
|
|
1357
|
+
openCalls.add(currentCall);
|
|
1358
|
+
const usage = message?.["usage"];
|
|
1359
|
+
if (typeof usage === "object" && usage !== null) callUsage.set(currentCall, { ...usage });
|
|
1360
|
+
}
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (inner?.["type"] === "message_delta" || inner?.["type"] === "message_stop") {
|
|
1364
|
+
endCall(inner["usage"]);
|
|
1365
|
+
mirrorUpTo(parser.lines.length - 1, false);
|
|
1256
1366
|
return;
|
|
1257
1367
|
}
|
|
1258
1368
|
const delta = inner?.["delta"];
|
|
@@ -1328,6 +1438,11 @@ var ClaudeLiveDriver = class {
|
|
|
1328
1438
|
throw new Error("subagent-claude: run cancelled locally");
|
|
1329
1439
|
}
|
|
1330
1440
|
childSession.append("turn/start", { turn });
|
|
1441
|
+
if (spec.configuration?.model !== void 0) appendClaudeRequestHeader(childSession, {
|
|
1442
|
+
provider: "claude-local",
|
|
1443
|
+
model: spec.configuration.model,
|
|
1444
|
+
reasoningEffort: spec.configuration.effort ?? "auto"
|
|
1445
|
+
});
|
|
1331
1446
|
childSession.append("user/message", createUserMessage({
|
|
1332
1447
|
content: [{
|
|
1333
1448
|
type: "text",
|
|
@@ -1379,16 +1494,20 @@ var ClaudeLiveDriver = class {
|
|
|
1379
1494
|
liveFlush.dispose();
|
|
1380
1495
|
turnInFlight = false;
|
|
1381
1496
|
if (turnOpened) {
|
|
1382
|
-
if (mirrored < parser.lines.length) mirrorUpTo(parser.lines.length, true);
|
|
1497
|
+
if (mirrored < parser.lines.length) mirrorUpTo(parser.lines.length, true, true);
|
|
1498
|
+
while (mirrored < parser.lines.length && streams.size > 0) {
|
|
1499
|
+
const oldest = streams.values().next().value;
|
|
1500
|
+
appendStreamSnapshot(oldest, true, settled.stopReason !== "completed", false, true);
|
|
1501
|
+
finishStream(oldest);
|
|
1502
|
+
closeOpenStep();
|
|
1503
|
+
mirrorUpTo(parser.lines.length, true, true);
|
|
1504
|
+
}
|
|
1383
1505
|
if (streams.size > 0) {
|
|
1384
1506
|
const remaining = [...streams.values()];
|
|
1385
1507
|
for (const [position, stream] of remaining.entries()) {
|
|
1386
1508
|
appendStreamSnapshot(stream, true, settled.stopReason !== "completed", position === remaining.length - 1, true);
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
turn,
|
|
1390
|
-
step: stream.step
|
|
1391
|
-
});
|
|
1509
|
+
finishStream(stream);
|
|
1510
|
+
closeOpenStep();
|
|
1392
1511
|
}
|
|
1393
1512
|
streams.clear();
|
|
1394
1513
|
activeStream = void 0;
|
|
@@ -1460,24 +1579,6 @@ var ClaudeLiveDriver = class {
|
|
|
1460
1579
|
});
|
|
1461
1580
|
}
|
|
1462
1581
|
};
|
|
1463
|
-
//#endregion
|
|
1464
|
-
//#region lib/types/claude-cli-provider.js
|
|
1465
|
-
/**
|
|
1466
|
-
* One-shot Claude Code CLI subagent lifecycle: spawn `claude -p "<task>"`
|
|
1467
|
-
* through the subprocess seam under the harness's scoped home, capture its
|
|
1468
|
-
* JSON result as the run output, and dispose to whole-tree quiescence.
|
|
1469
|
-
* Mirrors the kimi/codex providers: every accepted run is a fresh process
|
|
1470
|
-
* and a fresh claude session; there is no continuation across runs.
|
|
1471
|
-
*
|
|
1472
|
-
* The provider name `claude-local` avoids colliding with the official
|
|
1473
|
-
* `subagent-claude-code` package's provider name `claude-code` — a
|
|
1474
|
-
* composition that mounts both would fail loud with DUPLICATE_PROVIDER. The
|
|
1475
|
-
* tool row in this bundle's patch takes the official model-facing name
|
|
1476
|
-
* `subagent_claude_code` instead: the official preset row ships disabled,
|
|
1477
|
-
* and the patch disables it too (a deliberate user re-enable conflicts
|
|
1478
|
-
* loud, by design).
|
|
1479
|
-
* @module @khorsheed/dsh-local-agent-claude-code/claude-cli-provider
|
|
1480
|
-
*/
|
|
1481
1582
|
/** Default POSIX grace between subprocess termination tiers. */
|
|
1482
1583
|
const DEFAULT_DISPOSE_GRACE_MS = 3e3;
|
|
1483
1584
|
/**
|
|
@@ -1489,6 +1590,11 @@ const DEFAULT_DISPOSE_GRACE_MS = 3e3;
|
|
|
1489
1590
|
* provider-controlled error strings only.
|
|
1490
1591
|
*/
|
|
1491
1592
|
const CLAUDE_AUTH_FAILURE = /failed to authenticate|authentication_failed|oauth access token|invalid (x-api-key|api key|authentication token)|api error: 40[13]/i;
|
|
1593
|
+
/**
|
|
1594
|
+
* The MCP tool-call bound for a member's claude process (`MCP_TOOL_TIMEOUT`,
|
|
1595
|
+
* ms): a bridge call may wait up to 30 minutes for the human's answer.
|
|
1596
|
+
*/
|
|
1597
|
+
const MEMBER_TOOL_TIMEOUT_MS = String(18e5);
|
|
1492
1598
|
/** Register one member run with the channel; see {@link ClaudeMemberRunHandle}. */
|
|
1493
1599
|
function registerClaudeMemberRun(ctx, childSessionId, parentSessionId) {
|
|
1494
1600
|
const registry = ctx.localAgent;
|
|
@@ -1707,6 +1813,7 @@ var ClaudeCliProvider = class {
|
|
|
1707
1813
|
cwd,
|
|
1708
1814
|
env: delegationEnv({
|
|
1709
1815
|
CLAUDE_CONFIG_DIR: homeDir,
|
|
1816
|
+
...member === void 0 ? {} : { MCP_TOOL_TIMEOUT: MEMBER_TOOL_TIMEOUT_MS },
|
|
1710
1817
|
...endpointAuthEnv(endpoint)
|
|
1711
1818
|
}),
|
|
1712
1819
|
...exec === void 0 ? {} : { exec },
|
|
@@ -1796,6 +1903,7 @@ var ClaudeCliProvider = class {
|
|
|
1796
1903
|
cwd,
|
|
1797
1904
|
env: delegationEnv({
|
|
1798
1905
|
CLAUDE_CONFIG_DIR: homeDir,
|
|
1906
|
+
...member === void 0 ? {} : { MCP_TOOL_TIMEOUT: MEMBER_TOOL_TIMEOUT_MS },
|
|
1799
1907
|
...endpointAuthEnv(endpoint)
|
|
1800
1908
|
}),
|
|
1801
1909
|
...exec === void 0 ? {} : { exec },
|
|
@@ -1866,6 +1974,17 @@ function textTask(prompt) {
|
|
|
1866
1974
|
if (texts.every((text) => text.trim().length === 0)) throw new Error("subagent-claude: the one-shot task must not be empty");
|
|
1867
1975
|
return texts.join("\n");
|
|
1868
1976
|
}
|
|
1977
|
+
/** Group the lines of one API call (consecutive lines sharing a message id).
|
|
1978
|
+
* Lines without an id stay singleton units (the pre-grouping shape). */
|
|
1979
|
+
function groupClaudeTranscriptLines(lines) {
|
|
1980
|
+
const groups = [];
|
|
1981
|
+
for (const line of lines) {
|
|
1982
|
+
const last = groups[groups.length - 1];
|
|
1983
|
+
if (line.messageId !== void 0 && last !== void 0 && last[0]?.messageId === line.messageId) last.push(line);
|
|
1984
|
+
else groups.push([line]);
|
|
1985
|
+
}
|
|
1986
|
+
return groups;
|
|
1987
|
+
}
|
|
1869
1988
|
/**
|
|
1870
1989
|
* Fold a tool-call counter into the settled shape: `{count, byName}` with the
|
|
1871
1990
|
* per-name values summing to `count`. An empty map yields undefined — a
|
|
@@ -1978,11 +2097,17 @@ function foldClaudeStreamLine(state, raw) {
|
|
|
1978
2097
|
}
|
|
1979
2098
|
if (event.type !== "assistant" && event.type !== "user") return;
|
|
1980
2099
|
const blocks = event.message?.content ?? [];
|
|
2100
|
+
const linesBefore = state.lines.length;
|
|
2101
|
+
const messageId = event.type === "assistant" && typeof event.message?.id === "string" ? event.message.id : void 0;
|
|
1981
2102
|
for (const [blockIndex, block] of blocks.entries()) {
|
|
1982
2103
|
if (typeof block !== "object" || block === null) continue;
|
|
1983
2104
|
const record = block;
|
|
1984
|
-
const identity =
|
|
2105
|
+
const identity = messageId !== void 0 ? {
|
|
2106
|
+
streamId: `${messageId}:${blockIndex}`,
|
|
2107
|
+
messageId
|
|
2108
|
+
} : {};
|
|
1985
2109
|
const kind = record["type"];
|
|
2110
|
+
if (kind === "text" && event.type === "user") continue;
|
|
1986
2111
|
if (kind === "text" && typeof record["text"] === "string" && record["text"].trim() !== "") {
|
|
1987
2112
|
state.lines.push({
|
|
1988
2113
|
kind: "text",
|
|
@@ -2019,6 +2144,7 @@ function foldClaudeStreamLine(state, raw) {
|
|
|
2019
2144
|
id: id ?? `claude-tool-${state.lines.length}`,
|
|
2020
2145
|
name: "ApplyPatch",
|
|
2021
2146
|
args: edit.args,
|
|
2147
|
+
...messageId === void 0 ? {} : { messageId },
|
|
2022
2148
|
...edit.patch === void 0 ? {} : { result: edit.patch }
|
|
2023
2149
|
});
|
|
2024
2150
|
else {
|
|
@@ -2027,6 +2153,7 @@ function foldClaudeStreamLine(state, raw) {
|
|
|
2027
2153
|
kind: "tool",
|
|
2028
2154
|
id: id ?? `claude-tool-${state.lines.length}`,
|
|
2029
2155
|
name: kind === "server_tool_use" ? SERVER_TOOL_CARD_NAMES[name] ?? name : name,
|
|
2156
|
+
...messageId === void 0 ? {} : { messageId },
|
|
2030
2157
|
...detail === void 0 ? {} : { args: detail }
|
|
2031
2158
|
});
|
|
2032
2159
|
}
|
|
@@ -2038,6 +2165,22 @@ function foldClaudeStreamLine(state, raw) {
|
|
|
2038
2165
|
if (resultText !== void 0 && resultText.trim() !== "") attachToolResult(state, record["tool_use_id"], resultText, false);
|
|
2039
2166
|
}
|
|
2040
2167
|
}
|
|
2168
|
+
if (event.type === "assistant" && event.message?.usage !== void 0 && state.lines.length > linesBefore) {
|
|
2169
|
+
const lastIndex = state.lines.length - 1;
|
|
2170
|
+
const last = state.lines[lastIndex];
|
|
2171
|
+
if (messageId !== void 0) {
|
|
2172
|
+
const previous = state.usageStamped.get(messageId);
|
|
2173
|
+
if (previous !== void 0 && previous !== lastIndex) {
|
|
2174
|
+
const { usage: _cleared, ...rest } = state.lines[previous];
|
|
2175
|
+
state.lines[previous] = rest;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
state.lines[lastIndex] = {
|
|
2179
|
+
...last,
|
|
2180
|
+
usage: usageFromClaude(event.message.usage)
|
|
2181
|
+
};
|
|
2182
|
+
if (messageId !== void 0) state.usageStamped.set(messageId, lastIndex);
|
|
2183
|
+
}
|
|
2041
2184
|
}
|
|
2042
2185
|
/**
|
|
2043
2186
|
* Merge a tool result into its call line: pair by tool_use_id when the stream
|
|
@@ -2077,6 +2220,7 @@ var ClaudeStreamParser = class {
|
|
|
2077
2220
|
buffer = "";
|
|
2078
2221
|
lines = [];
|
|
2079
2222
|
callsById = /* @__PURE__ */ new Map();
|
|
2223
|
+
usageStamped = /* @__PURE__ */ new Map();
|
|
2080
2224
|
text;
|
|
2081
2225
|
usage;
|
|
2082
2226
|
sessionId;
|
|
@@ -2113,6 +2257,7 @@ function parseClaudeStreamJson(output) {
|
|
|
2113
2257
|
const state = {
|
|
2114
2258
|
lines: [],
|
|
2115
2259
|
callsById: /* @__PURE__ */ new Map(),
|
|
2260
|
+
usageStamped: /* @__PURE__ */ new Map(),
|
|
2116
2261
|
toolCalls: /* @__PURE__ */ new Map(),
|
|
2117
2262
|
text: void 0,
|
|
2118
2263
|
usage: void 0,
|
|
@@ -2259,16 +2404,30 @@ function serverToolResultText(record) {
|
|
|
2259
2404
|
return [url, body === void 0 ? void 0 : body.length <= WEB_FETCH_TEXT_LIMIT ? body : `${body.slice(0, WEB_FETCH_TEXT_LIMIT)}\n…`].filter((part) => part !== void 0 && part !== "").join("\n") || void 0;
|
|
2260
2405
|
}
|
|
2261
2406
|
}
|
|
2262
|
-
/**
|
|
2407
|
+
/**
|
|
2408
|
+
* Flatten a tool_result content payload (string or block array) to text.
|
|
2409
|
+
* Beyond `text` blocks, ToolSearch answers with `tool_reference` blocks — one
|
|
2410
|
+
* per discovered tool — which list as the found tool names; dropping them
|
|
2411
|
+
* stranded the call (no result recorded) and it settled as a phantom
|
|
2412
|
+
* interrupted failure.
|
|
2413
|
+
*/
|
|
2263
2414
|
function toolResultText(content) {
|
|
2264
2415
|
if (typeof content === "string") return content;
|
|
2265
2416
|
if (!Array.isArray(content)) return void 0;
|
|
2266
|
-
return content.
|
|
2417
|
+
return content.map((block) => {
|
|
2418
|
+
if (typeof block !== "object" || block === null) return void 0;
|
|
2419
|
+
const record = block;
|
|
2420
|
+
if (record.type === "text" && typeof record.text === "string") return record.text;
|
|
2421
|
+
if (record.type === "tool_reference" && typeof record.tool_name === "string") return record.tool_name;
|
|
2422
|
+
}).filter((text) => text !== void 0).join("\n");
|
|
2267
2423
|
}
|
|
2268
2424
|
/**
|
|
2269
2425
|
* Map claude's usage payload onto the shared usage contract. Claude's
|
|
2270
2426
|
* `input_tokens` is the uncached input; cache reads and cache creation are
|
|
2271
2427
|
* reported separately, so each maps to its own bucket with no subtraction.
|
|
2428
|
+
* When the payload carries ANY cache field, `cacheReadTokens` is always
|
|
2429
|
+
* written (0 when the read counter is absent or zero) — a reader can then
|
|
2430
|
+
* tell "no hits" from "not reported".
|
|
2272
2431
|
* @param usage - the raw claude usage object.
|
|
2273
2432
|
* @returns the shared usage record.
|
|
2274
2433
|
*/
|
|
@@ -2290,7 +2449,7 @@ function usageFromClaude(usage) {
|
|
|
2290
2449
|
inputTokens: input ?? 0,
|
|
2291
2450
|
outputTokens: output ?? 0
|
|
2292
2451
|
};
|
|
2293
|
-
if (cacheRead !== void 0
|
|
2452
|
+
if (cacheRead !== void 0 || cacheWrite !== void 0) usageRecord.cacheReadTokens = cacheRead ?? 0;
|
|
2294
2453
|
if (cacheWrite !== void 0 && cacheWrite > 0) usageRecord.cacheWriteTokens = cacheWrite;
|
|
2295
2454
|
return usageRecord;
|
|
2296
2455
|
}
|
|
@@ -2449,41 +2608,47 @@ async function startClaudeCliRun(request, spec) {
|
|
|
2449
2608
|
onAbort
|
|
2450
2609
|
}).then((settled) => {
|
|
2451
2610
|
if (settled.stopReason === "completed") spec.onAuthSuccess?.();
|
|
2452
|
-
|
|
2453
|
-
|
|
2611
|
+
return settled;
|
|
2612
|
+
});
|
|
2613
|
+
result.then(() => child.done).then(async () => {
|
|
2614
|
+
const drainedStdout = child.collected.stdout?.readFrom(0).text || output;
|
|
2615
|
+
if (spec.onAuthFailure !== void 0) {
|
|
2616
|
+
const drainedStderr = child.collected.stderr?.readFrom(0).text || stderr;
|
|
2617
|
+
const authDetail = `${parseClaudeStreamJson(drainedStdout).error ?? ""}\n${drainedStderr}`;
|
|
2618
|
+
if (CLAUDE_AUTH_FAILURE.test(authDetail)) spec.onAuthFailure(authDetail.split("\n").find((line) => line.trim() !== "") ?? "auth failure");
|
|
2619
|
+
}
|
|
2620
|
+
await mirrorClaudeAfterExit(spec, task, turn, drainedStdout, liveMirror);
|
|
2621
|
+
const closeTurn = async () => {
|
|
2622
|
+
if (spec.childSession === void 0) return;
|
|
2623
|
+
const childSession = spec.childSession;
|
|
2624
|
+
const settled = await result;
|
|
2625
|
+
if (settled.stopReason === "completed") childSession.append("turn/end", {
|
|
2454
2626
|
turn,
|
|
2455
2627
|
reason: { kind: "completed" }
|
|
2456
2628
|
});
|
|
2457
|
-
else if (settled.stopReason === "aborted")
|
|
2629
|
+
else if (settled.stopReason === "aborted") childSession.append("turn/end", {
|
|
2458
2630
|
turn,
|
|
2459
2631
|
reason: {
|
|
2460
2632
|
kind: "aborted",
|
|
2461
2633
|
reason: { kind: "parent" }
|
|
2462
2634
|
}
|
|
2463
2635
|
});
|
|
2464
|
-
else {
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
message: parsed.error ?? `claude -p exited with code ${String(exitCode)}`,
|
|
2472
|
-
code: "UNKNOWN"
|
|
2473
|
-
}
|
|
2636
|
+
else childSession.append("turn/end", {
|
|
2637
|
+
turn,
|
|
2638
|
+
reason: {
|
|
2639
|
+
kind: "error",
|
|
2640
|
+
error: {
|
|
2641
|
+
message: parseClaudeStreamJson(drainedStdout).error ?? `claude -p exited with code ${String(exitCode)}`,
|
|
2642
|
+
code: "UNKNOWN"
|
|
2474
2643
|
}
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
if (
|
|
2482
|
-
|
|
2483
|
-
const authDetail = `${parseClaudeStreamJson(child.collected.stdout?.readFrom(0).text || output).error ?? ""}\n${drainedStderr}`;
|
|
2484
|
-
if (CLAUDE_AUTH_FAILURE.test(authDetail)) spec.onAuthFailure(authDetail.split("\n").find((line) => line.trim() !== "") ?? "auth failure");
|
|
2485
|
-
}
|
|
2486
|
-
return mirrorClaudeAfterExit(spec, task, turn, output, liveMirror);
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
if (spec.ctx !== void 0) await persistChildSession(spec.ctx, childSession).catch((error) => {
|
|
2647
|
+
spec.ctx?.logger.warn(`subagent-claude: turn/end persistence failed: ${thrown(error).message}`);
|
|
2648
|
+
});
|
|
2649
|
+
};
|
|
2650
|
+
if (liveMirror === void 0) await closeTurn();
|
|
2651
|
+
else await liveMirror.enqueue(closeTurn);
|
|
2487
2652
|
}, () => {});
|
|
2488
2653
|
return Promise.resolve(subprocessRunHandle({
|
|
2489
2654
|
id: SessionId(randomUUID()),
|
|
@@ -2495,6 +2660,22 @@ async function startClaudeCliRun(request, spec) {
|
|
|
2495
2660
|
}));
|
|
2496
2661
|
}
|
|
2497
2662
|
/** One assistant-role message event, attributed to the claude route. */
|
|
2663
|
+
/**
|
|
2664
|
+
* Write the round's request/header when the config differs from the child
|
|
2665
|
+
* log's last one (the host logs changed snapshots, not per-call duplicates):
|
|
2666
|
+
* the first write is `initial`, a differing one `change`, an identical one
|
|
2667
|
+
* skipped. Context Lens reads the child log's config from these headers.
|
|
2668
|
+
*/
|
|
2669
|
+
function appendClaudeRequestHeader(childSession, config) {
|
|
2670
|
+
const serialized = JSON.stringify(config);
|
|
2671
|
+
let last;
|
|
2672
|
+
for (const event of childSession.snapshotEvents()) if (event.type === "request/header") last = JSON.stringify(event.data.header?.config);
|
|
2673
|
+
if (last === serialized) return;
|
|
2674
|
+
childSession.append("request/header", {
|
|
2675
|
+
header: { config },
|
|
2676
|
+
reason: last === void 0 ? "initial" : "change"
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2498
2679
|
function assistantEvent(blocks) {
|
|
2499
2680
|
return createAssistantMessage({
|
|
2500
2681
|
content: blocks,
|
|
@@ -2512,30 +2693,53 @@ function assistantEvent(blocks) {
|
|
|
2512
2693
|
* the real-time view renders nothing (only a full rebuild, which mints step
|
|
2513
2694
|
* drafts from the explicit coordinates, recovers it). Tool cards escape that
|
|
2514
2695
|
* fate only because their definition does not consult the step location.
|
|
2696
|
+
*
|
|
2697
|
+
* A tool line folds the native lifecycle inside its one step: an assistant
|
|
2698
|
+
* message advertising the tool-call block, the `tool/call`, and its
|
|
2699
|
+
* `tool/result` — the released-log reader pairs call and result against the
|
|
2700
|
+
* advertisement and resolves every advertised call before the step closes, so
|
|
2701
|
+
* a line whose result the stream has not recorded yet must NOT reach this
|
|
2702
|
+
* fold mid-round (the callers hold there; the result merges into the call's
|
|
2703
|
+
* line in place, so a later pass sees it). A line still unresulted at settle
|
|
2704
|
+
* folds with the interrupted placeholder ({@link CLAUDE_INTERRUPTED_TOOL_RESULT}).
|
|
2515
2705
|
*/
|
|
2516
2706
|
function appendClaudeTranscriptLine(childSession, turn, step, line, usage) {
|
|
2707
|
+
const lineUsage = line.usage ?? usage;
|
|
2517
2708
|
childSession.append("step/start", {
|
|
2518
2709
|
turn,
|
|
2519
2710
|
step
|
|
2520
2711
|
});
|
|
2521
2712
|
if (line.kind === "tool") {
|
|
2713
|
+
const args = line.args ?? "";
|
|
2714
|
+
childSession.append("assistant/message", {
|
|
2715
|
+
turn,
|
|
2716
|
+
step,
|
|
2717
|
+
message: assistantEvent([{
|
|
2718
|
+
type: "tool-call",
|
|
2719
|
+
id: line.id,
|
|
2720
|
+
name: line.name,
|
|
2721
|
+
arguments: args
|
|
2722
|
+
}]),
|
|
2723
|
+
stream: [],
|
|
2724
|
+
...lineUsage === void 0 ? {} : { usage: lineUsage }
|
|
2725
|
+
}, { surfaceOp: "append" });
|
|
2522
2726
|
const call = childSession.append("tool/call", {
|
|
2523
2727
|
turn,
|
|
2524
2728
|
step,
|
|
2525
2729
|
callId: line.id,
|
|
2526
2730
|
name: line.name,
|
|
2527
|
-
arguments:
|
|
2731
|
+
arguments: args
|
|
2528
2732
|
});
|
|
2529
|
-
|
|
2733
|
+
childSession.append("tool/result", {
|
|
2530
2734
|
turn,
|
|
2531
2735
|
step,
|
|
2532
2736
|
message: createToolResultMessage({
|
|
2533
2737
|
callId: line.id,
|
|
2534
2738
|
content: [{
|
|
2535
2739
|
type: "text",
|
|
2536
|
-
text: line.result
|
|
2740
|
+
text: line.result ?? "(工具调用被中断,未能记录返回结果)"
|
|
2537
2741
|
}],
|
|
2538
|
-
isError:
|
|
2742
|
+
isError: line.result === void 0
|
|
2539
2743
|
})
|
|
2540
2744
|
}, {
|
|
2541
2745
|
surfaceOp: "append",
|
|
@@ -2559,24 +2763,82 @@ function appendClaudeTranscriptLine(childSession, turn, step, line, usage) {
|
|
|
2559
2763
|
step,
|
|
2560
2764
|
message: assistantEvent(blocks),
|
|
2561
2765
|
stream: [],
|
|
2562
|
-
...
|
|
2766
|
+
...lineUsage === void 0 ? {} : { usage: lineUsage }
|
|
2563
2767
|
}, { surfaceOp: "append" });
|
|
2564
2768
|
childSession.append("step/end", {
|
|
2565
2769
|
turn,
|
|
2566
2770
|
step
|
|
2567
2771
|
});
|
|
2568
2772
|
}
|
|
2569
|
-
/**
|
|
2570
|
-
* Fold one transcript line into the run's child session as one assistant step.
|
|
2571
|
-
*/
|
|
2572
|
-
function appendClaudeLine(spec, turn, step, line, usage) {
|
|
2573
|
-
if (spec.childSession === void 0) return;
|
|
2574
|
-
appendClaudeTranscriptLine(spec.childSession, turn, step, line, usage);
|
|
2575
|
-
}
|
|
2576
2773
|
/** The delta-progress text for one transcript line. */
|
|
2577
2774
|
function claudeLineText(line) {
|
|
2578
2775
|
return line.kind === "tool" ? `[工具 ${line.name}]${line.args !== void 0 ? ` ${line.args}` : ""}${line.result !== void 0 ? ` → ${line.result}` : ""}` : line.text;
|
|
2579
2776
|
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Fold one API call (a message-id group) into the child session as ONE
|
|
2779
|
+
* assistant step: a single assistant/message carrying the call's reasoning,
|
|
2780
|
+
* text and tool-call blocks (the host's own mixed-content shape), then each
|
|
2781
|
+
* call's native lifecycle, then step/end. The call's usage rides the message
|
|
2782
|
+
* once — the group's own per-call stamp wins over the round-total fallback.
|
|
2783
|
+
*/
|
|
2784
|
+
function appendClaudeTranscriptGroup(childSession, turn, step, group, usage) {
|
|
2785
|
+
const lineUsage = group.map((line) => line.usage).find((value) => value !== void 0) ?? usage;
|
|
2786
|
+
const blocks = [];
|
|
2787
|
+
for (const line of group) if (line.kind === "think") blocks.push({
|
|
2788
|
+
type: "reasoning",
|
|
2789
|
+
text: line.text
|
|
2790
|
+
});
|
|
2791
|
+
else if (line.kind === "text") blocks.push({
|
|
2792
|
+
type: "text",
|
|
2793
|
+
text: line.text
|
|
2794
|
+
});
|
|
2795
|
+
else blocks.push({
|
|
2796
|
+
type: "tool-call",
|
|
2797
|
+
id: line.id,
|
|
2798
|
+
name: line.name,
|
|
2799
|
+
arguments: line.args ?? ""
|
|
2800
|
+
});
|
|
2801
|
+
childSession.append("step/start", {
|
|
2802
|
+
turn,
|
|
2803
|
+
step
|
|
2804
|
+
});
|
|
2805
|
+
childSession.append("assistant/message", {
|
|
2806
|
+
turn,
|
|
2807
|
+
step,
|
|
2808
|
+
message: assistantEvent(blocks),
|
|
2809
|
+
stream: [],
|
|
2810
|
+
...lineUsage === void 0 ? {} : { usage: lineUsage }
|
|
2811
|
+
}, { surfaceOp: "append" });
|
|
2812
|
+
for (const line of group) {
|
|
2813
|
+
if (line.kind !== "tool") continue;
|
|
2814
|
+
const call = childSession.append("tool/call", {
|
|
2815
|
+
turn,
|
|
2816
|
+
step,
|
|
2817
|
+
callId: line.id,
|
|
2818
|
+
name: line.name,
|
|
2819
|
+
arguments: line.args ?? ""
|
|
2820
|
+
});
|
|
2821
|
+
childSession.append("tool/result", {
|
|
2822
|
+
turn,
|
|
2823
|
+
step,
|
|
2824
|
+
message: createToolResultMessage({
|
|
2825
|
+
callId: line.id,
|
|
2826
|
+
content: [{
|
|
2827
|
+
type: "text",
|
|
2828
|
+
text: line.result ?? "(工具调用被中断,未能记录返回结果)"
|
|
2829
|
+
}],
|
|
2830
|
+
isError: line.result === void 0
|
|
2831
|
+
})
|
|
2832
|
+
}, {
|
|
2833
|
+
surfaceOp: "append",
|
|
2834
|
+
sourceEventSeqs: [call.seq]
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
childSession.append("step/end", {
|
|
2838
|
+
turn,
|
|
2839
|
+
step
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2580
2842
|
/** Create the per-run live mirror over the incremental parser. */
|
|
2581
2843
|
function createClaudeLiveMirror(spec, task, turn) {
|
|
2582
2844
|
const parser = new ClaudeStreamParser();
|
|
@@ -2584,6 +2846,7 @@ function createClaudeLiveMirror(spec, task, turn) {
|
|
|
2584
2846
|
const ctx = spec.ctx;
|
|
2585
2847
|
let mirrored = 0;
|
|
2586
2848
|
let userMirrored = false;
|
|
2849
|
+
let headerWritten = false;
|
|
2587
2850
|
let lastTodos;
|
|
2588
2851
|
let todoSkewWarned = false;
|
|
2589
2852
|
let queue = Promise.resolve();
|
|
@@ -2595,7 +2858,7 @@ function createClaudeLiveMirror(spec, task, turn) {
|
|
|
2595
2858
|
return userMirrored;
|
|
2596
2859
|
},
|
|
2597
2860
|
enqueue(task) {
|
|
2598
|
-
queue = queue.then(task);
|
|
2861
|
+
queue = queue.catch(() => {}).then(task);
|
|
2599
2862
|
return queue;
|
|
2600
2863
|
},
|
|
2601
2864
|
push(chunk) {
|
|
@@ -2608,8 +2871,22 @@ function createClaudeLiveMirror(spec, task, turn) {
|
|
|
2608
2871
|
todoSkewWarned = true;
|
|
2609
2872
|
ctx.logger.warn("subagent-claude: a TodoWrite call missed the documented input shape; folded as a plain tool line");
|
|
2610
2873
|
}
|
|
2611
|
-
const
|
|
2612
|
-
|
|
2874
|
+
const groups = groupClaudeTranscriptLines(parser.lines);
|
|
2875
|
+
const groupStarts = [];
|
|
2876
|
+
let totalLines = 0;
|
|
2877
|
+
for (const group of groups) {
|
|
2878
|
+
groupStarts.push(totalLines);
|
|
2879
|
+
totalLines += group.length;
|
|
2880
|
+
}
|
|
2881
|
+
let uptoGroups = parser.completed ? groups.length : groups.length - 1;
|
|
2882
|
+
if (!parser.completed) for (let scan = 0; scan < uptoGroups; scan += 1) {
|
|
2883
|
+
if (groupStarts[scan] < mirrored) continue;
|
|
2884
|
+
if (groups[scan].some((line) => line.kind === "tool" && line.result === void 0)) {
|
|
2885
|
+
uptoGroups = scan;
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if ((groupStarts[uptoGroups] ?? totalLines) <= mirrored && userMirrored) return;
|
|
2613
2890
|
mirror.enqueue(async () => {
|
|
2614
2891
|
try {
|
|
2615
2892
|
const localAgent = ctx.get("localAgent");
|
|
@@ -2627,17 +2904,27 @@ function createClaudeLiveMirror(spec, task, turn) {
|
|
|
2627
2904
|
text: task
|
|
2628
2905
|
});
|
|
2629
2906
|
}
|
|
2907
|
+
if (!headerWritten) {
|
|
2908
|
+
const observedModel = parser.model ?? spec.model;
|
|
2909
|
+
if (observedModel !== void 0) {
|
|
2910
|
+
headerWritten = true;
|
|
2911
|
+
appendClaudeRequestHeader(childSession, {
|
|
2912
|
+
provider: "claude-local",
|
|
2913
|
+
model: observedModel,
|
|
2914
|
+
...spec.controlled ? { reasoningEffort: spec.effort ?? "auto" } : {}
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2630
2918
|
let carrier = -1;
|
|
2631
|
-
if (parser.completed && parser.usage !== void 0) {
|
|
2632
|
-
for (let scan = 0; scan <
|
|
2919
|
+
if (parser.completed && parser.usage !== void 0 && !parser.lines.some((line) => line.usage !== void 0)) {
|
|
2920
|
+
for (let scan = 0; scan < groups.length; scan += 1) if (groups[scan].some((line) => line.kind !== "tool")) carrier = scan;
|
|
2633
2921
|
}
|
|
2634
|
-
for (let
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
localAgent?.reportRunProgress(childSession.id, {
|
|
2922
|
+
for (let scan = 0; scan < uptoGroups; scan += 1) {
|
|
2923
|
+
if (groupStarts[scan] < mirrored) continue;
|
|
2924
|
+
const group = groups[scan];
|
|
2925
|
+
appendClaudeTranscriptGroup(childSession, turn, scan + 1, group, scan === carrier ? parser.usage : void 0);
|
|
2926
|
+
mirrored = groupStarts[scan] + group.length;
|
|
2927
|
+
for (const line of group) localAgent?.reportRunProgress(childSession.id, {
|
|
2641
2928
|
kind: "delta",
|
|
2642
2929
|
text: claudeLineText(line)
|
|
2643
2930
|
});
|
|
@@ -2687,18 +2974,28 @@ async function appendClaudeResponse(spec, task, turn, parsed, fromLines = 0, use
|
|
|
2687
2974
|
text: task
|
|
2688
2975
|
});
|
|
2689
2976
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
let
|
|
2693
|
-
for (const
|
|
2694
|
-
|
|
2695
|
-
|
|
2977
|
+
const groups = groupClaudeTranscriptLines(parsed.lines);
|
|
2978
|
+
const groupStarts = [];
|
|
2979
|
+
let totalLines = 0;
|
|
2980
|
+
for (const group of groups) {
|
|
2981
|
+
groupStarts.push(totalLines);
|
|
2982
|
+
totalLines += group.length;
|
|
2983
|
+
}
|
|
2984
|
+
let carrier = -1;
|
|
2985
|
+
if (!parsed.lines.some((line) => line.usage !== void 0)) {
|
|
2986
|
+
for (let scan = 0; scan < groups.length; scan += 1) if (groups[scan].some((line) => line.kind !== "tool")) carrier = scan;
|
|
2987
|
+
}
|
|
2988
|
+
for (let scan = 0; scan < groups.length; scan += 1) {
|
|
2989
|
+
if (groupStarts[scan] < fromLines) continue;
|
|
2990
|
+
const group = groups[scan];
|
|
2991
|
+
appendClaudeTranscriptGroup(childSession, turn, scan + 1, group, scan === carrier ? parsed.usage : void 0);
|
|
2992
|
+
for (const line of group) localAgent?.reportRunProgress(childSession.id, {
|
|
2696
2993
|
kind: "delta",
|
|
2697
2994
|
text: claudeLineText(line)
|
|
2698
2995
|
});
|
|
2699
|
-
step += 1;
|
|
2700
2996
|
}
|
|
2701
|
-
|
|
2997
|
+
const carrierLine = carrier === -1 ? -1 : groupStarts[carrier] ?? -1;
|
|
2998
|
+
if (parsed.usage !== void 0 && carrierLine !== -1 && carrierLine < fromLines) spec.ctx.logger.warn(`subagent-claude: round ${turn} usage arrived after its carrier line was mirrored; the accounting is dropped`);
|
|
2702
2999
|
await persistChildSession(spec.ctx, childSession);
|
|
2703
3000
|
localAgent?.reportRunProgress(childSession.id, {
|
|
2704
3001
|
kind: "mirror",
|
|
@@ -3051,6 +3348,47 @@ var ClaudeScopedModelMemory = class {
|
|
|
3051
3348
|
this.written = model;
|
|
3052
3349
|
}
|
|
3053
3350
|
};
|
|
3351
|
+
/**
|
|
3352
|
+
* Scope the served directory to the surface asking for it (the same helper
|
|
3353
|
+
* lives in each provider's broker — fold into the family core when the
|
|
3354
|
+
* directory faces next evolve):
|
|
3355
|
+
*
|
|
3356
|
+
* - memberless (the card's add-model dialog, the invite picker): the
|
|
3357
|
+
* endpoint's served catalog only — the configuration/history extras the
|
|
3358
|
+
* broker's info extension adds for STATUS display must never become add
|
|
3359
|
+
* candidates;
|
|
3360
|
+
* - a member whose harness has a curated list: the list IS the vocabulary,
|
|
3361
|
+
* in the list's own order (the same rule `choices` already follows) — a
|
|
3362
|
+
* curated value the endpoint no longer serves stays as a bare row (the
|
|
3363
|
+
* card shows it the same way), and the member's current effective model is
|
|
3364
|
+
* kept visible as a bare entry when it sits outside the list, so a pre-list
|
|
3365
|
+
* binding never renders blank;
|
|
3366
|
+
* - a member without a curated list: the legacy extended vocabulary.
|
|
3367
|
+
*/
|
|
3368
|
+
function scopeServedDirectory(directory, opts) {
|
|
3369
|
+
if (opts.member && opts.curated.length === 0) return directory;
|
|
3370
|
+
const served = directory.entries.filter((entry) => entry.source === "native");
|
|
3371
|
+
if (!opts.member) return {
|
|
3372
|
+
...directory,
|
|
3373
|
+
entries: served
|
|
3374
|
+
};
|
|
3375
|
+
const byValue = new Map(served.flatMap((entry) => entry.resolvedModel === void 0 ? [[entry.value, entry]] : [[entry.value, entry], [entry.resolvedModel, entry]]));
|
|
3376
|
+
const entries = opts.curated.map((value) => byValue.get(value) ?? {
|
|
3377
|
+
value,
|
|
3378
|
+
label: value,
|
|
3379
|
+
source: "configuration"
|
|
3380
|
+
});
|
|
3381
|
+
const current = opts.current?.trim();
|
|
3382
|
+
if (current !== void 0 && current !== "" && !entries.some((entry) => entry.value === current || entry.resolvedModel === current)) entries.push({
|
|
3383
|
+
value: current,
|
|
3384
|
+
label: current,
|
|
3385
|
+
source: "configuration"
|
|
3386
|
+
});
|
|
3387
|
+
return {
|
|
3388
|
+
...directory,
|
|
3389
|
+
entries
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3054
3392
|
var ClaudeModelBroker = class {
|
|
3055
3393
|
deps;
|
|
3056
3394
|
constructor(deps) {
|
|
@@ -3101,14 +3439,20 @@ var ClaudeModelBroker = class {
|
|
|
3101
3439
|
}
|
|
3102
3440
|
async modelDirectory(childSessionId, refresh = false) {
|
|
3103
3441
|
if (refresh) await this.deps.directory?.(childSessionId, true);
|
|
3104
|
-
|
|
3442
|
+
const record = childSessionId === void 0 ? void 0 : this.deps.localAgent.getDelegation(childSessionId);
|
|
3443
|
+
const info = await this.modelInfo(childSessionId, record?.model);
|
|
3444
|
+
return scopeServedDirectory(info.directory ?? {
|
|
3105
3445
|
entries: [],
|
|
3106
3446
|
complete: false,
|
|
3107
3447
|
customInput: true,
|
|
3108
3448
|
status: "unsupported",
|
|
3109
3449
|
refreshing: false,
|
|
3110
3450
|
revision: 0
|
|
3111
|
-
}
|
|
3451
|
+
}, {
|
|
3452
|
+
member: childSessionId !== void 0,
|
|
3453
|
+
curated: this.deps.modelList?.(record?.scope) ?? [],
|
|
3454
|
+
current: info.effective
|
|
3455
|
+
});
|
|
3112
3456
|
}
|
|
3113
3457
|
async *followModelDirectory(childSessionId, signal) {
|
|
3114
3458
|
if (this.deps.followDirectory === void 0) {
|
|
@@ -3506,7 +3850,7 @@ const inject = [
|
|
|
3506
3850
|
* behavior byte-identical.
|
|
3507
3851
|
*/
|
|
3508
3852
|
const SETTINGS_FIELDS = {
|
|
3509
|
-
live: z.boolean().default(
|
|
3853
|
+
live: z.boolean().default(true),
|
|
3510
3854
|
liveMirrorGranularity: z.union([z.const("event"), z.const("token")]).default("token"),
|
|
3511
3855
|
model: z.string(),
|
|
3512
3856
|
recentModels: z.array(z.string()).default([])
|
|
@@ -3556,7 +3900,7 @@ function apply(ctx, config) {
|
|
|
3556
3900
|
const model = vol(config.model);
|
|
3557
3901
|
const recentModels = vol(config.recentModels);
|
|
3558
3902
|
return {
|
|
3559
|
-
live: vol(config.live) ??
|
|
3903
|
+
live: vol(config.live) ?? true,
|
|
3560
3904
|
liveMirrorGranularity: vol(config.liveMirrorGranularity) ?? "token",
|
|
3561
3905
|
...model === void 0 ? {} : { model },
|
|
3562
3906
|
...recentModels === void 0 ? {} : { recentModels }
|
|
@@ -3573,6 +3917,7 @@ function apply(ctx, config) {
|
|
|
3573
3917
|
}
|
|
3574
3918
|
const face = settingsFace(ctx, {
|
|
3575
3919
|
namespace: CLAUDE_SETTINGS_NAMESPACE,
|
|
3920
|
+
harness: "claude-code",
|
|
3576
3921
|
legacySchema: CLAUDE_SETTINGS_SCHEMA,
|
|
3577
3922
|
legacyBase,
|
|
3578
3923
|
read: readSettings
|