@ganglion/xacpx 0.19.0 → 0.19.2
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 +64 -2
- package/dist/cli.js +123 -79
- 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
|
|
|
@@ -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;
|
|
@@ -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
|
@@ -5762,6 +5762,10 @@ var init_agent_templates = __esm(() => {
|
|
|
5762
5762
|
"grok-build": {
|
|
5763
5763
|
driver: "grok-build"
|
|
5764
5764
|
},
|
|
5765
|
+
hermes: {
|
|
5766
|
+
driver: "hermes",
|
|
5767
|
+
command: "hermes acp"
|
|
5768
|
+
},
|
|
5765
5769
|
iflow: {
|
|
5766
5770
|
driver: "iflow"
|
|
5767
5771
|
},
|
|
@@ -32775,6 +32779,25 @@ var init_quota_gated_reply_sink = __esm(() => {
|
|
|
32775
32779
|
ADAPTIVE_WINDOW_SCHEDULE_MS = [3000, 6000, 12000, 24000, 48000, 60000];
|
|
32776
32780
|
});
|
|
32777
32781
|
|
|
32782
|
+
// src/transport/serialized-callback-queue.ts
|
|
32783
|
+
function createSerializedCallbackQueue() {
|
|
32784
|
+
let chain = Promise.resolve();
|
|
32785
|
+
let firstError;
|
|
32786
|
+
return {
|
|
32787
|
+
enqueue(callback) {
|
|
32788
|
+
chain = chain.then(callback).catch((error2) => {
|
|
32789
|
+
firstError ??= error2;
|
|
32790
|
+
});
|
|
32791
|
+
},
|
|
32792
|
+
async drain() {
|
|
32793
|
+
await chain;
|
|
32794
|
+
},
|
|
32795
|
+
getError() {
|
|
32796
|
+
return firstError;
|
|
32797
|
+
}
|
|
32798
|
+
};
|
|
32799
|
+
}
|
|
32800
|
+
|
|
32778
32801
|
// src/transport/tool-event-mode.ts
|
|
32779
32802
|
function resolveToolEventMode(input) {
|
|
32780
32803
|
if (input?.toolEventMode !== undefined) {
|
|
@@ -32827,12 +32850,7 @@ class AcpxBridgeTransport {
|
|
|
32827
32850
|
reply,
|
|
32828
32851
|
...replyContext ? { replyContext } : {}
|
|
32829
32852
|
}) : null;
|
|
32830
|
-
|
|
32831
|
-
let segmentChain = Promise.resolve();
|
|
32832
|
-
let toolEventError;
|
|
32833
|
-
let toolEventChain = Promise.resolve();
|
|
32834
|
-
let thoughtError;
|
|
32835
|
-
let thoughtChain = Promise.resolve();
|
|
32853
|
+
const transcriptEvents = createSerializedCallbackQueue();
|
|
32836
32854
|
let planError;
|
|
32837
32855
|
let planChain = Promise.resolve();
|
|
32838
32856
|
let usageError;
|
|
@@ -32852,22 +32870,19 @@ class AcpxBridgeTransport {
|
|
|
32852
32870
|
}, (event) => {
|
|
32853
32871
|
if (event.type === "prompt.segment") {
|
|
32854
32872
|
const onSegment = options?.onSegment;
|
|
32855
|
-
|
|
32856
|
-
|
|
32857
|
-
|
|
32858
|
-
|
|
32859
|
-
|
|
32860
|
-
}
|
|
32861
|
-
sink2?.feedSegment(event.text);
|
|
32873
|
+
const segmentText = event.text;
|
|
32874
|
+
transcriptEvents.enqueue(async () => {
|
|
32875
|
+
const segmentResult = onSegment?.(segmentText);
|
|
32876
|
+
sink2?.feedSegment(segmentText);
|
|
32877
|
+
await segmentResult;
|
|
32878
|
+
});
|
|
32862
32879
|
return;
|
|
32863
32880
|
}
|
|
32864
32881
|
if (event.type === "prompt.tool_event") {
|
|
32865
32882
|
const onToolEvent = options?.onToolEvent;
|
|
32866
32883
|
if (onToolEvent) {
|
|
32867
32884
|
const toolEvent = event.event;
|
|
32868
|
-
|
|
32869
|
-
toolEventError ??= error2;
|
|
32870
|
-
});
|
|
32885
|
+
transcriptEvents.enqueue(() => onToolEvent(toolEvent));
|
|
32871
32886
|
}
|
|
32872
32887
|
return;
|
|
32873
32888
|
}
|
|
@@ -32875,9 +32890,7 @@ class AcpxBridgeTransport {
|
|
|
32875
32890
|
const onThought = options?.onThought;
|
|
32876
32891
|
if (onThought) {
|
|
32877
32892
|
const thoughtText = event.text;
|
|
32878
|
-
|
|
32879
|
-
thoughtError ??= error2;
|
|
32880
|
-
});
|
|
32893
|
+
transcriptEvents.enqueue(() => onThought(thoughtText));
|
|
32881
32894
|
}
|
|
32882
32895
|
return;
|
|
32883
32896
|
}
|
|
@@ -32912,9 +32925,7 @@ class AcpxBridgeTransport {
|
|
|
32912
32925
|
return;
|
|
32913
32926
|
}
|
|
32914
32927
|
});
|
|
32915
|
-
await
|
|
32916
|
-
await toolEventChain;
|
|
32917
|
-
await thoughtChain;
|
|
32928
|
+
await transcriptEvents.drain();
|
|
32918
32929
|
await planChain;
|
|
32919
32930
|
await usageChain;
|
|
32920
32931
|
await commandsChain;
|
|
@@ -32926,14 +32937,9 @@ class AcpxBridgeTransport {
|
|
|
32926
32937
|
throw deferred;
|
|
32927
32938
|
}
|
|
32928
32939
|
const summary = buildOverflowSummary(overflowCount);
|
|
32929
|
-
|
|
32930
|
-
|
|
32931
|
-
|
|
32932
|
-
if (toolEventError) {
|
|
32933
|
-
throw toolEventError;
|
|
32934
|
-
}
|
|
32935
|
-
if (thoughtError) {
|
|
32936
|
-
throw thoughtError;
|
|
32940
|
+
const transcriptError2 = transcriptEvents.getError();
|
|
32941
|
+
if (transcriptError2) {
|
|
32942
|
+
throw transcriptError2;
|
|
32937
32943
|
}
|
|
32938
32944
|
if (planError) {
|
|
32939
32945
|
throw planError;
|
|
@@ -32948,14 +32954,9 @@ class AcpxBridgeTransport {
|
|
|
32948
32954
|
|
|
32949
32955
|
${result.text}` : "" };
|
|
32950
32956
|
}
|
|
32951
|
-
|
|
32952
|
-
|
|
32953
|
-
|
|
32954
|
-
if (toolEventError) {
|
|
32955
|
-
throw toolEventError;
|
|
32956
|
-
}
|
|
32957
|
-
if (thoughtError) {
|
|
32958
|
-
throw thoughtError;
|
|
32957
|
+
const transcriptError = transcriptEvents.getError();
|
|
32958
|
+
if (transcriptError) {
|
|
32959
|
+
throw transcriptError;
|
|
32959
32960
|
}
|
|
32960
32961
|
if (planError) {
|
|
32961
32962
|
throw planError;
|
|
@@ -33756,6 +33757,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
33756
33757
|
let onCommands;
|
|
33757
33758
|
let rawStream = false;
|
|
33758
33759
|
let driver;
|
|
33760
|
+
let onBeforeActivityEvent;
|
|
33759
33761
|
if (options === undefined) {
|
|
33760
33762
|
toolEventMode = "text";
|
|
33761
33763
|
onToolEvent = undefined;
|
|
@@ -33770,6 +33772,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
33770
33772
|
onCommands = options.onCommands;
|
|
33771
33773
|
rawStream = options.rawStream ?? false;
|
|
33772
33774
|
driver = options.driver?.trim().toLowerCase() || undefined;
|
|
33775
|
+
onBeforeActivityEvent = options.onBeforeActivityEvent;
|
|
33773
33776
|
toolEventMode = resolveToolEventMode({
|
|
33774
33777
|
toolEventMode: options.mode,
|
|
33775
33778
|
onToolEvent
|
|
@@ -33782,10 +33785,14 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
33782
33785
|
pendingLine: "",
|
|
33783
33786
|
formatToolCalls,
|
|
33784
33787
|
emittedToolCallIds: new Set,
|
|
33788
|
+
positionedToolCallIds: new Set,
|
|
33785
33789
|
toolCalls: new Map,
|
|
33786
33790
|
toolEventMode,
|
|
33787
33791
|
driver,
|
|
33788
33792
|
rawStream,
|
|
33793
|
+
lastTextTail: "",
|
|
33794
|
+
activitySinceLastText: false,
|
|
33795
|
+
onBeforeActivityEvent,
|
|
33789
33796
|
onToolEvent,
|
|
33790
33797
|
onThought,
|
|
33791
33798
|
onPlan,
|
|
@@ -33828,6 +33835,14 @@ function parseStreamingChunks(state, line) {
|
|
|
33828
33835
|
if (!update)
|
|
33829
33836
|
return;
|
|
33830
33837
|
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
|
|
33838
|
+
const isInitialToolEvent = typeof update.toolCallId === "string" ? !state.positionedToolCallIds.has(update.toolCallId) : update.sessionUpdate === "tool_call";
|
|
33839
|
+
if (isInitialToolEvent) {
|
|
33840
|
+
if (update.toolCallId)
|
|
33841
|
+
state.positionedToolCallIds.add(update.toolCallId);
|
|
33842
|
+
markActivityBoundary(state);
|
|
33843
|
+
} else {
|
|
33844
|
+
flushBeforeActivityEvent(state);
|
|
33845
|
+
}
|
|
33831
33846
|
const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
|
|
33832
33847
|
const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
|
|
33833
33848
|
if (wantsStructured && state.onToolEvent) {
|
|
@@ -33876,6 +33891,7 @@ function parseStreamingChunks(state, line) {
|
|
|
33876
33891
|
if (isThoughtChunk) {
|
|
33877
33892
|
const chunk2 = update.content.text;
|
|
33878
33893
|
if (chunk2.length > 0) {
|
|
33894
|
+
markActivityBoundary(state);
|
|
33879
33895
|
state.onThought?.(chunk2);
|
|
33880
33896
|
}
|
|
33881
33897
|
return;
|
|
@@ -33884,10 +33900,22 @@ function parseStreamingChunks(state, line) {
|
|
|
33884
33900
|
if (!isMessageChunk)
|
|
33885
33901
|
return;
|
|
33886
33902
|
state.hasAgentMessage = true;
|
|
33887
|
-
|
|
33903
|
+
let chunk = update.content.text ?? "";
|
|
33888
33904
|
if (chunk.length === 0)
|
|
33889
33905
|
return;
|
|
33906
|
+
const messageId = typeof update.messageId === "string" && update.messageId.length > 0 ? update.messageId : undefined;
|
|
33907
|
+
const messageIdChanged = state.lastMessageId !== undefined && messageId !== undefined && state.lastMessageId !== messageId;
|
|
33908
|
+
const fallbackBoundary = state.activitySinceLastText && (state.lastMessageId === undefined || messageId === undefined) && endsWithSentenceTerminal(state.lastTextTail);
|
|
33909
|
+
if ((messageIdChanged || fallbackBoundary) && !hasParagraphBoundaryAtJoin(state.lastTextTail, chunk)) {
|
|
33910
|
+
chunk = `
|
|
33911
|
+
|
|
33912
|
+
${chunk}`;
|
|
33913
|
+
state.lastTextTail = "";
|
|
33914
|
+
}
|
|
33890
33915
|
state.buffer += chunk;
|
|
33916
|
+
state.lastMessageId = messageId;
|
|
33917
|
+
state.activitySinceLastText = false;
|
|
33918
|
+
state.lastTextTail = `${state.lastTextTail}${chunk}`.slice(-256);
|
|
33891
33919
|
if (state.rawStream)
|
|
33892
33920
|
return;
|
|
33893
33921
|
let boundary;
|
|
@@ -33901,6 +33929,24 @@ function parseStreamingChunks(state, line) {
|
|
|
33901
33929
|
}
|
|
33902
33930
|
}
|
|
33903
33931
|
}
|
|
33932
|
+
function endsWithSentenceTerminal(text) {
|
|
33933
|
+
return SENTENCE_TERMINAL_AT_END.test(text.trimEnd());
|
|
33934
|
+
}
|
|
33935
|
+
function hasParagraphBoundaryAtJoin(left, right) {
|
|
33936
|
+
const leftHasBoundary = PARAGRAPH_BOUNDARY_AT_END.test(left);
|
|
33937
|
+
const rightHasBoundary = PARAGRAPH_BOUNDARY_AT_START.test(right);
|
|
33938
|
+
const boundarySpansJoin = LINE_BREAK_AT_END.test(left) && LINE_BREAK_AT_START.test(right);
|
|
33939
|
+
const crlfBoundarySpansJoin = PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END.test(left) && right.startsWith(`
|
|
33940
|
+
`);
|
|
33941
|
+
return leftHasBoundary || rightHasBoundary || boundarySpansJoin || crlfBoundarySpansJoin;
|
|
33942
|
+
}
|
|
33943
|
+
function markActivityBoundary(state) {
|
|
33944
|
+
flushBeforeActivityEvent(state);
|
|
33945
|
+
state.activitySinceLastText = state.hasAgentMessage;
|
|
33946
|
+
}
|
|
33947
|
+
function flushBeforeActivityEvent(state) {
|
|
33948
|
+
state.onBeforeActivityEvent?.();
|
|
33949
|
+
}
|
|
33904
33950
|
function formatToolCallEvent(update, sessionUpdate) {
|
|
33905
33951
|
if (!update)
|
|
33906
33952
|
return null;
|
|
@@ -34162,10 +34208,16 @@ function isGenericToolTitle(kind, title) {
|
|
|
34162
34208
|
}
|
|
34163
34209
|
return false;
|
|
34164
34210
|
}
|
|
34165
|
-
var USAGE_BREAKDOWN_FIELDS;
|
|
34211
|
+
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
34212
|
var init_streaming_prompt = __esm(() => {
|
|
34167
34213
|
init_background_followup();
|
|
34168
34214
|
init_tool_kind_emoji();
|
|
34215
|
+
SENTENCE_TERMINAL_AT_END = /(?:\p{Sentence_Terminal}|…|⋯)[\p{Close_Punctuation}\p{Final_Punctuation}"“”‘’*_~`]*$/u;
|
|
34216
|
+
PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r?\n[\t ]*$/;
|
|
34217
|
+
PARAGRAPH_BOUNDARY_AT_START = /^[\t ]*\r?\n[\t ]*\r?\n/;
|
|
34218
|
+
LINE_BREAK_AT_END = /\r?\n[\t ]*$/;
|
|
34219
|
+
LINE_BREAK_AT_START = /^[\t ]*\r?\n/;
|
|
34220
|
+
PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r$/;
|
|
34169
34221
|
USAGE_BREAKDOWN_FIELDS = [
|
|
34170
34222
|
["inputTokens", ["inputTokens", "input_tokens"]],
|
|
34171
34223
|
["outputTokens", ["outputTokens", "output_tokens"]],
|
|
@@ -35074,12 +35126,7 @@ ${baseText}` : "" };
|
|
|
35074
35126
|
let stdout2 = "";
|
|
35075
35127
|
let stderr = "";
|
|
35076
35128
|
let lastReplyAt = now();
|
|
35077
|
-
|
|
35078
|
-
let segmentError;
|
|
35079
|
-
let toolEventChain = Promise.resolve();
|
|
35080
|
-
let toolEventError;
|
|
35081
|
-
let thoughtChain = Promise.resolve();
|
|
35082
|
-
let thoughtError;
|
|
35129
|
+
const transcriptEvents = createSerializedCallbackQueue();
|
|
35083
35130
|
let planChain = Promise.resolve();
|
|
35084
35131
|
let planError;
|
|
35085
35132
|
let usageChain = Promise.resolve();
|
|
@@ -35091,22 +35138,22 @@ ${baseText}` : "" };
|
|
|
35091
35138
|
const userOnPlan = onPlan;
|
|
35092
35139
|
const userOnUsage = onUsage;
|
|
35093
35140
|
const userOnCommands = onCommands;
|
|
35141
|
+
let flushPendingText = () => {};
|
|
35094
35142
|
const state = createStreamingPromptState(formatToolCalls, {
|
|
35095
35143
|
mode: toolEventMode,
|
|
35096
35144
|
driver,
|
|
35097
35145
|
rawStream,
|
|
35146
|
+
onBeforeActivityEvent: () => {
|
|
35147
|
+
flushPendingText();
|
|
35148
|
+
},
|
|
35098
35149
|
...userOnToolEvent ? {
|
|
35099
35150
|
onToolEvent: (event) => {
|
|
35100
|
-
|
|
35101
|
-
toolEventError ??= error2;
|
|
35102
|
-
});
|
|
35151
|
+
transcriptEvents.enqueue(() => userOnToolEvent(event));
|
|
35103
35152
|
}
|
|
35104
35153
|
} : {},
|
|
35105
35154
|
...userOnThought ? {
|
|
35106
35155
|
onThought: (chunk) => {
|
|
35107
|
-
|
|
35108
|
-
thoughtError ??= error2;
|
|
35109
|
-
});
|
|
35156
|
+
transcriptEvents.enqueue(() => userOnThought(chunk));
|
|
35110
35157
|
}
|
|
35111
35158
|
} : {},
|
|
35112
35159
|
...userOnPlan ? {
|
|
@@ -35136,12 +35183,11 @@ ${baseText}` : "" };
|
|
|
35136
35183
|
...replyContext ? { replyContext } : {}
|
|
35137
35184
|
}) : null;
|
|
35138
35185
|
const feedSegment = (segment) => {
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
}
|
|
35144
|
-
sink2?.feedSegment(segment);
|
|
35186
|
+
transcriptEvents.enqueue(async () => {
|
|
35187
|
+
const segmentResult = onSegment?.(segment);
|
|
35188
|
+
sink2?.feedSegment(segment);
|
|
35189
|
+
await segmentResult;
|
|
35190
|
+
});
|
|
35145
35191
|
lastReplyAt = now();
|
|
35146
35192
|
};
|
|
35147
35193
|
const flushBuffer = () => {
|
|
@@ -35151,6 +35197,12 @@ ${baseText}` : "" };
|
|
|
35151
35197
|
feedSegment(remaining);
|
|
35152
35198
|
}
|
|
35153
35199
|
};
|
|
35200
|
+
flushPendingText = () => {
|
|
35201
|
+
for (const segment of state.segments.splice(0)) {
|
|
35202
|
+
feedSegment(segment);
|
|
35203
|
+
}
|
|
35204
|
+
flushBuffer();
|
|
35205
|
+
};
|
|
35154
35206
|
const timer = setIntervalFn(() => {
|
|
35155
35207
|
if (state.buffer.trim().length > 0 && now() - lastReplyAt >= maxSegmentWaitMs) {
|
|
35156
35208
|
flushBuffer();
|
|
@@ -35177,31 +35229,23 @@ ${baseText}` : "" };
|
|
|
35177
35229
|
if (remaining.length > 0) {
|
|
35178
35230
|
feedSegment(remaining);
|
|
35179
35231
|
}
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
]).then(() => {
|
|
35232
|
+
(async () => {
|
|
35233
|
+
await Promise.all([
|
|
35234
|
+
transcriptEvents.drain(),
|
|
35235
|
+
planChain,
|
|
35236
|
+
usageChain,
|
|
35237
|
+
commandsChain
|
|
35238
|
+
]);
|
|
35239
|
+
const { overflowCount } = sink2?.finalize() ?? { overflowCount: 0 };
|
|
35240
|
+
await (sink2?.drain({ timeoutMs: 30000 }) ?? Promise.resolve());
|
|
35190
35241
|
const deferred = sink2?.getPendingError();
|
|
35191
35242
|
if (deferred) {
|
|
35192
35243
|
reject(deferred);
|
|
35193
35244
|
return;
|
|
35194
35245
|
}
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
}
|
|
35199
|
-
if (toolEventError) {
|
|
35200
|
-
reject(toolEventError);
|
|
35201
|
-
return;
|
|
35202
|
-
}
|
|
35203
|
-
if (thoughtError) {
|
|
35204
|
-
reject(thoughtError);
|
|
35246
|
+
const transcriptError = transcriptEvents.getError();
|
|
35247
|
+
if (transcriptError) {
|
|
35248
|
+
reject(transcriptError);
|
|
35205
35249
|
return;
|
|
35206
35250
|
}
|
|
35207
35251
|
if (planError) {
|
|
@@ -35220,7 +35264,7 @@ ${baseText}` : "" };
|
|
|
35220
35264
|
result: { code: code ?? 1, stdout: stdout2, stderr },
|
|
35221
35265
|
overflowCount
|
|
35222
35266
|
});
|
|
35223
|
-
}).catch((error2) => {
|
|
35267
|
+
})().catch((error2) => {
|
|
35224
35268
|
reject(error2);
|
|
35225
35269
|
});
|
|
35226
35270
|
});
|