@ganglion/xacpx 0.18.1 → 0.19.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/README.md +13 -3
- package/dist/bridge/bridge-main.js +76 -14
- package/dist/cli.js +198 -112
- package/dist/control/control-service.d.ts +2 -0
- package/dist/control/workspace-git.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ English · **[中文](./docs/zh/README_zh.md)**
|
|
|
23
23
|
|
|
24
24
|
If you need to code or work remotely on a temporary basis, `xacpx` gives you a fast, convenient **remote entry point** so you can get things done from WeChat or Feishu anytime, anywhere.
|
|
25
25
|
|
|
26
|
+
Chat isn't the only entry point: xacpx also ships a self-hostable **[relay hub](#self-hosted-relay-hub--web-dashboard)** — a multi-tenant web dashboard that drives every one of your xacpx instances from a single browser tab (installable as a PWA on your phone).
|
|
27
|
+
|
|
26
28
|
> For everyday use, remember `/ss` first: it creates or reuses an xacpx logical session. If you want to attach to an existing native session of a local agent such as Codex, use `/ssn`; see [native sessions](./docs/native-sessions.md).
|
|
27
29
|
|
|
28
30
|
## 5-minute quick start
|
|
@@ -173,9 +175,17 @@ take a look at today's API timeout issue
|
|
|
173
175
|
/ssn 1
|
|
174
176
|
```
|
|
175
177
|
|
|
176
|
-
## Self-hosted relay hub
|
|
178
|
+
## Self-hosted relay hub — web dashboard
|
|
179
|
+
|
|
180
|
+
If you run one or more xacpx instances and want to drive them from a browser instead of (or in addition to) chat, self-host the **relay hub**. Each instance dials out to the hub over WebSocket and registers; you log in to a multi-tenant web dashboard and manage every instance's sessions from one place.
|
|
181
|
+
|
|
182
|
+
What you get:
|
|
177
183
|
|
|
178
|
-
|
|
184
|
+
- **A three-pane IM-style dashboard** — instance/session tree on the left, live chat in the middle, scheduled tasks & orchestration panel on the right. English + 中文 UI.
|
|
185
|
+
- **Live streaming replies** rendered as markdown, with tool-call and subagent activity inline; cancel running turns from the browser.
|
|
186
|
+
- **Mobile-ready** — installs as a PWA, so it feels like an app on your phone.
|
|
187
|
+
- **One package, one port** — `@ganglion/xacpx-relay` ships the dashboard **bundled in**; HTTP API, web socket, and instance gateway share a single port by default. SQLite storage via the built-in `node:sqlite`/`bun:sqlite` — no native addons to compile.
|
|
188
|
+
- **Multi-tenant & token-based** — every token is a user who only sees their own instances; tokens and credentials are stored hashed. Onboard teammates with **single-use invite links** (`xacpx-relay add invite`) instead of handing out tokens.
|
|
179
189
|
|
|
180
190
|
```bash
|
|
181
191
|
npm i -g @ganglion/xacpx-relay
|
|
@@ -188,7 +198,7 @@ xacpx channel add relay --url wss://relay.example.com --token <access-token> --n
|
|
|
188
198
|
xacpx restart
|
|
189
199
|
```
|
|
190
200
|
|
|
191
|
-
Full walkthrough — pairing, TLS/reverse-proxy, systemd, backups, troubleshooting: **[Self-Hosting the Relay Hub](https://gadzan.github.io/xacpx/guide/relay-self-hosting)** (or [relay-deployment.md](./docs/relay-deployment.md) for the terse runbook).
|
|
201
|
+
The same access token works for both web login and connector pairing. Full walkthrough — pairing, invites, TLS/reverse-proxy, systemd, backups, troubleshooting: **[Self-Hosting the Relay Hub](https://gadzan.github.io/xacpx/guide/relay-self-hosting)** (or [relay-deployment.md](./docs/relay-deployment.md) for the terse runbook).
|
|
192
202
|
|
|
193
203
|
## Config and runtime files
|
|
194
204
|
|
|
@@ -2633,12 +2633,12 @@ var init_prompt_media = __esm(() => {
|
|
|
2633
2633
|
};
|
|
2634
2634
|
});
|
|
2635
2635
|
|
|
2636
|
-
// src/transport/
|
|
2636
|
+
// src/transport/background-followup.ts
|
|
2637
2637
|
import { access, open as open2, readFile, readdir, stat } from "node:fs/promises";
|
|
2638
2638
|
import { constants as fsConstants } from "node:fs";
|
|
2639
2639
|
import { homedir as homedir2 } from "node:os";
|
|
2640
2640
|
import { basename, dirname as dirname2, join as join2 } from "node:path";
|
|
2641
|
-
function
|
|
2641
|
+
function isAsyncAgentLaunch(event) {
|
|
2642
2642
|
return isAsyncAgentLaunchOutput(event.rawOutput);
|
|
2643
2643
|
}
|
|
2644
2644
|
function isAsyncAgentLaunchOutput(rawOutput) {
|
|
@@ -2660,13 +2660,13 @@ function tryParseJsonObject(text) {
|
|
|
2660
2660
|
return;
|
|
2661
2661
|
}
|
|
2662
2662
|
}
|
|
2663
|
-
function
|
|
2663
|
+
function asyncAgentId(event) {
|
|
2664
2664
|
const fromObject = findStringField(event.rawOutput, new Set(["agentId", "agent_id"]));
|
|
2665
2665
|
if (fromObject)
|
|
2666
2666
|
return fromObject;
|
|
2667
2667
|
return /\bagent(?:Id|_id)["']?\s*[:=]\s*["']?([A-Za-z0-9_-]+)/i.exec(textOfUnknown(event.rawOutput))?.[1];
|
|
2668
2668
|
}
|
|
2669
|
-
async function
|
|
2669
|
+
async function findNativeTranscriptPath(input) {
|
|
2670
2670
|
const configDirName = input.driver === "qoder" ? ".qoder" : ".claude";
|
|
2671
2671
|
const projectsDir = join2(input.homeDir ?? homedir2(), configDirName, "projects");
|
|
2672
2672
|
const direct = join2(projectsDir, encodeClaudeProjectDirectory(input.cwd), `${input.sessionId}.jsonl`);
|
|
@@ -2686,8 +2686,8 @@ async function findClaudeTranscriptPath(input) {
|
|
|
2686
2686
|
}
|
|
2687
2687
|
return;
|
|
2688
2688
|
}
|
|
2689
|
-
async function
|
|
2690
|
-
const transcriptPath = options.transcriptPath ?? await
|
|
2689
|
+
async function followBackgroundTurn(options) {
|
|
2690
|
+
const transcriptPath = options.transcriptPath ?? await findNativeTranscriptPath(options);
|
|
2691
2691
|
if (!transcriptPath) {
|
|
2692
2692
|
return { status: "unavailable", completedToolCallIds: [], failedToolCallIds: [] };
|
|
2693
2693
|
}
|
|
@@ -2869,8 +2869,8 @@ async function followClaudeBackgroundTurn(options) {
|
|
|
2869
2869
|
...previous?.rawOutput !== undefined ? { rawOutput: previous.rawOutput } : output ? { rawOutput: output } : {},
|
|
2870
2870
|
status: readBoolean(block, "is_error") || previous?.status === "error" ? "error" : "success"
|
|
2871
2871
|
};
|
|
2872
|
-
if (event.isSubagent &&
|
|
2873
|
-
const agentId =
|
|
2872
|
+
if (event.isSubagent && isAsyncAgentLaunch(event)) {
|
|
2873
|
+
const agentId = asyncAgentId(event);
|
|
2874
2874
|
if (agentId)
|
|
2875
2875
|
parentToolCallIdByAgentId.set(agentId, event.toolCallId);
|
|
2876
2876
|
}
|
|
@@ -3183,7 +3183,7 @@ async function waitForPoll(ms, signal) {
|
|
|
3183
3183
|
});
|
|
3184
3184
|
}
|
|
3185
3185
|
var DEFAULT_POLL_INTERVAL_MS = 250, DEFAULT_TIMEOUT_MS, FINAL_SUBAGENT_DRAIN_MAX_POLLS = 4, FINAL_SUBAGENT_DRAIN_STABLE_POLLS = 2, ASYNC_AGENT_LAUNCH;
|
|
3186
|
-
var
|
|
3186
|
+
var init_background_followup = __esm(() => {
|
|
3187
3187
|
DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
3188
3188
|
ASYNC_AGENT_LAUNCH = /Async agent launched successfully|["']?status["']?\s*:\s*["']async_launched["']/i;
|
|
3189
3189
|
});
|
|
@@ -3223,6 +3223,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
3223
3223
|
let onCommands;
|
|
3224
3224
|
let rawStream = false;
|
|
3225
3225
|
let driver;
|
|
3226
|
+
let onBeforeActivityEvent;
|
|
3226
3227
|
if (options === undefined) {
|
|
3227
3228
|
toolEventMode = "text";
|
|
3228
3229
|
onToolEvent = undefined;
|
|
@@ -3237,6 +3238,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
3237
3238
|
onCommands = options.onCommands;
|
|
3238
3239
|
rawStream = options.rawStream ?? false;
|
|
3239
3240
|
driver = options.driver?.trim().toLowerCase() || undefined;
|
|
3241
|
+
onBeforeActivityEvent = options.onBeforeActivityEvent;
|
|
3240
3242
|
toolEventMode = resolveToolEventMode({
|
|
3241
3243
|
toolEventMode: options.mode,
|
|
3242
3244
|
onToolEvent
|
|
@@ -3249,10 +3251,14 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
3249
3251
|
pendingLine: "",
|
|
3250
3252
|
formatToolCalls,
|
|
3251
3253
|
emittedToolCallIds: new Set,
|
|
3254
|
+
positionedToolCallIds: new Set,
|
|
3252
3255
|
toolCalls: new Map,
|
|
3253
3256
|
toolEventMode,
|
|
3254
3257
|
driver,
|
|
3255
3258
|
rawStream,
|
|
3259
|
+
lastTextTail: "",
|
|
3260
|
+
activitySinceLastText: false,
|
|
3261
|
+
onBeforeActivityEvent,
|
|
3256
3262
|
onToolEvent,
|
|
3257
3263
|
onThought,
|
|
3258
3264
|
onPlan,
|
|
@@ -3295,6 +3301,14 @@ function parseStreamingChunks(state, line) {
|
|
|
3295
3301
|
if (!update)
|
|
3296
3302
|
return;
|
|
3297
3303
|
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
|
|
3304
|
+
const isInitialToolEvent = typeof update.toolCallId === "string" ? !state.positionedToolCallIds.has(update.toolCallId) : update.sessionUpdate === "tool_call";
|
|
3305
|
+
if (isInitialToolEvent) {
|
|
3306
|
+
if (update.toolCallId)
|
|
3307
|
+
state.positionedToolCallIds.add(update.toolCallId);
|
|
3308
|
+
markActivityBoundary(state);
|
|
3309
|
+
} else {
|
|
3310
|
+
flushBeforeActivityEvent(state);
|
|
3311
|
+
}
|
|
3298
3312
|
const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
|
|
3299
3313
|
const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
|
|
3300
3314
|
if (wantsStructured && state.onToolEvent) {
|
|
@@ -3343,6 +3357,7 @@ function parseStreamingChunks(state, line) {
|
|
|
3343
3357
|
if (isThoughtChunk) {
|
|
3344
3358
|
const chunk2 = update.content.text;
|
|
3345
3359
|
if (chunk2.length > 0) {
|
|
3360
|
+
markActivityBoundary(state);
|
|
3346
3361
|
state.onThought?.(chunk2);
|
|
3347
3362
|
}
|
|
3348
3363
|
return;
|
|
@@ -3351,10 +3366,22 @@ function parseStreamingChunks(state, line) {
|
|
|
3351
3366
|
if (!isMessageChunk)
|
|
3352
3367
|
return;
|
|
3353
3368
|
state.hasAgentMessage = true;
|
|
3354
|
-
|
|
3369
|
+
let chunk = update.content.text ?? "";
|
|
3355
3370
|
if (chunk.length === 0)
|
|
3356
3371
|
return;
|
|
3372
|
+
const messageId = typeof update.messageId === "string" && update.messageId.length > 0 ? update.messageId : undefined;
|
|
3373
|
+
const messageIdChanged = state.lastMessageId !== undefined && messageId !== undefined && state.lastMessageId !== messageId;
|
|
3374
|
+
const fallbackBoundary = state.activitySinceLastText && (state.lastMessageId === undefined || messageId === undefined) && endsWithSentenceTerminal(state.lastTextTail);
|
|
3375
|
+
if ((messageIdChanged || fallbackBoundary) && !hasParagraphBoundaryAtJoin(state.lastTextTail, chunk)) {
|
|
3376
|
+
chunk = `
|
|
3377
|
+
|
|
3378
|
+
${chunk}`;
|
|
3379
|
+
state.lastTextTail = "";
|
|
3380
|
+
}
|
|
3357
3381
|
state.buffer += chunk;
|
|
3382
|
+
state.lastMessageId = messageId;
|
|
3383
|
+
state.activitySinceLastText = false;
|
|
3384
|
+
state.lastTextTail = `${state.lastTextTail}${chunk}`.slice(-256);
|
|
3358
3385
|
if (state.rawStream)
|
|
3359
3386
|
return;
|
|
3360
3387
|
let boundary;
|
|
@@ -3368,6 +3395,24 @@ function parseStreamingChunks(state, line) {
|
|
|
3368
3395
|
}
|
|
3369
3396
|
}
|
|
3370
3397
|
}
|
|
3398
|
+
function endsWithSentenceTerminal(text) {
|
|
3399
|
+
return SENTENCE_TERMINAL_AT_END.test(text.trimEnd());
|
|
3400
|
+
}
|
|
3401
|
+
function hasParagraphBoundaryAtJoin(left, right) {
|
|
3402
|
+
const leftHasBoundary = PARAGRAPH_BOUNDARY_AT_END.test(left);
|
|
3403
|
+
const rightHasBoundary = PARAGRAPH_BOUNDARY_AT_START.test(right);
|
|
3404
|
+
const boundarySpansJoin = LINE_BREAK_AT_END.test(left) && LINE_BREAK_AT_START.test(right);
|
|
3405
|
+
const crlfBoundarySpansJoin = PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END.test(left) && right.startsWith(`
|
|
3406
|
+
`);
|
|
3407
|
+
return leftHasBoundary || rightHasBoundary || boundarySpansJoin || crlfBoundarySpansJoin;
|
|
3408
|
+
}
|
|
3409
|
+
function markActivityBoundary(state) {
|
|
3410
|
+
flushBeforeActivityEvent(state);
|
|
3411
|
+
state.activitySinceLastText = state.hasAgentMessage;
|
|
3412
|
+
}
|
|
3413
|
+
function flushBeforeActivityEvent(state) {
|
|
3414
|
+
state.onBeforeActivityEvent?.();
|
|
3415
|
+
}
|
|
3371
3416
|
function formatToolCallEvent(update, sessionUpdate) {
|
|
3372
3417
|
if (!update)
|
|
3373
3418
|
return null;
|
|
@@ -3454,8 +3499,8 @@ function buildToolUseEvent(update, driver) {
|
|
|
3454
3499
|
const claudeMeta = isClaudeDriver ? update._meta?.claudeCode : undefined;
|
|
3455
3500
|
const claudeToolResponse = claudeMeta?.toolResponse;
|
|
3456
3501
|
const hasClaudeToolResponse = !isEmptyToolField(claudeToolResponse);
|
|
3457
|
-
const
|
|
3458
|
-
const status =
|
|
3502
|
+
const isAsyncAgentLaunch2 = update._meta?.claudeCode?.toolName === "Agent" && isRecord2(claudeToolResponse) && claudeToolResponse.status === "async_launched" || driver === "qoder" && update._meta?.qoder?.toolName === "Agent" && isAsyncAgentLaunchOutput(update.rawOutput);
|
|
3503
|
+
const status = isAsyncAgentLaunch2 ? "running" : statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : hasClaudeToolResponse ? "success" : "running";
|
|
3459
3504
|
const rawInput = update.rawInput;
|
|
3460
3505
|
const content = update.content;
|
|
3461
3506
|
const rawOutput = update.rawOutput ?? claudeToolResponse;
|
|
@@ -3629,10 +3674,16 @@ function isGenericToolTitle(kind, title) {
|
|
|
3629
3674
|
}
|
|
3630
3675
|
return false;
|
|
3631
3676
|
}
|
|
3632
|
-
var USAGE_BREAKDOWN_FIELDS;
|
|
3677
|
+
var SENTENCE_TERMINAL_AT_END, PARAGRAPH_BOUNDARY_AT_END, PARAGRAPH_BOUNDARY_AT_START, LINE_BREAK_AT_END, LINE_BREAK_AT_START, PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END, USAGE_BREAKDOWN_FIELDS;
|
|
3633
3678
|
var init_streaming_prompt = __esm(() => {
|
|
3634
|
-
|
|
3679
|
+
init_background_followup();
|
|
3635
3680
|
init_tool_kind_emoji();
|
|
3681
|
+
SENTENCE_TERMINAL_AT_END = /(?:\p{Sentence_Terminal}|…|⋯)[\p{Close_Punctuation}\p{Final_Punctuation}"“”‘’*_~`]*$/u;
|
|
3682
|
+
PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r?\n[\t ]*$/;
|
|
3683
|
+
PARAGRAPH_BOUNDARY_AT_START = /^[\t ]*\r?\n[\t ]*\r?\n/;
|
|
3684
|
+
LINE_BREAK_AT_END = /\r?\n[\t ]*$/;
|
|
3685
|
+
LINE_BREAK_AT_START = /^[\t ]*\r?\n/;
|
|
3686
|
+
PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r$/;
|
|
3636
3687
|
USAGE_BREAKDOWN_FIELDS = [
|
|
3637
3688
|
["inputTokens", ["inputTokens", "input_tokens"]],
|
|
3638
3689
|
["outputTokens", ["outputTokens", "output_tokens"]],
|
|
@@ -7534,10 +7585,14 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
|
7534
7585
|
let stdout = "";
|
|
7535
7586
|
let stderr = "";
|
|
7536
7587
|
const toolEventMode = options.toolEventMode ?? "text";
|
|
7588
|
+
let flushPendingText = () => {};
|
|
7537
7589
|
const state = createStreamingPromptState(options.formatToolCalls ?? false, {
|
|
7538
7590
|
mode: toolEventMode,
|
|
7539
7591
|
driver: options.driver,
|
|
7540
7592
|
rawStream,
|
|
7593
|
+
onBeforeActivityEvent: () => {
|
|
7594
|
+
flushPendingText();
|
|
7595
|
+
},
|
|
7541
7596
|
...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {},
|
|
7542
7597
|
...onEvent ? { onThought: (chunk) => onEvent({ type: "prompt.thought", text: chunk }) } : {},
|
|
7543
7598
|
...onEvent ? { onPlan: (entries) => onEvent({ type: "prompt.plan", entries }) } : {},
|
|
@@ -7553,6 +7608,13 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
|
7553
7608
|
lastReplyAt = now();
|
|
7554
7609
|
}
|
|
7555
7610
|
};
|
|
7611
|
+
flushPendingText = () => {
|
|
7612
|
+
for (const segment of state.segments.splice(0)) {
|
|
7613
|
+
onEvent?.({ type: "prompt.segment", text: segment });
|
|
7614
|
+
lastReplyAt = now();
|
|
7615
|
+
}
|
|
7616
|
+
flushBuffer();
|
|
7617
|
+
};
|
|
7556
7618
|
const timer = setIntervalFn(() => {
|
|
7557
7619
|
if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
|
|
7558
7620
|
flushBuffer();
|
package/dist/cli.js
CHANGED
|
@@ -32775,6 +32775,25 @@ var init_quota_gated_reply_sink = __esm(() => {
|
|
|
32775
32775
|
ADAPTIVE_WINDOW_SCHEDULE_MS = [3000, 6000, 12000, 24000, 48000, 60000];
|
|
32776
32776
|
});
|
|
32777
32777
|
|
|
32778
|
+
// src/transport/serialized-callback-queue.ts
|
|
32779
|
+
function createSerializedCallbackQueue() {
|
|
32780
|
+
let chain = Promise.resolve();
|
|
32781
|
+
let firstError;
|
|
32782
|
+
return {
|
|
32783
|
+
enqueue(callback) {
|
|
32784
|
+
chain = chain.then(callback).catch((error2) => {
|
|
32785
|
+
firstError ??= error2;
|
|
32786
|
+
});
|
|
32787
|
+
},
|
|
32788
|
+
async drain() {
|
|
32789
|
+
await chain;
|
|
32790
|
+
},
|
|
32791
|
+
getError() {
|
|
32792
|
+
return firstError;
|
|
32793
|
+
}
|
|
32794
|
+
};
|
|
32795
|
+
}
|
|
32796
|
+
|
|
32778
32797
|
// src/transport/tool-event-mode.ts
|
|
32779
32798
|
function resolveToolEventMode(input) {
|
|
32780
32799
|
if (input?.toolEventMode !== undefined) {
|
|
@@ -32827,12 +32846,7 @@ class AcpxBridgeTransport {
|
|
|
32827
32846
|
reply,
|
|
32828
32847
|
...replyContext ? { replyContext } : {}
|
|
32829
32848
|
}) : null;
|
|
32830
|
-
|
|
32831
|
-
let segmentChain = Promise.resolve();
|
|
32832
|
-
let toolEventError;
|
|
32833
|
-
let toolEventChain = Promise.resolve();
|
|
32834
|
-
let thoughtError;
|
|
32835
|
-
let thoughtChain = Promise.resolve();
|
|
32849
|
+
const transcriptEvents = createSerializedCallbackQueue();
|
|
32836
32850
|
let planError;
|
|
32837
32851
|
let planChain = Promise.resolve();
|
|
32838
32852
|
let usageError;
|
|
@@ -32852,22 +32866,19 @@ class AcpxBridgeTransport {
|
|
|
32852
32866
|
}, (event) => {
|
|
32853
32867
|
if (event.type === "prompt.segment") {
|
|
32854
32868
|
const onSegment = options?.onSegment;
|
|
32855
|
-
|
|
32856
|
-
|
|
32857
|
-
|
|
32858
|
-
|
|
32859
|
-
|
|
32860
|
-
}
|
|
32861
|
-
sink2?.feedSegment(event.text);
|
|
32869
|
+
const segmentText = event.text;
|
|
32870
|
+
transcriptEvents.enqueue(async () => {
|
|
32871
|
+
const segmentResult = onSegment?.(segmentText);
|
|
32872
|
+
sink2?.feedSegment(segmentText);
|
|
32873
|
+
await segmentResult;
|
|
32874
|
+
});
|
|
32862
32875
|
return;
|
|
32863
32876
|
}
|
|
32864
32877
|
if (event.type === "prompt.tool_event") {
|
|
32865
32878
|
const onToolEvent = options?.onToolEvent;
|
|
32866
32879
|
if (onToolEvent) {
|
|
32867
32880
|
const toolEvent = event.event;
|
|
32868
|
-
|
|
32869
|
-
toolEventError ??= error2;
|
|
32870
|
-
});
|
|
32881
|
+
transcriptEvents.enqueue(() => onToolEvent(toolEvent));
|
|
32871
32882
|
}
|
|
32872
32883
|
return;
|
|
32873
32884
|
}
|
|
@@ -32875,9 +32886,7 @@ class AcpxBridgeTransport {
|
|
|
32875
32886
|
const onThought = options?.onThought;
|
|
32876
32887
|
if (onThought) {
|
|
32877
32888
|
const thoughtText = event.text;
|
|
32878
|
-
|
|
32879
|
-
thoughtError ??= error2;
|
|
32880
|
-
});
|
|
32889
|
+
transcriptEvents.enqueue(() => onThought(thoughtText));
|
|
32881
32890
|
}
|
|
32882
32891
|
return;
|
|
32883
32892
|
}
|
|
@@ -32912,9 +32921,7 @@ class AcpxBridgeTransport {
|
|
|
32912
32921
|
return;
|
|
32913
32922
|
}
|
|
32914
32923
|
});
|
|
32915
|
-
await
|
|
32916
|
-
await toolEventChain;
|
|
32917
|
-
await thoughtChain;
|
|
32924
|
+
await transcriptEvents.drain();
|
|
32918
32925
|
await planChain;
|
|
32919
32926
|
await usageChain;
|
|
32920
32927
|
await commandsChain;
|
|
@@ -32926,14 +32933,9 @@ class AcpxBridgeTransport {
|
|
|
32926
32933
|
throw deferred;
|
|
32927
32934
|
}
|
|
32928
32935
|
const summary = buildOverflowSummary(overflowCount);
|
|
32929
|
-
|
|
32930
|
-
|
|
32931
|
-
|
|
32932
|
-
if (toolEventError) {
|
|
32933
|
-
throw toolEventError;
|
|
32934
|
-
}
|
|
32935
|
-
if (thoughtError) {
|
|
32936
|
-
throw thoughtError;
|
|
32936
|
+
const transcriptError2 = transcriptEvents.getError();
|
|
32937
|
+
if (transcriptError2) {
|
|
32938
|
+
throw transcriptError2;
|
|
32937
32939
|
}
|
|
32938
32940
|
if (planError) {
|
|
32939
32941
|
throw planError;
|
|
@@ -32948,14 +32950,9 @@ class AcpxBridgeTransport {
|
|
|
32948
32950
|
|
|
32949
32951
|
${result.text}` : "" };
|
|
32950
32952
|
}
|
|
32951
|
-
|
|
32952
|
-
|
|
32953
|
-
|
|
32954
|
-
if (toolEventError) {
|
|
32955
|
-
throw toolEventError;
|
|
32956
|
-
}
|
|
32957
|
-
if (thoughtError) {
|
|
32958
|
-
throw thoughtError;
|
|
32953
|
+
const transcriptError = transcriptEvents.getError();
|
|
32954
|
+
if (transcriptError) {
|
|
32955
|
+
throw transcriptError;
|
|
32959
32956
|
}
|
|
32960
32957
|
if (planError) {
|
|
32961
32958
|
throw planError;
|
|
@@ -33177,12 +33174,12 @@ var init_prompt_media = __esm(() => {
|
|
|
33177
33174
|
};
|
|
33178
33175
|
});
|
|
33179
33176
|
|
|
33180
|
-
// src/transport/
|
|
33177
|
+
// src/transport/background-followup.ts
|
|
33181
33178
|
import { access as access4, open as open5, readFile as readFile14, readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
33182
33179
|
import { constants as fsConstants } from "node:fs";
|
|
33183
33180
|
import { homedir as homedir10 } from "node:os";
|
|
33184
33181
|
import { basename as basename3, dirname as dirname12, join as join20 } from "node:path";
|
|
33185
|
-
function
|
|
33182
|
+
function isAsyncAgentLaunch(event) {
|
|
33186
33183
|
return isAsyncAgentLaunchOutput(event.rawOutput);
|
|
33187
33184
|
}
|
|
33188
33185
|
function isAsyncAgentLaunchOutput(rawOutput) {
|
|
@@ -33204,13 +33201,13 @@ function tryParseJsonObject(text) {
|
|
|
33204
33201
|
return;
|
|
33205
33202
|
}
|
|
33206
33203
|
}
|
|
33207
|
-
function
|
|
33204
|
+
function asyncAgentId(event) {
|
|
33208
33205
|
const fromObject = findStringField(event.rawOutput, new Set(["agentId", "agent_id"]));
|
|
33209
33206
|
if (fromObject)
|
|
33210
33207
|
return fromObject;
|
|
33211
33208
|
return /\bagent(?:Id|_id)["']?\s*[:=]\s*["']?([A-Za-z0-9_-]+)/i.exec(textOfUnknown(event.rawOutput))?.[1];
|
|
33212
33209
|
}
|
|
33213
|
-
async function
|
|
33210
|
+
async function findNativeTranscriptPath(input) {
|
|
33214
33211
|
const configDirName = input.driver === "qoder" ? ".qoder" : ".claude";
|
|
33215
33212
|
const projectsDir = join20(input.homeDir ?? homedir10(), configDirName, "projects");
|
|
33216
33213
|
const direct = join20(projectsDir, encodeClaudeProjectDirectory(input.cwd), `${input.sessionId}.jsonl`);
|
|
@@ -33230,8 +33227,8 @@ async function findClaudeTranscriptPath(input) {
|
|
|
33230
33227
|
}
|
|
33231
33228
|
return;
|
|
33232
33229
|
}
|
|
33233
|
-
async function
|
|
33234
|
-
const transcriptPath = options.transcriptPath ?? await
|
|
33230
|
+
async function followBackgroundTurn(options) {
|
|
33231
|
+
const transcriptPath = options.transcriptPath ?? await findNativeTranscriptPath(options);
|
|
33235
33232
|
if (!transcriptPath) {
|
|
33236
33233
|
return { status: "unavailable", completedToolCallIds: [], failedToolCallIds: [] };
|
|
33237
33234
|
}
|
|
@@ -33413,8 +33410,8 @@ async function followClaudeBackgroundTurn(options) {
|
|
|
33413
33410
|
...previous?.rawOutput !== undefined ? { rawOutput: previous.rawOutput } : output ? { rawOutput: output } : {},
|
|
33414
33411
|
status: readBoolean(block, "is_error") || previous?.status === "error" ? "error" : "success"
|
|
33415
33412
|
};
|
|
33416
|
-
if (event.isSubagent &&
|
|
33417
|
-
const agentId =
|
|
33413
|
+
if (event.isSubagent && isAsyncAgentLaunch(event)) {
|
|
33414
|
+
const agentId = asyncAgentId(event);
|
|
33418
33415
|
if (agentId)
|
|
33419
33416
|
parentToolCallIdByAgentId.set(agentId, event.toolCallId);
|
|
33420
33417
|
}
|
|
@@ -33727,7 +33724,7 @@ async function waitForPoll(ms, signal) {
|
|
|
33727
33724
|
});
|
|
33728
33725
|
}
|
|
33729
33726
|
var DEFAULT_POLL_INTERVAL_MS = 250, DEFAULT_TIMEOUT_MS, FINAL_SUBAGENT_DRAIN_MAX_POLLS = 4, FINAL_SUBAGENT_DRAIN_STABLE_POLLS = 2, ASYNC_AGENT_LAUNCH;
|
|
33730
|
-
var
|
|
33727
|
+
var init_background_followup = __esm(() => {
|
|
33731
33728
|
DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
33732
33729
|
ASYNC_AGENT_LAUNCH = /Async agent launched successfully|["']?status["']?\s*:\s*["']async_launched["']/i;
|
|
33733
33730
|
});
|
|
@@ -33756,6 +33753,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
33756
33753
|
let onCommands;
|
|
33757
33754
|
let rawStream = false;
|
|
33758
33755
|
let driver;
|
|
33756
|
+
let onBeforeActivityEvent;
|
|
33759
33757
|
if (options === undefined) {
|
|
33760
33758
|
toolEventMode = "text";
|
|
33761
33759
|
onToolEvent = undefined;
|
|
@@ -33770,6 +33768,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
33770
33768
|
onCommands = options.onCommands;
|
|
33771
33769
|
rawStream = options.rawStream ?? false;
|
|
33772
33770
|
driver = options.driver?.trim().toLowerCase() || undefined;
|
|
33771
|
+
onBeforeActivityEvent = options.onBeforeActivityEvent;
|
|
33773
33772
|
toolEventMode = resolveToolEventMode({
|
|
33774
33773
|
toolEventMode: options.mode,
|
|
33775
33774
|
onToolEvent
|
|
@@ -33782,10 +33781,14 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
33782
33781
|
pendingLine: "",
|
|
33783
33782
|
formatToolCalls,
|
|
33784
33783
|
emittedToolCallIds: new Set,
|
|
33784
|
+
positionedToolCallIds: new Set,
|
|
33785
33785
|
toolCalls: new Map,
|
|
33786
33786
|
toolEventMode,
|
|
33787
33787
|
driver,
|
|
33788
33788
|
rawStream,
|
|
33789
|
+
lastTextTail: "",
|
|
33790
|
+
activitySinceLastText: false,
|
|
33791
|
+
onBeforeActivityEvent,
|
|
33789
33792
|
onToolEvent,
|
|
33790
33793
|
onThought,
|
|
33791
33794
|
onPlan,
|
|
@@ -33828,6 +33831,14 @@ function parseStreamingChunks(state, line) {
|
|
|
33828
33831
|
if (!update)
|
|
33829
33832
|
return;
|
|
33830
33833
|
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
|
|
33834
|
+
const isInitialToolEvent = typeof update.toolCallId === "string" ? !state.positionedToolCallIds.has(update.toolCallId) : update.sessionUpdate === "tool_call";
|
|
33835
|
+
if (isInitialToolEvent) {
|
|
33836
|
+
if (update.toolCallId)
|
|
33837
|
+
state.positionedToolCallIds.add(update.toolCallId);
|
|
33838
|
+
markActivityBoundary(state);
|
|
33839
|
+
} else {
|
|
33840
|
+
flushBeforeActivityEvent(state);
|
|
33841
|
+
}
|
|
33831
33842
|
const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
|
|
33832
33843
|
const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
|
|
33833
33844
|
if (wantsStructured && state.onToolEvent) {
|
|
@@ -33876,6 +33887,7 @@ function parseStreamingChunks(state, line) {
|
|
|
33876
33887
|
if (isThoughtChunk) {
|
|
33877
33888
|
const chunk2 = update.content.text;
|
|
33878
33889
|
if (chunk2.length > 0) {
|
|
33890
|
+
markActivityBoundary(state);
|
|
33879
33891
|
state.onThought?.(chunk2);
|
|
33880
33892
|
}
|
|
33881
33893
|
return;
|
|
@@ -33884,10 +33896,22 @@ function parseStreamingChunks(state, line) {
|
|
|
33884
33896
|
if (!isMessageChunk)
|
|
33885
33897
|
return;
|
|
33886
33898
|
state.hasAgentMessage = true;
|
|
33887
|
-
|
|
33899
|
+
let chunk = update.content.text ?? "";
|
|
33888
33900
|
if (chunk.length === 0)
|
|
33889
33901
|
return;
|
|
33902
|
+
const messageId = typeof update.messageId === "string" && update.messageId.length > 0 ? update.messageId : undefined;
|
|
33903
|
+
const messageIdChanged = state.lastMessageId !== undefined && messageId !== undefined && state.lastMessageId !== messageId;
|
|
33904
|
+
const fallbackBoundary = state.activitySinceLastText && (state.lastMessageId === undefined || messageId === undefined) && endsWithSentenceTerminal(state.lastTextTail);
|
|
33905
|
+
if ((messageIdChanged || fallbackBoundary) && !hasParagraphBoundaryAtJoin(state.lastTextTail, chunk)) {
|
|
33906
|
+
chunk = `
|
|
33907
|
+
|
|
33908
|
+
${chunk}`;
|
|
33909
|
+
state.lastTextTail = "";
|
|
33910
|
+
}
|
|
33890
33911
|
state.buffer += chunk;
|
|
33912
|
+
state.lastMessageId = messageId;
|
|
33913
|
+
state.activitySinceLastText = false;
|
|
33914
|
+
state.lastTextTail = `${state.lastTextTail}${chunk}`.slice(-256);
|
|
33891
33915
|
if (state.rawStream)
|
|
33892
33916
|
return;
|
|
33893
33917
|
let boundary;
|
|
@@ -33901,6 +33925,24 @@ function parseStreamingChunks(state, line) {
|
|
|
33901
33925
|
}
|
|
33902
33926
|
}
|
|
33903
33927
|
}
|
|
33928
|
+
function endsWithSentenceTerminal(text) {
|
|
33929
|
+
return SENTENCE_TERMINAL_AT_END.test(text.trimEnd());
|
|
33930
|
+
}
|
|
33931
|
+
function hasParagraphBoundaryAtJoin(left, right) {
|
|
33932
|
+
const leftHasBoundary = PARAGRAPH_BOUNDARY_AT_END.test(left);
|
|
33933
|
+
const rightHasBoundary = PARAGRAPH_BOUNDARY_AT_START.test(right);
|
|
33934
|
+
const boundarySpansJoin = LINE_BREAK_AT_END.test(left) && LINE_BREAK_AT_START.test(right);
|
|
33935
|
+
const crlfBoundarySpansJoin = PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END.test(left) && right.startsWith(`
|
|
33936
|
+
`);
|
|
33937
|
+
return leftHasBoundary || rightHasBoundary || boundarySpansJoin || crlfBoundarySpansJoin;
|
|
33938
|
+
}
|
|
33939
|
+
function markActivityBoundary(state) {
|
|
33940
|
+
flushBeforeActivityEvent(state);
|
|
33941
|
+
state.activitySinceLastText = state.hasAgentMessage;
|
|
33942
|
+
}
|
|
33943
|
+
function flushBeforeActivityEvent(state) {
|
|
33944
|
+
state.onBeforeActivityEvent?.();
|
|
33945
|
+
}
|
|
33904
33946
|
function formatToolCallEvent(update, sessionUpdate) {
|
|
33905
33947
|
if (!update)
|
|
33906
33948
|
return null;
|
|
@@ -33987,8 +34029,8 @@ function buildToolUseEvent(update, driver) {
|
|
|
33987
34029
|
const claudeMeta = isClaudeDriver ? update._meta?.claudeCode : undefined;
|
|
33988
34030
|
const claudeToolResponse = claudeMeta?.toolResponse;
|
|
33989
34031
|
const hasClaudeToolResponse = !isEmptyToolField(claudeToolResponse);
|
|
33990
|
-
const
|
|
33991
|
-
const status =
|
|
34032
|
+
const isAsyncAgentLaunch2 = update._meta?.claudeCode?.toolName === "Agent" && isRecord4(claudeToolResponse) && claudeToolResponse.status === "async_launched" || driver === "qoder" && update._meta?.qoder?.toolName === "Agent" && isAsyncAgentLaunchOutput(update.rawOutput);
|
|
34033
|
+
const status = isAsyncAgentLaunch2 ? "running" : statusRaw === "completed" || statusRaw === "success" ? "success" : statusRaw === "failed" || statusRaw === "error" ? "error" : hasClaudeToolResponse ? "success" : "running";
|
|
33992
34034
|
const rawInput = update.rawInput;
|
|
33993
34035
|
const content = update.content;
|
|
33994
34036
|
const rawOutput = update.rawOutput ?? claudeToolResponse;
|
|
@@ -34162,10 +34204,16 @@ function isGenericToolTitle(kind, title) {
|
|
|
34162
34204
|
}
|
|
34163
34205
|
return false;
|
|
34164
34206
|
}
|
|
34165
|
-
var USAGE_BREAKDOWN_FIELDS;
|
|
34207
|
+
var SENTENCE_TERMINAL_AT_END, PARAGRAPH_BOUNDARY_AT_END, PARAGRAPH_BOUNDARY_AT_START, LINE_BREAK_AT_END, LINE_BREAK_AT_START, PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END, USAGE_BREAKDOWN_FIELDS;
|
|
34166
34208
|
var init_streaming_prompt = __esm(() => {
|
|
34167
|
-
|
|
34209
|
+
init_background_followup();
|
|
34168
34210
|
init_tool_kind_emoji();
|
|
34211
|
+
SENTENCE_TERMINAL_AT_END = /(?:\p{Sentence_Terminal}|…|⋯)[\p{Close_Punctuation}\p{Final_Punctuation}"“”‘’*_~`]*$/u;
|
|
34212
|
+
PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r?\n[\t ]*$/;
|
|
34213
|
+
PARAGRAPH_BOUNDARY_AT_START = /^[\t ]*\r?\n[\t ]*\r?\n/;
|
|
34214
|
+
LINE_BREAK_AT_END = /\r?\n[\t ]*$/;
|
|
34215
|
+
LINE_BREAK_AT_START = /^[\t ]*\r?\n/;
|
|
34216
|
+
PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r$/;
|
|
34169
34217
|
USAGE_BREAKDOWN_FIELDS = [
|
|
34170
34218
|
["inputTokens", ["inputTokens", "input_tokens"]],
|
|
34171
34219
|
["outputTokens", ["outputTokens", "output_tokens"]],
|
|
@@ -35074,12 +35122,7 @@ ${baseText}` : "" };
|
|
|
35074
35122
|
let stdout2 = "";
|
|
35075
35123
|
let stderr = "";
|
|
35076
35124
|
let lastReplyAt = now();
|
|
35077
|
-
|
|
35078
|
-
let segmentError;
|
|
35079
|
-
let toolEventChain = Promise.resolve();
|
|
35080
|
-
let toolEventError;
|
|
35081
|
-
let thoughtChain = Promise.resolve();
|
|
35082
|
-
let thoughtError;
|
|
35125
|
+
const transcriptEvents = createSerializedCallbackQueue();
|
|
35083
35126
|
let planChain = Promise.resolve();
|
|
35084
35127
|
let planError;
|
|
35085
35128
|
let usageChain = Promise.resolve();
|
|
@@ -35091,22 +35134,22 @@ ${baseText}` : "" };
|
|
|
35091
35134
|
const userOnPlan = onPlan;
|
|
35092
35135
|
const userOnUsage = onUsage;
|
|
35093
35136
|
const userOnCommands = onCommands;
|
|
35137
|
+
let flushPendingText = () => {};
|
|
35094
35138
|
const state = createStreamingPromptState(formatToolCalls, {
|
|
35095
35139
|
mode: toolEventMode,
|
|
35096
35140
|
driver,
|
|
35097
35141
|
rawStream,
|
|
35142
|
+
onBeforeActivityEvent: () => {
|
|
35143
|
+
flushPendingText();
|
|
35144
|
+
},
|
|
35098
35145
|
...userOnToolEvent ? {
|
|
35099
35146
|
onToolEvent: (event) => {
|
|
35100
|
-
|
|
35101
|
-
toolEventError ??= error2;
|
|
35102
|
-
});
|
|
35147
|
+
transcriptEvents.enqueue(() => userOnToolEvent(event));
|
|
35103
35148
|
}
|
|
35104
35149
|
} : {},
|
|
35105
35150
|
...userOnThought ? {
|
|
35106
35151
|
onThought: (chunk) => {
|
|
35107
|
-
|
|
35108
|
-
thoughtError ??= error2;
|
|
35109
|
-
});
|
|
35152
|
+
transcriptEvents.enqueue(() => userOnThought(chunk));
|
|
35110
35153
|
}
|
|
35111
35154
|
} : {},
|
|
35112
35155
|
...userOnPlan ? {
|
|
@@ -35136,12 +35179,11 @@ ${baseText}` : "" };
|
|
|
35136
35179
|
...replyContext ? { replyContext } : {}
|
|
35137
35180
|
}) : null;
|
|
35138
35181
|
const feedSegment = (segment) => {
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
}
|
|
35144
|
-
sink2?.feedSegment(segment);
|
|
35182
|
+
transcriptEvents.enqueue(async () => {
|
|
35183
|
+
const segmentResult = onSegment?.(segment);
|
|
35184
|
+
sink2?.feedSegment(segment);
|
|
35185
|
+
await segmentResult;
|
|
35186
|
+
});
|
|
35145
35187
|
lastReplyAt = now();
|
|
35146
35188
|
};
|
|
35147
35189
|
const flushBuffer = () => {
|
|
@@ -35151,6 +35193,12 @@ ${baseText}` : "" };
|
|
|
35151
35193
|
feedSegment(remaining);
|
|
35152
35194
|
}
|
|
35153
35195
|
};
|
|
35196
|
+
flushPendingText = () => {
|
|
35197
|
+
for (const segment of state.segments.splice(0)) {
|
|
35198
|
+
feedSegment(segment);
|
|
35199
|
+
}
|
|
35200
|
+
flushBuffer();
|
|
35201
|
+
};
|
|
35154
35202
|
const timer = setIntervalFn(() => {
|
|
35155
35203
|
if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
|
|
35156
35204
|
flushBuffer();
|
|
@@ -35177,31 +35225,23 @@ ${baseText}` : "" };
|
|
|
35177
35225
|
if (remaining.length > 0) {
|
|
35178
35226
|
feedSegment(remaining);
|
|
35179
35227
|
}
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
]).then(() => {
|
|
35228
|
+
(async () => {
|
|
35229
|
+
await Promise.all([
|
|
35230
|
+
transcriptEvents.drain(),
|
|
35231
|
+
planChain,
|
|
35232
|
+
usageChain,
|
|
35233
|
+
commandsChain
|
|
35234
|
+
]);
|
|
35235
|
+
const { overflowCount } = sink2?.finalize() ?? { overflowCount: 0 };
|
|
35236
|
+
await (sink2?.drain({ timeoutMs: 30000 }) ?? Promise.resolve());
|
|
35190
35237
|
const deferred = sink2?.getPendingError();
|
|
35191
35238
|
if (deferred) {
|
|
35192
35239
|
reject(deferred);
|
|
35193
35240
|
return;
|
|
35194
35241
|
}
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
}
|
|
35199
|
-
if (toolEventError) {
|
|
35200
|
-
reject(toolEventError);
|
|
35201
|
-
return;
|
|
35202
|
-
}
|
|
35203
|
-
if (thoughtError) {
|
|
35204
|
-
reject(thoughtError);
|
|
35242
|
+
const transcriptError = transcriptEvents.getError();
|
|
35243
|
+
if (transcriptError) {
|
|
35244
|
+
reject(transcriptError);
|
|
35205
35245
|
return;
|
|
35206
35246
|
}
|
|
35207
35247
|
if (planError) {
|
|
@@ -35220,7 +35260,7 @@ ${baseText}` : "" };
|
|
|
35220
35260
|
result: { code: code ?? 1, stdout: stdout2, stderr },
|
|
35221
35261
|
overflowCount
|
|
35222
35262
|
});
|
|
35223
|
-
}).catch((error2) => {
|
|
35263
|
+
})().catch((error2) => {
|
|
35224
35264
|
reject(error2);
|
|
35225
35265
|
});
|
|
35226
35266
|
});
|
|
@@ -35314,9 +35354,9 @@ var init_acpx_cli_transport = __esm(() => {
|
|
|
35314
35354
|
require4 = createRequire5(import.meta.url);
|
|
35315
35355
|
});
|
|
35316
35356
|
|
|
35317
|
-
// src/transport/
|
|
35318
|
-
function
|
|
35319
|
-
const
|
|
35357
|
+
// src/transport/background-followup-transport.ts
|
|
35358
|
+
function createBackgroundFollowupTransport(delegate, options) {
|
|
35359
|
+
const runBackgroundFollowup = options.followBackgroundTurn ?? followBackgroundTurn;
|
|
35320
35360
|
const prompt = async (session3, text, reply, replyContext, promptOptions) => {
|
|
35321
35361
|
const latestToolEvents = new Map;
|
|
35322
35362
|
const pendingToolEvents = new Map;
|
|
@@ -35329,9 +35369,9 @@ function createClaudeBackgroundFollowupTransport(delegate, options) {
|
|
|
35329
35369
|
pendingToolEvents.set(event.toolCallId, event);
|
|
35330
35370
|
else
|
|
35331
35371
|
pendingToolEvents.delete(event.toolCallId);
|
|
35332
|
-
if (event.isSubagent &&
|
|
35372
|
+
if (event.isSubagent && isAsyncAgentLaunch(event)) {
|
|
35333
35373
|
asyncToolCallIds.add(event.toolCallId);
|
|
35334
|
-
const agentId =
|
|
35374
|
+
const agentId = asyncAgentId(event);
|
|
35335
35375
|
if (agentId)
|
|
35336
35376
|
subagentIdsByToolCallId.set(event.toolCallId, agentId);
|
|
35337
35377
|
}
|
|
@@ -35350,7 +35390,7 @@ function createClaudeBackgroundFollowupTransport(delegate, options) {
|
|
|
35350
35390
|
try {
|
|
35351
35391
|
agentSessionId = await delegate.getAgentSessionId?.(session3);
|
|
35352
35392
|
} catch (error2) {
|
|
35353
|
-
await options.logger.error(
|
|
35393
|
+
await options.logger.error(`${LEGACY_LOG_EVENT_PREFIX}.session_id_failed`, "failed to resolve agent session id for background follow-up", { alias: session3.alias, error: error2 instanceof Error ? error2.message : String(error2) });
|
|
35354
35394
|
}
|
|
35355
35395
|
}
|
|
35356
35396
|
if (!agentSessionId) {
|
|
@@ -35363,8 +35403,8 @@ function createClaudeBackgroundFollowupTransport(delegate, options) {
|
|
|
35363
35403
|
}
|
|
35364
35404
|
return result;
|
|
35365
35405
|
}
|
|
35366
|
-
await options.logger.info(
|
|
35367
|
-
const followup = await
|
|
35406
|
+
await options.logger.info(`${LEGACY_LOG_EVENT_PREFIX}.started`, "following background-agent continuation", { alias: session3.alias, agentSessionId, driver, taskCount: asyncToolCallIds.size });
|
|
35407
|
+
const followup = await runBackgroundFollowup({
|
|
35368
35408
|
cwd: session3.cwd,
|
|
35369
35409
|
sessionId: agentSessionId,
|
|
35370
35410
|
driver,
|
|
@@ -35399,7 +35439,7 @@ function createClaudeBackgroundFollowupTransport(delegate, options) {
|
|
|
35399
35439
|
...followup.status === "completed" && !failed ? {} : { rawOutput: { message: failed ? "background Agent failed" : `background continuation ${followup.status}` } }
|
|
35400
35440
|
});
|
|
35401
35441
|
}
|
|
35402
|
-
await options.logger.info(
|
|
35442
|
+
await options.logger.info(`${LEGACY_LOG_EVENT_PREFIX}.${followup.status}`, `background-agent follow-up ${followup.status}`, {
|
|
35403
35443
|
alias: session3.alias,
|
|
35404
35444
|
agentSessionId,
|
|
35405
35445
|
driver,
|
|
@@ -35417,8 +35457,9 @@ function createClaudeBackgroundFollowupTransport(delegate, options) {
|
|
|
35417
35457
|
}
|
|
35418
35458
|
});
|
|
35419
35459
|
}
|
|
35420
|
-
var
|
|
35421
|
-
|
|
35460
|
+
var LEGACY_LOG_EVENT_PREFIX = "transport.claude_background_followup";
|
|
35461
|
+
var init_background_followup_transport = __esm(() => {
|
|
35462
|
+
init_background_followup();
|
|
35422
35463
|
});
|
|
35423
35464
|
|
|
35424
35465
|
// src/util/async.ts
|
|
@@ -36782,21 +36823,28 @@ class WorkspaceGit {
|
|
|
36782
36823
|
}
|
|
36783
36824
|
return startPoint;
|
|
36784
36825
|
}
|
|
36785
|
-
async
|
|
36786
|
-
const selected = new Set(paths);
|
|
36787
|
-
const expanded = new Set(paths);
|
|
36826
|
+
async readPorcelain(root) {
|
|
36788
36827
|
const fields = (await this.runRaw(root, ["-c", "core.quotePath=false", "status", "--porcelain", "-z"])).split("\x00");
|
|
36828
|
+
const entries = [];
|
|
36789
36829
|
for (let index = 0;index < fields.length; index += 1) {
|
|
36790
36830
|
const field = fields[index];
|
|
36791
36831
|
if (!field)
|
|
36792
36832
|
continue;
|
|
36793
36833
|
const status = field.slice(0, 2);
|
|
36794
36834
|
const path14 = field.slice(3);
|
|
36795
|
-
|
|
36796
|
-
|
|
36797
|
-
|
|
36798
|
-
|
|
36799
|
-
|
|
36835
|
+
const entry = { status, path: path14 };
|
|
36836
|
+
if (status[0] === "R" || status[0] === "C")
|
|
36837
|
+
entry.originalPath = fields[++index];
|
|
36838
|
+
entries.push(entry);
|
|
36839
|
+
}
|
|
36840
|
+
return entries;
|
|
36841
|
+
}
|
|
36842
|
+
async includeRenameSources(root, paths) {
|
|
36843
|
+
const selected = new Set(paths);
|
|
36844
|
+
const expanded = new Set(paths);
|
|
36845
|
+
for (const entry of await this.readPorcelain(root)) {
|
|
36846
|
+
if (entry.originalPath && selected.has(entry.path))
|
|
36847
|
+
expanded.add(entry.originalPath);
|
|
36800
36848
|
}
|
|
36801
36849
|
return [...expanded];
|
|
36802
36850
|
}
|
|
@@ -36834,6 +36882,38 @@ class WorkspaceGit {
|
|
|
36834
36882
|
}
|
|
36835
36883
|
});
|
|
36836
36884
|
}
|
|
36885
|
+
async untrack(workspace3, paths) {
|
|
36886
|
+
await this.withWrite(workspace3, async () => {
|
|
36887
|
+
const root = await this.rootFor(workspace3);
|
|
36888
|
+
await this.run(root, ["--literal-pathspecs", "rm", "--cached", "--force", "--", ...this.validatePaths(paths)]);
|
|
36889
|
+
});
|
|
36890
|
+
}
|
|
36891
|
+
async discard(workspace3, paths) {
|
|
36892
|
+
await this.withWrite(workspace3, async () => {
|
|
36893
|
+
const root = await this.rootFor(workspace3);
|
|
36894
|
+
const selected = new Set(this.validatePaths(paths));
|
|
36895
|
+
const deletePaths = new Set;
|
|
36896
|
+
const restorePaths = new Set;
|
|
36897
|
+
for (const entry of await this.readPorcelain(root)) {
|
|
36898
|
+
if (!selected.has(entry.path))
|
|
36899
|
+
continue;
|
|
36900
|
+
if (entry.status === "??" || entry.status[0] === "A" || entry.originalPath !== undefined) {
|
|
36901
|
+
deletePaths.add(entry.path);
|
|
36902
|
+
if (entry.originalPath)
|
|
36903
|
+
restorePaths.add(entry.originalPath);
|
|
36904
|
+
} else {
|
|
36905
|
+
restorePaths.add(entry.path);
|
|
36906
|
+
}
|
|
36907
|
+
}
|
|
36908
|
+
if (deletePaths.size) {
|
|
36909
|
+
await this.run(root, ["--literal-pathspecs", "reset", "-q", "--", ...deletePaths]);
|
|
36910
|
+
await this.run(root, ["--literal-pathspecs", "clean", "-qf", "--", ...deletePaths]);
|
|
36911
|
+
}
|
|
36912
|
+
if (restorePaths.size) {
|
|
36913
|
+
await this.run(root, ["--literal-pathspecs", "restore", "--source=HEAD", "--staged", "--worktree", "--", ...restorePaths]);
|
|
36914
|
+
}
|
|
36915
|
+
});
|
|
36916
|
+
}
|
|
36837
36917
|
async commit(workspace3, message) {
|
|
36838
36918
|
return this.withWrite(workspace3, async () => {
|
|
36839
36919
|
const root = await this.rootFor(workspace3);
|
|
@@ -37564,6 +37644,12 @@ class ControlService {
|
|
|
37564
37644
|
gitUnstage(workspace3, paths) {
|
|
37565
37645
|
return this.mutateWorkspaceGit(() => this.workspaceGit.unstage(workspace3, paths));
|
|
37566
37646
|
}
|
|
37647
|
+
gitUntrack(workspace3, paths) {
|
|
37648
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.untrack(workspace3, paths));
|
|
37649
|
+
}
|
|
37650
|
+
gitDiscard(workspace3, paths) {
|
|
37651
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.discard(workspace3, paths));
|
|
37652
|
+
}
|
|
37567
37653
|
gitCommit(workspace3, message) {
|
|
37568
37654
|
return this.mutateWorkspaceGit(() => this.workspaceGit.commit(workspace3, message));
|
|
37569
37655
|
}
|
|
@@ -38594,7 +38680,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
38594
38680
|
});
|
|
38595
38681
|
}
|
|
38596
38682
|
})))) : deps.createCliTransport?.(acpxCommand) ?? new AcpxCliTransport({ ...config4.transport, command: acpxCommand });
|
|
38597
|
-
const transport =
|
|
38683
|
+
const transport = createBackgroundFollowupTransport(baseTransport, {
|
|
38598
38684
|
logger,
|
|
38599
38685
|
resolveDriver: (agent4) => config4.agents[agent4]?.driver
|
|
38600
38686
|
});
|
|
@@ -39269,7 +39355,7 @@ var init_main = __esm(async () => {
|
|
|
39269
39355
|
init_acpx_bridge_client();
|
|
39270
39356
|
init_acpx_bridge_transport();
|
|
39271
39357
|
init_acpx_cli_transport();
|
|
39272
|
-
|
|
39358
|
+
init_background_followup_transport();
|
|
39273
39359
|
init_queue_owner_reaper();
|
|
39274
39360
|
init_collect_reap_targets();
|
|
39275
39361
|
init_channel_registry();
|
|
@@ -158,6 +158,8 @@ export declare class ControlService {
|
|
|
158
158
|
private withWorktreeRegistration;
|
|
159
159
|
gitStage(workspace: string, paths: string[]): Promise<void>;
|
|
160
160
|
gitUnstage(workspace: string, paths: string[]): Promise<void>;
|
|
161
|
+
gitUntrack(workspace: string, paths: string[]): Promise<void>;
|
|
162
|
+
gitDiscard(workspace: string, paths: string[]): Promise<void>;
|
|
161
163
|
gitCommit(workspace: string, message: string): Promise<GitCommitResult>;
|
|
162
164
|
gitFetch(workspace: string, remote?: string): Promise<void>;
|
|
163
165
|
gitPull(workspace: string): Promise<void>;
|
|
@@ -77,6 +77,7 @@ export declare class WorkspaceGit {
|
|
|
77
77
|
private validatePaths;
|
|
78
78
|
private validateBranch;
|
|
79
79
|
private validateStartPoint;
|
|
80
|
+
private readPorcelain;
|
|
80
81
|
private includeRenameSources;
|
|
81
82
|
/** Git write commands for one configured workspace share a FIFO. Reads remain
|
|
82
83
|
* concurrent, and a failed write releases the next command instead of poisoning
|
|
@@ -84,6 +85,8 @@ export declare class WorkspaceGit {
|
|
|
84
85
|
private withWrite;
|
|
85
86
|
stage(workspace: string, paths: string[]): Promise<void>;
|
|
86
87
|
unstage(workspace: string, paths: string[]): Promise<void>;
|
|
88
|
+
untrack(workspace: string, paths: string[]): Promise<void>;
|
|
89
|
+
discard(workspace: string, paths: string[]): Promise<void>;
|
|
87
90
|
commit(workspace: string, message: string): Promise<GitCommitResult>;
|
|
88
91
|
checkout(workspace: string, options: GitCheckoutOptions): Promise<void>;
|
|
89
92
|
fetch(workspace: string, remote?: string): Promise<void>;
|