@ganglion/xacpx 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/bridge/bridge-main.js +84 -4
- package/dist/cli.js +315 -89
- package/dist/commands/handlers/session-handler.d.ts +3 -9
- package/dist/commands/router-types.d.ts +2 -5
- package/dist/control/control-event-bus.d.ts +8 -0
- package/dist/control/control-service.d.ts +15 -0
- package/dist/control/upload-store.d.ts +28 -0
- package/dist/transport/types.d.ts +37 -4
- package/dist/weixin/agent/interface.d.ts +4 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -519,19 +519,27 @@ For more filtering, aliases, and troubleshooting, see [docs/native-sessions.md](
|
|
|
519
519
|
|
|
520
520
|
If you run several xacpx instances and want to drive them all from one browser dashboard, you can 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 — chat, scheduled tasks, and orchestration — from one place. Streaming agent replies render as markdown, and the layout works on mobile.
|
|
521
521
|
|
|
522
|
-
|
|
522
|
+
The hub ships as an npm package (`@ganglion/xacpx-relay`) with the dashboard **bundled in** — no separate build. It serves everything on a single port (HTTP API + dashboard + the instance WebSocket gateway), and authentication is a single **access token** used for both web login and connector pairing.
|
|
523
523
|
|
|
524
524
|
```bash
|
|
525
|
-
#
|
|
526
|
-
|
|
527
|
-
bun run build:relay && bun run build:relay-web
|
|
525
|
+
# 1. On the hub host: install (dashboard is bundled — nothing else to build)
|
|
526
|
+
npm i -g @ganglion/xacpx-relay
|
|
528
527
|
|
|
529
|
-
#
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
528
|
+
# 2. Mint an access token (DB auto-created at ~/.xacpx-relay/relay.db)
|
|
529
|
+
xacpx-relay add token
|
|
530
|
+
# → prints the token once; use it to log into the dashboard AND to pair connectors
|
|
531
|
+
|
|
532
|
+
# 3. Start the hub (defaults: --host 0.0.0.0 --http-port 8787, dashboard auto-detected)
|
|
533
|
+
xacpx-relay start
|
|
534
|
+
|
|
535
|
+
# 4. On each instance host: add the connector channel and point it at the hub
|
|
536
|
+
xacpx plugin add @ganglion/xacpx-channel-relay # requires xacpx >= 0.11.0
|
|
537
|
+
xacpx channel add relay --url wss://relay.example.com --token <access-token> --name my-box
|
|
538
|
+
xacpx restart
|
|
533
539
|
```
|
|
534
540
|
|
|
541
|
+
In production, terminate TLS at a reverse proxy in front of the single port and have instances dial `wss://`. There's no `stop`/`status` subcommand — manage the process with systemd/pm2/Docker (`Ctrl-C`/`SIGTERM` to stop); update with `xacpx-relay update`.
|
|
542
|
+
|
|
535
543
|
Full walkthrough — pairing instances, TLS/reverse-proxy, systemd, backups, troubleshooting: **[Self-Hosting the Relay Hub](https://gadzan.github.io/xacpx/guide/relay-self-hosting)** (or [docs/relay-deployment.md](./docs/relay-deployment.md) for the terse runbook).
|
|
536
544
|
|
|
537
545
|
## Config and runtime files
|
|
@@ -72,6 +72,10 @@ function encodeBridgePromptUsageEvent(event) {
|
|
|
72
72
|
return `${JSON.stringify(event)}
|
|
73
73
|
`;
|
|
74
74
|
}
|
|
75
|
+
function encodeBridgePromptCommandsEvent(event) {
|
|
76
|
+
return `${JSON.stringify(event)}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
75
79
|
function encodeBridgeSessionProgressEvent(event) {
|
|
76
80
|
return `${JSON.stringify(event)}
|
|
77
81
|
`;
|
|
@@ -429,6 +433,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
429
433
|
let onThought;
|
|
430
434
|
let onPlan;
|
|
431
435
|
let onUsage;
|
|
436
|
+
let onCommands;
|
|
432
437
|
let rawStream = false;
|
|
433
438
|
if (options === undefined) {
|
|
434
439
|
toolEventMode = "text";
|
|
@@ -441,6 +446,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
441
446
|
onThought = options.onThought;
|
|
442
447
|
onPlan = options.onPlan;
|
|
443
448
|
onUsage = options.onUsage;
|
|
449
|
+
onCommands = options.onCommands;
|
|
444
450
|
rawStream = options.rawStream ?? false;
|
|
445
451
|
toolEventMode = resolveToolEventMode({
|
|
446
452
|
toolEventMode: options.mode,
|
|
@@ -460,6 +466,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
460
466
|
onThought,
|
|
461
467
|
onPlan,
|
|
462
468
|
onUsage,
|
|
469
|
+
onCommands,
|
|
463
470
|
finalize() {
|
|
464
471
|
if (this.pendingLine.trim().length > 0) {
|
|
465
472
|
parseStreamingChunks(this, this.pendingLine);
|
|
@@ -527,8 +534,17 @@ function parseStreamingChunks(state, line) {
|
|
|
527
534
|
if (update.sessionUpdate === "usage_update") {
|
|
528
535
|
const used = typeof update.used === "number" && Number.isFinite(update.used) ? update.used : undefined;
|
|
529
536
|
const size = typeof update.size === "number" && Number.isFinite(update.size) ? update.size : undefined;
|
|
530
|
-
if (used !== undefined && size !== undefined && size > 0)
|
|
531
|
-
|
|
537
|
+
if (used !== undefined && size !== undefined && size > 0) {
|
|
538
|
+
const cost = normalizeUsageCost(update.cost);
|
|
539
|
+
const breakdown = normalizeUsageBreakdown(update._meta?.usage);
|
|
540
|
+
state.onUsage?.({ used, size, ...cost ? { cost } : {}, ...breakdown ? { breakdown } : {} });
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (update.sessionUpdate === "available_commands_update") {
|
|
545
|
+
if (Array.isArray(update.availableCommands)) {
|
|
546
|
+
state.onCommands?.(normalizeAgentCommands(update.availableCommands));
|
|
547
|
+
}
|
|
532
548
|
return;
|
|
533
549
|
}
|
|
534
550
|
const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
@@ -695,6 +711,52 @@ function readFirstStringArray(record, keys) {
|
|
|
695
711
|
}
|
|
696
712
|
return;
|
|
697
713
|
}
|
|
714
|
+
function asFiniteNumber(value) {
|
|
715
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
716
|
+
}
|
|
717
|
+
function firstFiniteNumber(record, keys) {
|
|
718
|
+
for (const key of keys) {
|
|
719
|
+
const n = asFiniteNumber(record[key]);
|
|
720
|
+
if (n !== undefined)
|
|
721
|
+
return n;
|
|
722
|
+
}
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
function normalizeUsageBreakdown(value) {
|
|
726
|
+
if (!isRecord(value))
|
|
727
|
+
return;
|
|
728
|
+
const out = {};
|
|
729
|
+
for (const [key, aliases] of USAGE_BREAKDOWN_FIELDS) {
|
|
730
|
+
const n = firstFiniteNumber(value, aliases);
|
|
731
|
+
if (n !== undefined)
|
|
732
|
+
out[key] = n;
|
|
733
|
+
}
|
|
734
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
735
|
+
}
|
|
736
|
+
function normalizeUsageCost(value) {
|
|
737
|
+
if (!isRecord(value))
|
|
738
|
+
return;
|
|
739
|
+
const amount = asFiniteNumber(value.amount);
|
|
740
|
+
const currency = readString(value, "currency");
|
|
741
|
+
if (amount === undefined && !currency)
|
|
742
|
+
return;
|
|
743
|
+
return { ...amount !== undefined ? { amount } : {}, ...currency ? { currency } : {} };
|
|
744
|
+
}
|
|
745
|
+
function normalizeAgentCommands(value) {
|
|
746
|
+
if (!Array.isArray(value))
|
|
747
|
+
return [];
|
|
748
|
+
const out = [];
|
|
749
|
+
for (const entry of value) {
|
|
750
|
+
if (!isRecord(entry))
|
|
751
|
+
continue;
|
|
752
|
+
const name = readString(entry, "name");
|
|
753
|
+
if (!name)
|
|
754
|
+
continue;
|
|
755
|
+
const description = readString(entry, "description");
|
|
756
|
+
out.push({ name, ...description ? { description } : {}, hasInput: entry.input != null });
|
|
757
|
+
}
|
|
758
|
+
return out;
|
|
759
|
+
}
|
|
698
760
|
function isRecord(value) {
|
|
699
761
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
700
762
|
}
|
|
@@ -720,8 +782,17 @@ function isGenericToolTitle(kind, title) {
|
|
|
720
782
|
}
|
|
721
783
|
return false;
|
|
722
784
|
}
|
|
785
|
+
var USAGE_BREAKDOWN_FIELDS;
|
|
723
786
|
var init_streaming_prompt = __esm(() => {
|
|
724
787
|
init_tool_kind_emoji();
|
|
788
|
+
USAGE_BREAKDOWN_FIELDS = [
|
|
789
|
+
["inputTokens", ["inputTokens", "input_tokens"]],
|
|
790
|
+
["outputTokens", ["outputTokens", "output_tokens"]],
|
|
791
|
+
["cachedReadTokens", ["cachedReadTokens", "cacheReadInputTokens", "cache_read_input_tokens"]],
|
|
792
|
+
["cachedWriteTokens", ["cachedWriteTokens", "cacheCreationInputTokens", "cache_creation_input_tokens"]],
|
|
793
|
+
["thoughtTokens", ["thoughtTokens", "thought_tokens"]],
|
|
794
|
+
["totalTokens", ["totalTokens", "total_tokens"]]
|
|
795
|
+
];
|
|
725
796
|
});
|
|
726
797
|
|
|
727
798
|
// src/recovery/discover-parent-package-paths.ts
|
|
@@ -4049,7 +4120,8 @@ async function runStreamingPrompt(command, args, onEvent, options = {}) {
|
|
|
4049
4120
|
...onEvent && (toolEventMode === "structured" || toolEventMode === "both") ? { onToolEvent: (toolEvent) => onEvent({ type: "prompt.tool_event", event: toolEvent }) } : {},
|
|
4050
4121
|
...onEvent ? { onThought: (chunk) => onEvent({ type: "prompt.thought", text: chunk }) } : {},
|
|
4051
4122
|
...onEvent ? { onPlan: (entries) => onEvent({ type: "prompt.plan", entries }) } : {},
|
|
4052
|
-
...onEvent ? { onUsage: (usage) => onEvent({ type: "prompt.usage", used: usage.used, size: usage.size }) } : {}
|
|
4123
|
+
...onEvent ? { onUsage: (usage) => onEvent({ type: "prompt.usage", used: usage.used, size: usage.size, ...usage.cost ? { cost: usage.cost } : {}, ...usage.breakdown ? { breakdown: usage.breakdown } : {} }) } : {},
|
|
4124
|
+
...onEvent ? { onCommands: (commands) => onEvent({ type: "prompt.commands", commands }) } : {}
|
|
4053
4125
|
});
|
|
4054
4126
|
let lastReplyAt = now();
|
|
4055
4127
|
const flushBuffer = () => {
|
|
@@ -4347,7 +4419,15 @@ class BridgeServer {
|
|
|
4347
4419
|
id: requestId,
|
|
4348
4420
|
event: "prompt.usage",
|
|
4349
4421
|
used: event.used,
|
|
4350
|
-
size: event.size
|
|
4422
|
+
size: event.size,
|
|
4423
|
+
...event.cost ? { cost: event.cost } : {},
|
|
4424
|
+
...event.breakdown ? { breakdown: event.breakdown } : {}
|
|
4425
|
+
}));
|
|
4426
|
+
} else if (event.type === "prompt.commands") {
|
|
4427
|
+
writeLine?.(encodeBridgePromptCommandsEvent({
|
|
4428
|
+
id: requestId,
|
|
4429
|
+
event: "prompt.commands",
|
|
4430
|
+
commands: event.commands
|
|
4351
4431
|
}));
|
|
4352
4432
|
}
|
|
4353
4433
|
});
|
package/dist/cli.js
CHANGED
|
@@ -22377,7 +22377,7 @@ async function handleSessionArchive(context, chatKey, alias, archive) {
|
|
|
22377
22377
|
await archive(internalAlias);
|
|
22378
22378
|
return { text: t().session.sessionArchived(alias) };
|
|
22379
22379
|
}
|
|
22380
|
-
async function promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage) {
|
|
22380
|
+
async function promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands) {
|
|
22381
22381
|
if (session3.archived) {
|
|
22382
22382
|
await context.sessions.setArchived(session3.alias, false);
|
|
22383
22383
|
}
|
|
@@ -22412,7 +22412,7 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
|
|
|
22412
22412
|
const { promptText, taskIds, groupIds, claimHumanReply } = await preparePromptWithFallback(context, session3, chatKey, text, replyContextToken, accountId);
|
|
22413
22413
|
try {
|
|
22414
22414
|
const replyContext = transportReply && context.quota && getChannelIdFromChatKey(chatKey) === "weixin" ? { chatKey, quota: context.quota } : undefined;
|
|
22415
|
-
const result = await context.interaction.promptTransportSession(session3, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage);
|
|
22415
|
+
const result = await context.interaction.promptTransportSession(session3, promptText, transportReply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage, onCommands);
|
|
22416
22416
|
if (claimHumanReply) {
|
|
22417
22417
|
try {
|
|
22418
22418
|
await context.orchestration?.claimActiveHumanReply?.(claimHumanReply);
|
|
@@ -22432,23 +22432,23 @@ async function promptWithSession(context, session3, chatKey, text, reply, replyC
|
|
|
22432
22432
|
throw error2;
|
|
22433
22433
|
}
|
|
22434
22434
|
}
|
|
22435
|
-
async function handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage) {
|
|
22435
|
+
async function handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands) {
|
|
22436
22436
|
try {
|
|
22437
|
-
return await promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage);
|
|
22437
|
+
return await promptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
|
|
22438
22438
|
} catch (error2) {
|
|
22439
22439
|
const recovered = await context.recovery.tryRecoverMissingSession(session3, error2);
|
|
22440
22440
|
if (recovered) {
|
|
22441
|
-
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage);
|
|
22441
|
+
return await promptWithSession(context, recovered, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
|
|
22442
22442
|
}
|
|
22443
22443
|
return context.recovery.renderTransportError(session3, error2);
|
|
22444
22444
|
}
|
|
22445
22445
|
}
|
|
22446
|
-
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage) {
|
|
22446
|
+
async function handlePrompt(context, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands) {
|
|
22447
22447
|
const session3 = metadata?.boundSessionAlias ? context.sessions.getResolvedSessionByInternalAlias(metadata.boundSessionAlias) : await context.sessions.getCurrentSession(chatKey);
|
|
22448
22448
|
if (!session3) {
|
|
22449
22449
|
return { text: t().session.noCurrent };
|
|
22450
22450
|
}
|
|
22451
|
-
return await handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage);
|
|
22451
|
+
return await handlePromptWithSession(context, session3, chatKey, text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
|
|
22452
22452
|
}
|
|
22453
22453
|
function toCoordinatorRouteChatMetadata(metadata) {
|
|
22454
22454
|
if (!metadata) {
|
|
@@ -24707,7 +24707,7 @@ class CommandRouter {
|
|
|
24707
24707
|
this.logger = logger2 ?? createNoopAppLogger();
|
|
24708
24708
|
this.activeTurns = activeTurns;
|
|
24709
24709
|
}
|
|
24710
|
-
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage) {
|
|
24710
|
+
async handle(chatKey, input, reply, replyContextToken, accountId, media, metadata, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage, onCommands) {
|
|
24711
24711
|
const startedAt = Date.now();
|
|
24712
24712
|
let command = parseCommand(input);
|
|
24713
24713
|
if (metadata?.channel === "control" && command.kind !== "prompt") {
|
|
@@ -24869,16 +24869,16 @@ class CommandRouter {
|
|
|
24869
24869
|
...this.sessions.resolveSession(descriptor.alias, descriptor.agent, descriptor.workspace, descriptor.transportSession),
|
|
24870
24870
|
transient: true
|
|
24871
24871
|
};
|
|
24872
|
-
return await handlePromptWithSession(sessionContext, transientSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage);
|
|
24872
|
+
return await handlePromptWithSession(sessionContext, transientSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
|
|
24873
24873
|
}
|
|
24874
24874
|
if (metadata?.scheduledSessionAlias) {
|
|
24875
24875
|
const scheduledSession = await this.sessions.getSession(metadata.scheduledSessionAlias);
|
|
24876
24876
|
if (!scheduledSession) {
|
|
24877
24877
|
throw new Error(`session "${metadata.scheduledSessionAlias}" not found for scheduled prompt`);
|
|
24878
24878
|
}
|
|
24879
|
-
return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage);
|
|
24879
|
+
return await handlePromptWithSession(sessionContext, scheduledSession, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
|
|
24880
24880
|
}
|
|
24881
|
-
return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage);
|
|
24881
|
+
return await handlePrompt(sessionContext, chatKey, command.text, reply, replyContextToken, accountId, media, abortSignal, onToolEvent, onThought, perfSpan, metadata, onPlan, onUsage, onCommands);
|
|
24882
24882
|
}
|
|
24883
24883
|
}
|
|
24884
24884
|
});
|
|
@@ -25102,7 +25102,7 @@ class CommandRouter {
|
|
|
25102
25102
|
setModelTransportSession: (session3, modelId) => this.setModelTransportSession(session3, modelId),
|
|
25103
25103
|
getModelTransportSession: (session3) => this.getModelTransportSession(session3),
|
|
25104
25104
|
cancelTransportSession: (session3) => this.cancelTransportSession(session3),
|
|
25105
|
-
promptTransportSession: (session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride, onPlan, onUsage) => this.promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride ?? perfSpan, onPlan, onUsage)
|
|
25105
|
+
promptTransportSession: (session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride, onPlan, onUsage, onCommands) => this.promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpanOverride ?? perfSpan, onPlan, onUsage, onCommands)
|
|
25106
25106
|
};
|
|
25107
25107
|
}
|
|
25108
25108
|
createSessionRenderRecoveryOps() {
|
|
@@ -25281,7 +25281,7 @@ class CommandRouter {
|
|
|
25281
25281
|
async checkTransportSession(session3) {
|
|
25282
25282
|
return await this.measureTransportCall("has_session", session3, () => this.transport.hasSession(session3));
|
|
25283
25283
|
}
|
|
25284
|
-
async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage) {
|
|
25284
|
+
async promptTransportSession(session3, text, reply, replyContext, media, abortSignal, onToolEvent, onThought, perfSpan, onPlan, onUsage, onCommands) {
|
|
25285
25285
|
session3.mcpCoordinatorSession ??= stableCoordinatorSession(session3.transportSession);
|
|
25286
25286
|
let done = false;
|
|
25287
25287
|
let abortRequested = false;
|
|
@@ -25340,7 +25340,8 @@ class CommandRouter {
|
|
|
25340
25340
|
...onToolEvent ? { onToolEvent } : {},
|
|
25341
25341
|
...onThought ? { onThought } : {},
|
|
25342
25342
|
...onPlan ? { onPlan } : {},
|
|
25343
|
-
...onUsage ? { onUsage } : {}
|
|
25343
|
+
...onUsage ? { onUsage } : {},
|
|
25344
|
+
...onCommands ? { onCommands } : {}
|
|
25344
25345
|
}));
|
|
25345
25346
|
} catch (error2) {
|
|
25346
25347
|
localOutcome = isAbortError2(error2) || abortRequested ? "aborted" : "error";
|
|
@@ -25526,7 +25527,7 @@ class ConsoleAgent {
|
|
|
25526
25527
|
...m.fileName ? { fileName: m.fileName } : {}
|
|
25527
25528
|
})) : undefined;
|
|
25528
25529
|
request.perfSpan?.mark("agent.dispatched");
|
|
25529
|
-
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.onThought, request.perfSpan, request.onPlan, request.onUsage);
|
|
25530
|
+
return await this.router.handle(request.conversationId, request.text, request.reply, request.replyContextToken, request.accountId, promptMedia, request.metadata, request.abortSignal, request.onToolEvent, request.onThought, request.perfSpan, request.onPlan, request.onUsage, request.onCommands);
|
|
25530
25531
|
}
|
|
25531
25532
|
isKnownCommand(text) {
|
|
25532
25533
|
return isKnownXacpxCommandText(text);
|
|
@@ -30607,6 +30608,10 @@ function encodeBridgePromptUsageEvent(event) {
|
|
|
30607
30608
|
return `${JSON.stringify(event)}
|
|
30608
30609
|
`;
|
|
30609
30610
|
}
|
|
30611
|
+
function encodeBridgePromptCommandsEvent(event) {
|
|
30612
|
+
return `${JSON.stringify(event)}
|
|
30613
|
+
`;
|
|
30614
|
+
}
|
|
30610
30615
|
function encodeBridgeSessionProgressEvent(event) {
|
|
30611
30616
|
return `${JSON.stringify(event)}
|
|
30612
30617
|
`;
|
|
@@ -30913,7 +30918,14 @@ class AcpxBridgeClient {
|
|
|
30913
30918
|
pending.onEvent?.({
|
|
30914
30919
|
type: "prompt.usage",
|
|
30915
30920
|
used: message.used,
|
|
30916
|
-
size: message.size
|
|
30921
|
+
size: message.size,
|
|
30922
|
+
...message.cost ? { cost: message.cost } : {},
|
|
30923
|
+
...message.breakdown ? { breakdown: message.breakdown } : {}
|
|
30924
|
+
});
|
|
30925
|
+
} else if (message.event === "prompt.commands") {
|
|
30926
|
+
pending.onEvent?.({
|
|
30927
|
+
type: "prompt.commands",
|
|
30928
|
+
commands: message.commands
|
|
30917
30929
|
});
|
|
30918
30930
|
} else if (message.event === "session.progress") {
|
|
30919
30931
|
pending.onEvent?.({
|
|
@@ -31376,6 +31388,8 @@ class AcpxBridgeTransport {
|
|
|
31376
31388
|
let planChain = Promise.resolve();
|
|
31377
31389
|
let usageError;
|
|
31378
31390
|
let usageChain = Promise.resolve();
|
|
31391
|
+
let commandsError;
|
|
31392
|
+
let commandsChain = Promise.resolve();
|
|
31379
31393
|
let toolEventMode = resolveToolEventMode(options);
|
|
31380
31394
|
if ((toolEventMode === "structured" || toolEventMode === "both") && !options?.onToolEvent) {
|
|
31381
31395
|
toolEventMode = "text";
|
|
@@ -31431,19 +31445,30 @@ class AcpxBridgeTransport {
|
|
|
31431
31445
|
if (event.type === "prompt.usage") {
|
|
31432
31446
|
const onUsage = options?.onUsage;
|
|
31433
31447
|
if (onUsage) {
|
|
31434
|
-
const usage = { used: event.used, size: event.size };
|
|
31448
|
+
const usage = { used: event.used, size: event.size, ...event.cost ? { cost: event.cost } : {}, ...event.breakdown ? { breakdown: event.breakdown } : {} };
|
|
31435
31449
|
usageChain = usageChain.then(() => onUsage(usage)).catch((error2) => {
|
|
31436
31450
|
usageError ??= error2;
|
|
31437
31451
|
});
|
|
31438
31452
|
}
|
|
31439
31453
|
return;
|
|
31440
31454
|
}
|
|
31455
|
+
if (event.type === "prompt.commands") {
|
|
31456
|
+
const onCommands = options?.onCommands;
|
|
31457
|
+
if (onCommands) {
|
|
31458
|
+
const commands = event.commands;
|
|
31459
|
+
commandsChain = commandsChain.then(() => onCommands(commands)).catch((error2) => {
|
|
31460
|
+
commandsError ??= error2;
|
|
31461
|
+
});
|
|
31462
|
+
}
|
|
31463
|
+
return;
|
|
31464
|
+
}
|
|
31441
31465
|
});
|
|
31442
31466
|
await segmentChain;
|
|
31443
31467
|
await toolEventChain;
|
|
31444
31468
|
await thoughtChain;
|
|
31445
31469
|
await planChain;
|
|
31446
31470
|
await usageChain;
|
|
31471
|
+
await commandsChain;
|
|
31447
31472
|
if (sink) {
|
|
31448
31473
|
const { overflowCount } = sink.finalize();
|
|
31449
31474
|
await sink.drain({ timeoutMs: 30000 });
|
|
@@ -31467,6 +31492,9 @@ class AcpxBridgeTransport {
|
|
|
31467
31492
|
if (usageError) {
|
|
31468
31493
|
throw usageError;
|
|
31469
31494
|
}
|
|
31495
|
+
if (commandsError) {
|
|
31496
|
+
throw commandsError;
|
|
31497
|
+
}
|
|
31470
31498
|
return { text: summary ? `${summary}
|
|
31471
31499
|
|
|
31472
31500
|
${result.text}` : "" };
|
|
@@ -31486,6 +31514,9 @@ ${result.text}` : "" };
|
|
|
31486
31514
|
if (usageError) {
|
|
31487
31515
|
throw usageError;
|
|
31488
31516
|
}
|
|
31517
|
+
if (commandsError) {
|
|
31518
|
+
throw commandsError;
|
|
31519
|
+
}
|
|
31489
31520
|
return result;
|
|
31490
31521
|
}
|
|
31491
31522
|
async setMode(session3, modeId) {
|
|
@@ -31695,6 +31726,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
31695
31726
|
let onThought;
|
|
31696
31727
|
let onPlan;
|
|
31697
31728
|
let onUsage;
|
|
31729
|
+
let onCommands;
|
|
31698
31730
|
let rawStream = false;
|
|
31699
31731
|
if (options === undefined) {
|
|
31700
31732
|
toolEventMode = "text";
|
|
@@ -31707,6 +31739,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
31707
31739
|
onThought = options.onThought;
|
|
31708
31740
|
onPlan = options.onPlan;
|
|
31709
31741
|
onUsage = options.onUsage;
|
|
31742
|
+
onCommands = options.onCommands;
|
|
31710
31743
|
rawStream = options.rawStream ?? false;
|
|
31711
31744
|
toolEventMode = resolveToolEventMode({
|
|
31712
31745
|
toolEventMode: options.mode,
|
|
@@ -31726,6 +31759,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
|
|
|
31726
31759
|
onThought,
|
|
31727
31760
|
onPlan,
|
|
31728
31761
|
onUsage,
|
|
31762
|
+
onCommands,
|
|
31729
31763
|
finalize() {
|
|
31730
31764
|
if (this.pendingLine.trim().length > 0) {
|
|
31731
31765
|
parseStreamingChunks(this, this.pendingLine);
|
|
@@ -31793,8 +31827,17 @@ function parseStreamingChunks(state, line) {
|
|
|
31793
31827
|
if (update.sessionUpdate === "usage_update") {
|
|
31794
31828
|
const used = typeof update.used === "number" && Number.isFinite(update.used) ? update.used : undefined;
|
|
31795
31829
|
const size = typeof update.size === "number" && Number.isFinite(update.size) ? update.size : undefined;
|
|
31796
|
-
if (used !== undefined && size !== undefined && size > 0)
|
|
31797
|
-
|
|
31830
|
+
if (used !== undefined && size !== undefined && size > 0) {
|
|
31831
|
+
const cost = normalizeUsageCost(update.cost);
|
|
31832
|
+
const breakdown = normalizeUsageBreakdown(update._meta?.usage);
|
|
31833
|
+
state.onUsage?.({ used, size, ...cost ? { cost } : {}, ...breakdown ? { breakdown } : {} });
|
|
31834
|
+
}
|
|
31835
|
+
return;
|
|
31836
|
+
}
|
|
31837
|
+
if (update.sessionUpdate === "available_commands_update") {
|
|
31838
|
+
if (Array.isArray(update.availableCommands)) {
|
|
31839
|
+
state.onCommands?.(normalizeAgentCommands(update.availableCommands));
|
|
31840
|
+
}
|
|
31798
31841
|
return;
|
|
31799
31842
|
}
|
|
31800
31843
|
const isThoughtChunk = update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string";
|
|
@@ -31961,6 +32004,52 @@ function readFirstStringArray(record3, keys) {
|
|
|
31961
32004
|
}
|
|
31962
32005
|
return;
|
|
31963
32006
|
}
|
|
32007
|
+
function asFiniteNumber(value) {
|
|
32008
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
32009
|
+
}
|
|
32010
|
+
function firstFiniteNumber(record3, keys) {
|
|
32011
|
+
for (const key of keys) {
|
|
32012
|
+
const n = asFiniteNumber(record3[key]);
|
|
32013
|
+
if (n !== undefined)
|
|
32014
|
+
return n;
|
|
32015
|
+
}
|
|
32016
|
+
return;
|
|
32017
|
+
}
|
|
32018
|
+
function normalizeUsageBreakdown(value) {
|
|
32019
|
+
if (!isRecord3(value))
|
|
32020
|
+
return;
|
|
32021
|
+
const out = {};
|
|
32022
|
+
for (const [key, aliases] of USAGE_BREAKDOWN_FIELDS) {
|
|
32023
|
+
const n = firstFiniteNumber(value, aliases);
|
|
32024
|
+
if (n !== undefined)
|
|
32025
|
+
out[key] = n;
|
|
32026
|
+
}
|
|
32027
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
32028
|
+
}
|
|
32029
|
+
function normalizeUsageCost(value) {
|
|
32030
|
+
if (!isRecord3(value))
|
|
32031
|
+
return;
|
|
32032
|
+
const amount = asFiniteNumber(value.amount);
|
|
32033
|
+
const currency = readString(value, "currency");
|
|
32034
|
+
if (amount === undefined && !currency)
|
|
32035
|
+
return;
|
|
32036
|
+
return { ...amount !== undefined ? { amount } : {}, ...currency ? { currency } : {} };
|
|
32037
|
+
}
|
|
32038
|
+
function normalizeAgentCommands(value) {
|
|
32039
|
+
if (!Array.isArray(value))
|
|
32040
|
+
return [];
|
|
32041
|
+
const out = [];
|
|
32042
|
+
for (const entry of value) {
|
|
32043
|
+
if (!isRecord3(entry))
|
|
32044
|
+
continue;
|
|
32045
|
+
const name = readString(entry, "name");
|
|
32046
|
+
if (!name)
|
|
32047
|
+
continue;
|
|
32048
|
+
const description = readString(entry, "description");
|
|
32049
|
+
out.push({ name, ...description ? { description } : {}, hasInput: entry.input != null });
|
|
32050
|
+
}
|
|
32051
|
+
return out;
|
|
32052
|
+
}
|
|
31964
32053
|
function isRecord3(value) {
|
|
31965
32054
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31966
32055
|
}
|
|
@@ -31986,8 +32075,17 @@ function isGenericToolTitle(kind, title) {
|
|
|
31986
32075
|
}
|
|
31987
32076
|
return false;
|
|
31988
32077
|
}
|
|
32078
|
+
var USAGE_BREAKDOWN_FIELDS;
|
|
31989
32079
|
var init_streaming_prompt = __esm(() => {
|
|
31990
32080
|
init_tool_kind_emoji();
|
|
32081
|
+
USAGE_BREAKDOWN_FIELDS = [
|
|
32082
|
+
["inputTokens", ["inputTokens", "input_tokens"]],
|
|
32083
|
+
["outputTokens", ["outputTokens", "output_tokens"]],
|
|
32084
|
+
["cachedReadTokens", ["cachedReadTokens", "cacheReadInputTokens", "cache_read_input_tokens"]],
|
|
32085
|
+
["cachedWriteTokens", ["cachedWriteTokens", "cacheCreationInputTokens", "cache_creation_input_tokens"]],
|
|
32086
|
+
["thoughtTokens", ["thoughtTokens", "thought_tokens"]],
|
|
32087
|
+
["totalTokens", ["totalTokens", "total_tokens"]]
|
|
32088
|
+
];
|
|
31991
32089
|
});
|
|
31992
32090
|
|
|
31993
32091
|
// src/transport/acpx-cli/node-pty-helper.ts
|
|
@@ -33497,24 +33595,29 @@ var init_workspace_fs = __esm(() => {
|
|
|
33497
33595
|
});
|
|
33498
33596
|
|
|
33499
33597
|
// src/control/control-service.ts
|
|
33598
|
+
import path15 from "node:path";
|
|
33599
|
+
|
|
33500
33600
|
class ControlService {
|
|
33501
33601
|
deps;
|
|
33502
33602
|
constructor(deps) {
|
|
33503
33603
|
this.deps = deps;
|
|
33504
33604
|
}
|
|
33505
33605
|
workspaceFs = new WorkspaceFs(() => this.deps.workspaces.list().map((w) => ({ name: w.name, cwd: w.cwd })));
|
|
33506
|
-
listDirectory(workspace3,
|
|
33507
|
-
return this.workspaceFs.listDirectory(workspace3,
|
|
33606
|
+
listDirectory(workspace3, path16) {
|
|
33607
|
+
return this.workspaceFs.listDirectory(workspace3, path16);
|
|
33508
33608
|
}
|
|
33509
|
-
readWorkspaceFile(workspace3,
|
|
33510
|
-
return this.workspaceFs.readFile(workspace3,
|
|
33609
|
+
readWorkspaceFile(workspace3, path16) {
|
|
33610
|
+
return this.workspaceFs.readFile(workspace3, path16);
|
|
33511
33611
|
}
|
|
33512
|
-
workspaceGitDiff(workspace3,
|
|
33513
|
-
return this.workspaceFs.gitDiff(workspace3,
|
|
33612
|
+
workspaceGitDiff(workspace3, path16) {
|
|
33613
|
+
return this.workspaceFs.gitDiff(workspace3, path16);
|
|
33514
33614
|
}
|
|
33515
33615
|
searchWorkspace(workspace3, query) {
|
|
33516
33616
|
return this.workspaceFs.search(workspace3, query);
|
|
33517
33617
|
}
|
|
33618
|
+
async uploadFile(input) {
|
|
33619
|
+
return this.deps.uploadStore.save(input.filename, input.content, input.mimeType);
|
|
33620
|
+
}
|
|
33518
33621
|
async getSessionModel(chatKey, alias) {
|
|
33519
33622
|
const session3 = await this.resolveControlSession(chatKey, alias);
|
|
33520
33623
|
if (!session3)
|
|
@@ -33658,7 +33761,8 @@ class ControlService {
|
|
|
33658
33761
|
text: input.text,
|
|
33659
33762
|
senderId: input.senderId,
|
|
33660
33763
|
...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
|
|
33661
|
-
...input.accountId !== undefined ? { accountId: input.accountId } : {}
|
|
33764
|
+
...input.accountId !== undefined ? { accountId: input.accountId } : {},
|
|
33765
|
+
...input.media !== undefined ? { media: input.media } : {}
|
|
33662
33766
|
});
|
|
33663
33767
|
}
|
|
33664
33768
|
async runScheduledTurn(input) {
|
|
@@ -33730,6 +33834,32 @@ ${chunk}` : chunk
|
|
|
33730
33834
|
});
|
|
33731
33835
|
emittedChunk = true;
|
|
33732
33836
|
};
|
|
33837
|
+
const incomingMedia = params.media ?? [];
|
|
33838
|
+
const sandboxedMedia = incomingMedia.length ? (() => {
|
|
33839
|
+
const uploadRoot = path15.resolve(this.deps.uploadStore.root);
|
|
33840
|
+
const kept = incomingMedia.filter((ref) => {
|
|
33841
|
+
const resolved = path15.resolve(ref.filePath);
|
|
33842
|
+
return resolved === uploadRoot || resolved.startsWith(uploadRoot + path15.sep);
|
|
33843
|
+
});
|
|
33844
|
+
const dropped = incomingMedia.length - kept.length;
|
|
33845
|
+
if (dropped > 0) {
|
|
33846
|
+
console.warn(`[control] dropped ${dropped} media ref(s) with filePath outside the upload sandbox`);
|
|
33847
|
+
}
|
|
33848
|
+
return kept;
|
|
33849
|
+
})() : incomingMedia;
|
|
33850
|
+
const chatMedia = sandboxedMedia.map((ref) => ({
|
|
33851
|
+
kind: ref.kind,
|
|
33852
|
+
filePath: ref.filePath,
|
|
33853
|
+
mimeType: ref.mimeType,
|
|
33854
|
+
...ref.fileName ? { fileName: ref.fileName } : {},
|
|
33855
|
+
sizeBytes: ref.size,
|
|
33856
|
+
source: {
|
|
33857
|
+
channelId: "relay",
|
|
33858
|
+
accountId: params.accountId ?? "control",
|
|
33859
|
+
chatKey: params.chatKey,
|
|
33860
|
+
messageId: ref.id
|
|
33861
|
+
}
|
|
33862
|
+
}));
|
|
33733
33863
|
try {
|
|
33734
33864
|
const response = await this.deps.agent.chat({
|
|
33735
33865
|
accountId: params.accountId ?? "control",
|
|
@@ -33737,6 +33867,7 @@ ${chunk}` : chunk
|
|
|
33737
33867
|
text: params.text,
|
|
33738
33868
|
metadata: buildControlMetadata(params.senderId, params.isOwner),
|
|
33739
33869
|
abortSignal: controller.signal,
|
|
33870
|
+
...chatMedia.length > 0 ? { media: chatMedia } : {},
|
|
33740
33871
|
reply: async (chunk) => {
|
|
33741
33872
|
emitChunk(chunk);
|
|
33742
33873
|
},
|
|
@@ -33770,7 +33901,17 @@ ${chunk}` : chunk
|
|
|
33770
33901
|
chatKey: params.chatKey,
|
|
33771
33902
|
sessionAlias: params.sessionAlias,
|
|
33772
33903
|
used: usage.used,
|
|
33773
|
-
size: usage.size
|
|
33904
|
+
size: usage.size,
|
|
33905
|
+
...usage.cost ? { cost: usage.cost } : {},
|
|
33906
|
+
...usage.breakdown ? { breakdown: usage.breakdown } : {}
|
|
33907
|
+
});
|
|
33908
|
+
},
|
|
33909
|
+
onCommands: (commands) => {
|
|
33910
|
+
this.deps.events.emit({
|
|
33911
|
+
type: "agent-commands",
|
|
33912
|
+
chatKey: params.chatKey,
|
|
33913
|
+
sessionAlias: params.sessionAlias,
|
|
33914
|
+
commands
|
|
33774
33915
|
});
|
|
33775
33916
|
}
|
|
33776
33917
|
});
|
|
@@ -33859,13 +34000,92 @@ var init_control_service = __esm(() => {
|
|
|
33859
34000
|
init_workspace_fs();
|
|
33860
34001
|
});
|
|
33861
34002
|
|
|
34003
|
+
// src/control/upload-store.ts
|
|
34004
|
+
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
|
|
34005
|
+
import { homedir as homedir12 } from "node:os";
|
|
34006
|
+
import path16 from "node:path";
|
|
34007
|
+
function defaultRootDir() {
|
|
34008
|
+
const home = process.env.HOME ?? homedir12();
|
|
34009
|
+
return path16.join(coreHomeDir(home), "runtime", "uploads");
|
|
34010
|
+
}
|
|
34011
|
+
function sanitizeUploadFilename(raw) {
|
|
34012
|
+
const base = path16.basename(raw).replace(/[/\\]/g, "").replace(/^\.+/, "");
|
|
34013
|
+
const cleaned = base.trim();
|
|
34014
|
+
return cleaned.length > 0 ? cleaned : "file";
|
|
34015
|
+
}
|
|
34016
|
+
|
|
34017
|
+
class UploadStore {
|
|
34018
|
+
rootDir;
|
|
34019
|
+
maxBytes;
|
|
34020
|
+
ttlMs;
|
|
34021
|
+
now;
|
|
34022
|
+
constructor(opts = {}) {
|
|
34023
|
+
this.rootDir = opts.rootDir ?? defaultRootDir();
|
|
34024
|
+
this.maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
34025
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
34026
|
+
this.now = opts.now ?? (() => new Date);
|
|
34027
|
+
}
|
|
34028
|
+
get root() {
|
|
34029
|
+
return this.rootDir;
|
|
34030
|
+
}
|
|
34031
|
+
async save(filename, base642, mimeType) {
|
|
34032
|
+
if (base642.length > Math.ceil(this.maxBytes * 4 / 3) + 4)
|
|
34033
|
+
throw new Error("file-too-large");
|
|
34034
|
+
const bytes = Buffer.from(base642, "base64");
|
|
34035
|
+
if (bytes.byteLength === 0)
|
|
34036
|
+
throw new Error("empty-file");
|
|
34037
|
+
if (bytes.byteLength > this.maxBytes)
|
|
34038
|
+
throw new Error("file-too-large");
|
|
34039
|
+
const safeName = sanitizeUploadFilename(filename);
|
|
34040
|
+
const { mkdir: mkdir9 } = await import("node:fs/promises");
|
|
34041
|
+
await mkdir9(this.rootDir, { recursive: true });
|
|
34042
|
+
const dir = await mkdtemp2(path16.join(this.rootDir, "u-"));
|
|
34043
|
+
const filePath = path16.join(dir, safeName);
|
|
34044
|
+
await writeFile8(filePath, bytes);
|
|
34045
|
+
return {
|
|
34046
|
+
id: path16.basename(dir),
|
|
34047
|
+
path: filePath,
|
|
34048
|
+
filename: safeName,
|
|
34049
|
+
mimeType,
|
|
34050
|
+
size: bytes.byteLength
|
|
34051
|
+
};
|
|
34052
|
+
}
|
|
34053
|
+
async cleanup() {
|
|
34054
|
+
let entries;
|
|
34055
|
+
try {
|
|
34056
|
+
entries = await readdir5(this.rootDir);
|
|
34057
|
+
} catch {
|
|
34058
|
+
return 0;
|
|
34059
|
+
}
|
|
34060
|
+
const cutoff = this.now().getTime() - this.ttlMs;
|
|
34061
|
+
let removed = 0;
|
|
34062
|
+
for (const name of entries) {
|
|
34063
|
+
const dir = path16.join(this.rootDir, name);
|
|
34064
|
+
try {
|
|
34065
|
+
const info = await stat4(dir);
|
|
34066
|
+
if (info.mtimeMs < cutoff) {
|
|
34067
|
+
await rm10(dir, { recursive: true, force: true });
|
|
34068
|
+
removed += 1;
|
|
34069
|
+
}
|
|
34070
|
+
} catch {}
|
|
34071
|
+
}
|
|
34072
|
+
return removed;
|
|
34073
|
+
}
|
|
34074
|
+
}
|
|
34075
|
+
var DEFAULT_MAX_BYTES, DEFAULT_TTL_MS;
|
|
34076
|
+
var init_upload_store = __esm(() => {
|
|
34077
|
+
init_core_home();
|
|
34078
|
+
DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
|
34079
|
+
DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
34080
|
+
});
|
|
34081
|
+
|
|
33862
34082
|
// src/config/agent-catalog.ts
|
|
33863
34083
|
import { existsSync as existsSync3 } from "node:fs";
|
|
33864
34084
|
import { delimiter as delimiter2, join as join21 } from "node:path";
|
|
33865
34085
|
function isBinaryOnPath(binary) {
|
|
33866
|
-
const
|
|
34086
|
+
const path17 = process.env.PATH ?? "";
|
|
33867
34087
|
const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
|
|
33868
|
-
for (const dir of
|
|
34088
|
+
for (const dir of path17.split(delimiter2)) {
|
|
33869
34089
|
if (!dir)
|
|
33870
34090
|
continue;
|
|
33871
34091
|
for (const ext of exts) {
|
|
@@ -33910,7 +34130,7 @@ __export(exports_main, {
|
|
|
33910
34130
|
buildApp: () => buildApp
|
|
33911
34131
|
});
|
|
33912
34132
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
33913
|
-
import { homedir as
|
|
34133
|
+
import { homedir as homedir13 } from "node:os";
|
|
33914
34134
|
import { dirname as dirname12, join as join22 } from "node:path";
|
|
33915
34135
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
33916
34136
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
@@ -34402,6 +34622,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
34402
34622
|
const router3 = new CommandRouter(sessions, transport, config4, configStore, logger2, undefined, orchestration3, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined, deps.channel?.nativeSessionListFormat ? deps.channel.nativeSessionListFormat.bind(deps.channel) : undefined, activeTurns);
|
|
34403
34623
|
const agent3 = new ConsoleAgent(router3, logger2);
|
|
34404
34624
|
const controlEvents = createControlEventBus(logger2);
|
|
34625
|
+
const uploadStore = new UploadStore;
|
|
34626
|
+
uploadStore.cleanup();
|
|
34627
|
+
const uploadCleanupInterval = setInterval(() => void uploadStore.cleanup().catch(() => {}), 60 * 60 * 1000);
|
|
34405
34628
|
const control = new ControlService({
|
|
34406
34629
|
agent: agent3,
|
|
34407
34630
|
sessions,
|
|
@@ -34444,7 +34667,8 @@ async function buildApp(paths, deps = {}) {
|
|
|
34444
34667
|
const updated = await configStore.removeWorkspace(name);
|
|
34445
34668
|
replaceRuntimeConfig(config4, updated);
|
|
34446
34669
|
}
|
|
34447
|
-
}
|
|
34670
|
+
},
|
|
34671
|
+
uploadStore
|
|
34448
34672
|
});
|
|
34449
34673
|
const scheduledScheduler = new ScheduledTaskScheduler(scheduledService, {
|
|
34450
34674
|
dispatchTask: buildScheduledDispatchTask({
|
|
@@ -34514,6 +34738,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
34514
34738
|
reapStaleQueueOwners: () => reapWarmQueueOwners("startup"),
|
|
34515
34739
|
dispose: async () => {
|
|
34516
34740
|
scheduledScheduler.stop();
|
|
34741
|
+
clearInterval(uploadCleanupInterval);
|
|
34517
34742
|
if (progressHeartbeatInterval !== undefined) {
|
|
34518
34743
|
clearInterval(progressHeartbeatInterval);
|
|
34519
34744
|
}
|
|
@@ -34584,7 +34809,7 @@ async function prepareChannelMedia(configPath, config4) {
|
|
|
34584
34809
|
return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
|
|
34585
34810
|
}
|
|
34586
34811
|
function resolveRuntimePaths() {
|
|
34587
|
-
const home = process.env.HOME ??
|
|
34812
|
+
const home = process.env.HOME ?? homedir13();
|
|
34588
34813
|
if (!home) {
|
|
34589
34814
|
throw new Error("Unable to resolve the current user home directory");
|
|
34590
34815
|
}
|
|
@@ -34656,6 +34881,7 @@ var init_main = __esm(async () => {
|
|
|
34656
34881
|
init_render_text();
|
|
34657
34882
|
init_quota_manager();
|
|
34658
34883
|
init_control_service();
|
|
34884
|
+
init_upload_store();
|
|
34659
34885
|
init_agent_catalog();
|
|
34660
34886
|
init_perf_tracer();
|
|
34661
34887
|
init_bootstrap();
|
|
@@ -34849,12 +35075,12 @@ var init_config_check = __esm(async () => {
|
|
|
34849
35075
|
});
|
|
34850
35076
|
|
|
34851
35077
|
// src/doctor/checks/daemon-check.ts
|
|
34852
|
-
import { readdir as
|
|
35078
|
+
import { readdir as readdir6, readFile as readFile15, rm as rm11 } from "node:fs/promises";
|
|
34853
35079
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
34854
|
-
import { homedir as
|
|
35080
|
+
import { homedir as homedir14 } from "node:os";
|
|
34855
35081
|
import { join as join23 } from "node:path";
|
|
34856
35082
|
async function checkDaemon(options = {}) {
|
|
34857
|
-
const home = options.home ?? process.env.HOME ??
|
|
35083
|
+
const home = options.home ?? process.env.HOME ?? homedir14();
|
|
34858
35084
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
34859
35085
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
34860
35086
|
home,
|
|
@@ -34988,22 +35214,22 @@ async function detectStaleConsumerLockFix(runtimeDir, deps) {
|
|
|
34988
35214
|
}
|
|
34989
35215
|
async function defaultListConsumerLocks(runtimeDir) {
|
|
34990
35216
|
try {
|
|
34991
|
-
return await
|
|
35217
|
+
return await readdir6(runtimeDir);
|
|
34992
35218
|
} catch {
|
|
34993
35219
|
return [];
|
|
34994
35220
|
}
|
|
34995
35221
|
}
|
|
34996
|
-
async function defaultReadConsumerLock(
|
|
35222
|
+
async function defaultReadConsumerLock(path17) {
|
|
34997
35223
|
try {
|
|
34998
|
-
const raw = await readFile15(
|
|
35224
|
+
const raw = await readFile15(path17, "utf8");
|
|
34999
35225
|
const parsed = JSON.parse(raw);
|
|
35000
35226
|
return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
|
|
35001
35227
|
} catch {
|
|
35002
35228
|
return null;
|
|
35003
35229
|
}
|
|
35004
35230
|
}
|
|
35005
|
-
async function defaultRemoveConsumerLock(
|
|
35006
|
-
await
|
|
35231
|
+
async function defaultRemoveConsumerLock(path17) {
|
|
35232
|
+
await rm11(path17, { force: true });
|
|
35007
35233
|
}
|
|
35008
35234
|
function resolveCliEntryPath() {
|
|
35009
35235
|
return process.argv[1] ?? fileURLToPath6(import.meta.url);
|
|
@@ -35018,11 +35244,11 @@ var init_daemon_check = __esm(() => {
|
|
|
35018
35244
|
});
|
|
35019
35245
|
|
|
35020
35246
|
// src/doctor/checks/logs-check.ts
|
|
35021
|
-
import { stat as
|
|
35247
|
+
import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
|
|
35022
35248
|
import { basename as basename3, join as join24 } from "node:path";
|
|
35023
|
-
import { homedir as
|
|
35249
|
+
import { homedir as homedir15 } from "node:os";
|
|
35024
35250
|
async function checkLogs(options = {}) {
|
|
35025
|
-
const home = options.home ?? process.env.HOME ??
|
|
35251
|
+
const home = options.home ?? process.env.HOME ?? homedir15();
|
|
35026
35252
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
35027
35253
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
35028
35254
|
home,
|
|
@@ -35054,13 +35280,13 @@ async function checkLogs(options = {}) {
|
|
|
35054
35280
|
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
35055
35281
|
const files = [];
|
|
35056
35282
|
for (const name of matched) {
|
|
35057
|
-
const
|
|
35283
|
+
const path17 = join24(paths.runtimeDir, name);
|
|
35058
35284
|
try {
|
|
35059
|
-
const fileStat = await probe.stat(
|
|
35285
|
+
const fileStat = await probe.stat(path17);
|
|
35060
35286
|
if (fileStat.isDirectory()) {
|
|
35061
35287
|
continue;
|
|
35062
35288
|
}
|
|
35063
|
-
files.push({ name, path:
|
|
35289
|
+
files.push({ name, path: path17, size: fileStat.size });
|
|
35064
35290
|
} catch {
|
|
35065
35291
|
continue;
|
|
35066
35292
|
}
|
|
@@ -35134,8 +35360,8 @@ function formatBytes(bytes) {
|
|
|
35134
35360
|
}
|
|
35135
35361
|
function createLogsFsProbe() {
|
|
35136
35362
|
return {
|
|
35137
|
-
stat: async (
|
|
35138
|
-
readdir: async (
|
|
35363
|
+
stat: async (path17) => await stat5(path17),
|
|
35364
|
+
readdir: async (path17) => await readdir7(path17)
|
|
35139
35365
|
};
|
|
35140
35366
|
}
|
|
35141
35367
|
function formatError6(error2) {
|
|
@@ -35204,9 +35430,9 @@ var init_orchestration_health = __esm(() => {
|
|
|
35204
35430
|
});
|
|
35205
35431
|
|
|
35206
35432
|
// src/doctor/checks/orchestration-socket-check.ts
|
|
35207
|
-
import { homedir as
|
|
35433
|
+
import { homedir as homedir16 } from "node:os";
|
|
35208
35434
|
async function checkOrchestrationSocket(options = {}) {
|
|
35209
|
-
const home = options.home ?? process.env.HOME ??
|
|
35435
|
+
const home = options.home ?? process.env.HOME ?? homedir16();
|
|
35210
35436
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
35211
35437
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
35212
35438
|
home,
|
|
@@ -35379,11 +35605,11 @@ var init_plugin_check = __esm(async () => {
|
|
|
35379
35605
|
|
|
35380
35606
|
// src/doctor/checks/runtime-check.ts
|
|
35381
35607
|
import { constants } from "node:fs";
|
|
35382
|
-
import { access as access4, stat as
|
|
35608
|
+
import { access as access4, stat as stat6 } from "node:fs/promises";
|
|
35383
35609
|
import { dirname as dirname13 } from "node:path";
|
|
35384
|
-
import { homedir as
|
|
35610
|
+
import { homedir as homedir17 } from "node:os";
|
|
35385
35611
|
async function checkRuntime(options = {}) {
|
|
35386
|
-
const home = options.home ?? process.env.HOME ??
|
|
35612
|
+
const home = options.home ?? process.env.HOME ?? homedir17();
|
|
35387
35613
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
35388
35614
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
35389
35615
|
home,
|
|
@@ -35477,107 +35703,107 @@ function formatMode(mode) {
|
|
|
35477
35703
|
}
|
|
35478
35704
|
function createRuntimeFsProbe() {
|
|
35479
35705
|
return {
|
|
35480
|
-
stat: async (
|
|
35481
|
-
access: async (
|
|
35706
|
+
stat: async (path17) => await stat6(path17),
|
|
35707
|
+
access: async (path17, mode) => await access4(path17, mode)
|
|
35482
35708
|
};
|
|
35483
35709
|
}
|
|
35484
|
-
async function checkDirectoryCreatable(label,
|
|
35710
|
+
async function checkDirectoryCreatable(label, path17, probe, platform) {
|
|
35485
35711
|
try {
|
|
35486
|
-
const stats = await probe.stat(
|
|
35712
|
+
const stats = await probe.stat(path17);
|
|
35487
35713
|
if (!stats.isDirectory()) {
|
|
35488
35714
|
return {
|
|
35489
35715
|
ok: false,
|
|
35490
|
-
detail: `${label}: ${
|
|
35716
|
+
detail: `${label}: ${path17} (exists but is not a directory)`
|
|
35491
35717
|
};
|
|
35492
35718
|
}
|
|
35493
|
-
await probe.access(
|
|
35719
|
+
await probe.access(path17, directoryAccessMode(platform));
|
|
35494
35720
|
return {
|
|
35495
35721
|
ok: true,
|
|
35496
|
-
detail: `${label}: ${
|
|
35722
|
+
detail: `${label}: ${path17} (writable)`
|
|
35497
35723
|
};
|
|
35498
35724
|
} catch (error2) {
|
|
35499
35725
|
if (!isMissingPathError2(error2)) {
|
|
35500
35726
|
return {
|
|
35501
35727
|
ok: false,
|
|
35502
|
-
detail: `${label}: ${
|
|
35728
|
+
detail: `${label}: ${path17} (unusable: ${formatError9(error2)})`
|
|
35503
35729
|
};
|
|
35504
35730
|
}
|
|
35505
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
35731
|
+
const parentCheck = await checkCreatableAncestorDirectory(path17, probe, platform);
|
|
35506
35732
|
if (!parentCheck.ok) {
|
|
35507
35733
|
return {
|
|
35508
35734
|
ok: false,
|
|
35509
|
-
detail: `${label}: ${
|
|
35735
|
+
detail: `${label}: ${path17} (parent not writable: ${parentCheck.blockingPath})`
|
|
35510
35736
|
};
|
|
35511
35737
|
}
|
|
35512
35738
|
return {
|
|
35513
35739
|
ok: true,
|
|
35514
|
-
detail: `${label}: ${
|
|
35740
|
+
detail: `${label}: ${path17} (creatable via ${parentCheck.creatableFrom})`
|
|
35515
35741
|
};
|
|
35516
35742
|
}
|
|
35517
35743
|
}
|
|
35518
|
-
async function checkFileCreatable(label,
|
|
35744
|
+
async function checkFileCreatable(label, path17, probe, platform) {
|
|
35519
35745
|
try {
|
|
35520
|
-
const stats = await probe.stat(
|
|
35746
|
+
const stats = await probe.stat(path17);
|
|
35521
35747
|
if (stats.isDirectory()) {
|
|
35522
35748
|
return {
|
|
35523
35749
|
ok: false,
|
|
35524
|
-
detail: `${label}: ${
|
|
35750
|
+
detail: `${label}: ${path17} (exists but is a directory)`
|
|
35525
35751
|
};
|
|
35526
35752
|
}
|
|
35527
|
-
await probe.access(
|
|
35753
|
+
await probe.access(path17, constants.W_OK);
|
|
35528
35754
|
return {
|
|
35529
35755
|
ok: true,
|
|
35530
|
-
detail: `${label}: ${
|
|
35756
|
+
detail: `${label}: ${path17} (writable)`
|
|
35531
35757
|
};
|
|
35532
35758
|
} catch (error2) {
|
|
35533
35759
|
if (!isMissingPathError2(error2)) {
|
|
35534
35760
|
return {
|
|
35535
35761
|
ok: false,
|
|
35536
|
-
detail: `${label}: ${
|
|
35762
|
+
detail: `${label}: ${path17} (unusable: ${formatError9(error2)})`
|
|
35537
35763
|
};
|
|
35538
35764
|
}
|
|
35539
|
-
const parentCheck = await checkCreatableAncestorDirectory(dirname13(
|
|
35765
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname13(path17), probe, platform);
|
|
35540
35766
|
if (!parentCheck.ok) {
|
|
35541
35767
|
return {
|
|
35542
35768
|
ok: false,
|
|
35543
|
-
detail: `${label}: ${
|
|
35769
|
+
detail: `${label}: ${path17} (parent not writable: ${parentCheck.blockingPath})`
|
|
35544
35770
|
};
|
|
35545
35771
|
}
|
|
35546
35772
|
return {
|
|
35547
35773
|
ok: true,
|
|
35548
|
-
detail: `${label}: ${
|
|
35774
|
+
detail: `${label}: ${path17} (creatable via ${parentCheck.creatableFrom})`
|
|
35549
35775
|
};
|
|
35550
35776
|
}
|
|
35551
35777
|
}
|
|
35552
|
-
async function checkCreatableAncestorDirectory(
|
|
35778
|
+
async function checkCreatableAncestorDirectory(path17, probe, platform) {
|
|
35553
35779
|
try {
|
|
35554
|
-
const stats = await probe.stat(
|
|
35780
|
+
const stats = await probe.stat(path17);
|
|
35555
35781
|
if (!stats.isDirectory()) {
|
|
35556
35782
|
return {
|
|
35557
35783
|
ok: false,
|
|
35558
|
-
creatableFrom:
|
|
35559
|
-
blockingPath:
|
|
35784
|
+
creatableFrom: path17,
|
|
35785
|
+
blockingPath: path17
|
|
35560
35786
|
};
|
|
35561
35787
|
}
|
|
35562
|
-
await probe.access(
|
|
35788
|
+
await probe.access(path17, directoryAccessMode(platform));
|
|
35563
35789
|
return {
|
|
35564
35790
|
ok: true,
|
|
35565
|
-
creatableFrom:
|
|
35791
|
+
creatableFrom: path17
|
|
35566
35792
|
};
|
|
35567
35793
|
} catch (error2) {
|
|
35568
35794
|
if (!isMissingPathError2(error2)) {
|
|
35569
35795
|
return {
|
|
35570
35796
|
ok: false,
|
|
35571
|
-
creatableFrom:
|
|
35572
|
-
blockingPath:
|
|
35797
|
+
creatableFrom: path17,
|
|
35798
|
+
blockingPath: path17
|
|
35573
35799
|
};
|
|
35574
35800
|
}
|
|
35575
|
-
const parent = dirname13(
|
|
35576
|
-
if (parent ===
|
|
35801
|
+
const parent = dirname13(path17);
|
|
35802
|
+
if (parent === path17) {
|
|
35577
35803
|
return {
|
|
35578
35804
|
ok: false,
|
|
35579
|
-
creatableFrom:
|
|
35580
|
-
blockingPath:
|
|
35805
|
+
creatableFrom: path17,
|
|
35806
|
+
blockingPath: path17
|
|
35581
35807
|
};
|
|
35582
35808
|
}
|
|
35583
35809
|
const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
|
|
@@ -36016,10 +36242,10 @@ var init_render_doctor = __esm(() => {
|
|
|
36016
36242
|
});
|
|
36017
36243
|
|
|
36018
36244
|
// src/doctor/doctor.ts
|
|
36019
|
-
import { homedir as
|
|
36245
|
+
import { homedir as homedir18 } from "node:os";
|
|
36020
36246
|
import { join as join25 } from "node:path";
|
|
36021
36247
|
async function runDoctor(options = {}, deps = {}) {
|
|
36022
|
-
const home = deps.home ?? process.env.HOME ??
|
|
36248
|
+
const home = deps.home ?? process.env.HOME ?? homedir18();
|
|
36023
36249
|
const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
|
|
36024
36250
|
const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
|
|
36025
36251
|
const runners = [
|
|
@@ -36356,7 +36582,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
36356
36582
|
// src/cli.ts
|
|
36357
36583
|
init_core_home();
|
|
36358
36584
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
36359
|
-
import { homedir as
|
|
36585
|
+
import { homedir as homedir19 } from "node:os";
|
|
36360
36586
|
import { dirname as dirname14, join as join26, sep as sep2 } from "node:path";
|
|
36361
36587
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
36362
36588
|
|
|
@@ -52962,7 +53188,7 @@ function decodeFirstRunOnboarding(raw) {
|
|
|
52962
53188
|
return null;
|
|
52963
53189
|
}
|
|
52964
53190
|
function requireHome2() {
|
|
52965
|
-
const home = process.env.HOME ??
|
|
53191
|
+
const home = process.env.HOME ?? homedir19();
|
|
52966
53192
|
if (!home) {
|
|
52967
53193
|
throw new Error("Unable to resolve the current user home directory");
|
|
52968
53194
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommandRouterContext, RouterResponse, SessionInteractionOps, SessionLifecycleOps, SessionRenderRecoveryOps } from "../router-types";
|
|
2
|
-
import type { PromptMediaInput, ResolvedSession } from "../../transport/types";
|
|
2
|
+
import type { AgentCommand, PromptMediaInput, PromptUsage, ResolvedSession } from "../../transport/types";
|
|
3
3
|
import type { PlanEntry, ToolUseEvent } from "../../channels/types.js";
|
|
4
4
|
import type { PerfSpan } from "../../perf/perf-tracer";
|
|
5
5
|
import type { HelpTopicMetadata } from "../help/help-types";
|
|
@@ -39,11 +39,5 @@ export declare function handleSessionReset(context: SessionHandlerContext, chatK
|
|
|
39
39
|
export declare function handleSessionTail(context: SessionHandlerContext, chatKey: string, lines?: number): Promise<RouterResponse>;
|
|
40
40
|
export declare function handleSessionRemove(context: SessionHandlerContext, chatKey: string, alias: string): Promise<RouterResponse>;
|
|
41
41
|
export declare function handleSessionArchive(context: SessionHandlerContext, chatKey: string, alias: string, archive: (internalAlias: string) => Promise<void>): Promise<RouterResponse>;
|
|
42
|
-
export declare function handlePromptWithSession(context: SessionHandlerContext, session: ResolvedSession, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata, onPlan?: (entries: PlanEntry[]) => void | Promise<void>, onUsage?: (usage:
|
|
43
|
-
|
|
44
|
-
size: number;
|
|
45
|
-
}) => void | Promise<void>): Promise<RouterResponse>;
|
|
46
|
-
export declare function handlePrompt(context: SessionHandlerContext, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata, onPlan?: (entries: PlanEntry[]) => void | Promise<void>, onUsage?: (usage: {
|
|
47
|
-
used: number;
|
|
48
|
-
size: number;
|
|
49
|
-
}) => void | Promise<void>): Promise<RouterResponse>;
|
|
42
|
+
export declare function handlePromptWithSession(context: SessionHandlerContext, session: ResolvedSession, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata, onPlan?: (entries: PlanEntry[]) => void | Promise<void>, onUsage?: (usage: PromptUsage) => void | Promise<void>, onCommands?: (commands: AgentCommand[]) => void | Promise<void>): Promise<RouterResponse>;
|
|
43
|
+
export declare function handlePrompt(context: SessionHandlerContext, chatKey: string, text: string, reply?: (text: string) => Promise<void>, replyContextToken?: string, accountId?: string, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, metadata?: ChatRequestMetadata, onPlan?: (entries: PlanEntry[]) => void | Promise<void>, onUsage?: (usage: PromptUsage) => void | Promise<void>, onCommands?: (commands: AgentCommand[]) => void | Promise<void>): Promise<RouterResponse>;
|
|
@@ -3,7 +3,7 @@ import type { AppConfig } from "../config/types";
|
|
|
3
3
|
import type { AppLogger } from "../logging/app-logger";
|
|
4
4
|
import type { OrchestrationService } from "../orchestration/orchestration-service";
|
|
5
5
|
import type { SessionService } from "../sessions/session-service";
|
|
6
|
-
import type { PromptMediaInput, ReplyQuotaContext, SessionTransport } from "../transport/types";
|
|
6
|
+
import type { AgentCommand, PromptMediaInput, PromptUsage, ReplyQuotaContext, SessionTransport } from "../transport/types";
|
|
7
7
|
import type { QuotaManager } from "../weixin/messaging/quota-manager.js";
|
|
8
8
|
import type { PlanEntry, ToolUseEvent } from "../channels/types.js";
|
|
9
9
|
import type { PerfSpan } from "../perf/perf-tracer";
|
|
@@ -113,10 +113,7 @@ export interface SessionInteractionOps {
|
|
|
113
113
|
cancelled: boolean;
|
|
114
114
|
message: string;
|
|
115
115
|
}>;
|
|
116
|
-
promptTransportSession: (session: import("../transport/types").ResolvedSession, text: string, reply?: (text: string) => Promise<void>, replyContext?: ReplyQuotaContext, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, onPlan?: (entries: PlanEntry[]) => void | Promise<void>, onUsage?: (usage: {
|
|
117
|
-
used: number;
|
|
118
|
-
size: number;
|
|
119
|
-
}) => void | Promise<void>) => Promise<{
|
|
116
|
+
promptTransportSession: (session: import("../transport/types").ResolvedSession, text: string, reply?: (text: string) => Promise<void>, replyContext?: ReplyQuotaContext, media?: PromptMediaInput, abortSignal?: AbortSignal, onToolEvent?: (event: ToolUseEvent) => void | Promise<void>, onThought?: (chunk: string) => void | Promise<void>, perfSpan?: PerfSpan, onPlan?: (entries: PlanEntry[]) => void | Promise<void>, onUsage?: (usage: PromptUsage) => void | Promise<void>, onCommands?: (commands: AgentCommand[]) => void | Promise<void>) => Promise<{
|
|
120
117
|
text: string;
|
|
121
118
|
}>;
|
|
122
119
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AppLogger } from "../logging/app-logger";
|
|
2
2
|
import type { ToolUseEvent, PlanEntry } from "../channels/types";
|
|
3
|
+
import type { AgentCommand, UsageBreakdown, UsageCost } from "../transport/types";
|
|
3
4
|
import type { NativeHistoryMessage } from "../transport/native-session-history";
|
|
4
5
|
export interface ScheduledOrigin {
|
|
5
6
|
taskId: string;
|
|
@@ -37,6 +38,13 @@ export type ControlEvent = {
|
|
|
37
38
|
sessionAlias: string;
|
|
38
39
|
used: number;
|
|
39
40
|
size: number;
|
|
41
|
+
cost?: UsageCost;
|
|
42
|
+
breakdown?: UsageBreakdown;
|
|
43
|
+
} | {
|
|
44
|
+
type: "agent-commands";
|
|
45
|
+
chatKey: string;
|
|
46
|
+
sessionAlias: string;
|
|
47
|
+
commands: AgentCommand[];
|
|
40
48
|
} | {
|
|
41
49
|
type: "turn-finished";
|
|
42
50
|
chatKey: string;
|
|
@@ -9,6 +9,8 @@ import type { OrchestrationTaskRecord } from "../orchestration/orchestration-typ
|
|
|
9
9
|
import type { ControlEventBus } from "./control-event-bus";
|
|
10
10
|
import type { AgentCatalogEntry } from "../config/agent-catalog";
|
|
11
11
|
import { type DirListing, type FileContent, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
|
|
12
|
+
import type { PromptAttachmentRef } from "@ganglion/xacpx-relay-protocol";
|
|
13
|
+
import type { UploadStore } from "./upload-store.js";
|
|
12
14
|
export interface ControlSessionInfo {
|
|
13
15
|
alias: string;
|
|
14
16
|
agent: string;
|
|
@@ -63,6 +65,7 @@ export interface ControlServiceDeps {
|
|
|
63
65
|
create(name: string, cwd: string, description?: string): Promise<ControlWorkspaceInfo>;
|
|
64
66
|
remove(name: string): Promise<void>;
|
|
65
67
|
};
|
|
68
|
+
uploadStore: UploadStore;
|
|
66
69
|
}
|
|
67
70
|
export interface ControlPromptInput {
|
|
68
71
|
chatKey: string;
|
|
@@ -71,6 +74,7 @@ export interface ControlPromptInput {
|
|
|
71
74
|
accountId?: string;
|
|
72
75
|
senderId: string;
|
|
73
76
|
isOwner?: boolean;
|
|
77
|
+
media?: PromptAttachmentRef[];
|
|
74
78
|
}
|
|
75
79
|
export interface ControlPromptResult {
|
|
76
80
|
ok: boolean;
|
|
@@ -105,6 +109,17 @@ export declare class ControlService {
|
|
|
105
109
|
readWorkspaceFile(workspace: string, path: string): Promise<FileContent>;
|
|
106
110
|
workspaceGitDiff(workspace: string, path?: string): Promise<WorkspaceDiff>;
|
|
107
111
|
searchWorkspace(workspace: string, query: string): Promise<SearchResult>;
|
|
112
|
+
uploadFile(input: {
|
|
113
|
+
filename: string;
|
|
114
|
+
content: string;
|
|
115
|
+
mimeType: string;
|
|
116
|
+
}): Promise<{
|
|
117
|
+
id: string;
|
|
118
|
+
path: string;
|
|
119
|
+
filename: string;
|
|
120
|
+
mimeType: string;
|
|
121
|
+
size: number;
|
|
122
|
+
}>;
|
|
108
123
|
/** Read a session's current model and the agent-advertised available ids. */
|
|
109
124
|
getSessionModel(chatKey: string, alias: string): Promise<{
|
|
110
125
|
current?: string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface UploadStoreOptions {
|
|
2
|
+
rootDir?: string;
|
|
3
|
+
maxBytes?: number;
|
|
4
|
+
ttlMs?: number;
|
|
5
|
+
now?: () => Date;
|
|
6
|
+
}
|
|
7
|
+
export interface SavedUpload {
|
|
8
|
+
id: string;
|
|
9
|
+
path: string;
|
|
10
|
+
filename: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
size: number;
|
|
13
|
+
}
|
|
14
|
+
/** Strip directory components and traversal segments, leaving a safe basename. */
|
|
15
|
+
export declare function sanitizeUploadFilename(raw: string): string;
|
|
16
|
+
export declare class UploadStore {
|
|
17
|
+
private readonly rootDir;
|
|
18
|
+
private readonly maxBytes;
|
|
19
|
+
private readonly ttlMs;
|
|
20
|
+
private readonly now;
|
|
21
|
+
constructor(opts?: UploadStoreOptions);
|
|
22
|
+
/** Sandbox root all uploads are written under. Callers may use this to verify a
|
|
23
|
+
* media filePath actually originated from a control.upload (defense-in-depth). */
|
|
24
|
+
get root(): string;
|
|
25
|
+
save(filename: string, base64: string, mimeType: string): Promise<SavedUpload>;
|
|
26
|
+
/** Remove upload dirs whose mtime is older than the TTL. Returns count removed. */
|
|
27
|
+
cleanup(): Promise<number>;
|
|
28
|
+
}
|
|
@@ -3,6 +3,37 @@ import type { QuotaManager } from "../weixin/messaging/quota-manager.js";
|
|
|
3
3
|
import type { PlanEntry, ToolUseEvent } from "../channels/types.js";
|
|
4
4
|
import type { ToolEventMode } from "./tool-event-mode.js";
|
|
5
5
|
export type { ToolEventMode } from "./tool-event-mode.js";
|
|
6
|
+
/** Cumulative session cost the agent reported (ACP `usage_update.cost`). Both optional. */
|
|
7
|
+
export interface UsageCost {
|
|
8
|
+
amount?: number;
|
|
9
|
+
currency?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Per-turn token breakdown from ACP `usage_update._meta.usage` (Claude reports it;
|
|
13
|
+
* codex may omit). All fields optional — treat missing as "unknown", not zero.
|
|
14
|
+
*/
|
|
15
|
+
export interface UsageBreakdown {
|
|
16
|
+
inputTokens?: number;
|
|
17
|
+
outputTokens?: number;
|
|
18
|
+
cachedReadTokens?: number;
|
|
19
|
+
cachedWriteTokens?: number;
|
|
20
|
+
thoughtTokens?: number;
|
|
21
|
+
totalTokens?: number;
|
|
22
|
+
}
|
|
23
|
+
/** Context-usage side-channel payload: window fill plus optional cost & token breakdown. */
|
|
24
|
+
export interface PromptUsage {
|
|
25
|
+
used: number;
|
|
26
|
+
size: number;
|
|
27
|
+
cost?: UsageCost;
|
|
28
|
+
breakdown?: UsageBreakdown;
|
|
29
|
+
}
|
|
30
|
+
/** An agent-advertised slash command (ACP `available_commands_update`). */
|
|
31
|
+
export interface AgentCommand {
|
|
32
|
+
name: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
/** Whether the command accepts an argument (ACP advertised a non-null `input`). */
|
|
35
|
+
hasInput?: boolean;
|
|
36
|
+
}
|
|
6
37
|
export interface ReplyQuotaContext {
|
|
7
38
|
chatKey: string;
|
|
8
39
|
quota: QuotaManager;
|
|
@@ -125,10 +156,12 @@ export interface PromptOptions {
|
|
|
125
156
|
* scalar (re-sent during a turn). Optional — only agents that report it fire this
|
|
126
157
|
* (e.g. claude does, codex does not), and text channels omit the handler.
|
|
127
158
|
*/
|
|
128
|
-
onUsage?: (usage:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
159
|
+
onUsage?: (usage: PromptUsage) => void | Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Agent-advertised slash commands (ACP `available_commands_update`). Replace-latest
|
|
162
|
+
* list, re-sent when the agent updates it. Optional — not every adapter advertises.
|
|
163
|
+
*/
|
|
164
|
+
onCommands?: (commands: AgentCommand[]) => void | Promise<void>;
|
|
132
165
|
/**
|
|
133
166
|
* How tool_call / tool_call_update events are surfaced for this prompt.
|
|
134
167
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChannelMediaAttachment, OutboundChannelMedia } from "../../channels/media-types.js";
|
|
2
2
|
import type { PlanEntry, ScheduledSessionDescriptor, ToolUseEvent } from "../../channels/types.js";
|
|
3
|
+
import type { AgentCommand, PromptUsage } from "../../transport/types.js";
|
|
3
4
|
import type { PerfSpan } from "../../perf/perf-tracer.js";
|
|
4
5
|
/**
|
|
5
6
|
* Agent interface — any AI backend that can handle a chat message.
|
|
@@ -51,10 +52,9 @@ export interface ChatRequest {
|
|
|
51
52
|
/** Structured plan/todo side-channel; see PromptOptions.onPlan. */
|
|
52
53
|
onPlan?: (entries: PlanEntry[]) => void | Promise<void>;
|
|
53
54
|
/** Context-usage side-channel; see PromptOptions.onUsage. */
|
|
54
|
-
onUsage?: (usage:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}) => void | Promise<void>;
|
|
55
|
+
onUsage?: (usage: PromptUsage) => void | Promise<void>;
|
|
56
|
+
/** Agent-advertised slash commands; see PromptOptions.onCommands. */
|
|
57
|
+
onCommands?: (commands: AgentCommand[]) => void | Promise<void>;
|
|
58
58
|
/**
|
|
59
59
|
* Optional per-turn performance tracing span. When `logging.perf.enabled` is
|
|
60
60
|
* true, the channel handler attaches a `PerfSpan` so downstream layers can
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ganglion/xacpx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acpx",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
88
|
-
"acpx": "^0.
|
|
88
|
+
"acpx": "^0.11.0",
|
|
89
89
|
"node-pty": "^1.1.0",
|
|
90
90
|
"proper-lockfile": "^4.1.2",
|
|
91
91
|
"protobufjs": "^7.5.6",
|