@bytetrue/byspace 0.17.0 → 0.18.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/node_modules/@bytetrue/client/package.json +3 -3
- package/node_modules/@bytetrue/highlight/package.json +1 -1
- package/node_modules/@bytetrue/protocol/package.json +1 -1
- package/node_modules/@bytetrue/relay/package.json +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/agent/agent-manager.d.ts +30 -0
- package/node_modules/@bytetrue/server/dist/server/server/agent/agent-manager.js +118 -8
- package/node_modules/@bytetrue/server/dist/server/server/agent/mcp-shared.d.ts +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/agent/{providers/claude/subagents → provider-subagents}/observation.d.ts +2 -2
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/agent.js +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/subagents/live-source.d.ts +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/subagents/replay-source.d.ts +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/subagents/workflow-replay-source.d.ts +1 -1
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/omp/rpc-types.d.ts +6 -6
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent-subagents.d.ts +45 -0
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent-subagents.js +159 -0
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent.d.ts +2 -2
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent.js +86 -151
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/runtime.d.ts +0 -2
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/runtime.js +0 -4
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/session-descriptor.d.ts +2 -0
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/session-descriptor.js +2 -2
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/subagent-session-tailer.d.ts +47 -0
- package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/subagent-session-tailer.js +212 -0
- package/node_modules/@bytetrue/server/dist/server/server/bootstrap.d.ts +6 -0
- package/node_modules/@bytetrue/server/dist/server/server/bootstrap.js +49 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{index-8d553cf4f8b1937dcd7c1da125243e39.js → index-5762beef0f3c7a285c88f6cb383d0f16.js} +485 -484
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-5762beef0f3c7a285c88f6cb383d0f16.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-5762beef0f3c7a285c88f6cb383d0f16.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-9f3696181e42915ed4f9cb01f7595007.js → indexeddb-attachment-store-433f8ff1c621845eba95cc633f47b72d.js} +1 -1
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-433f8ff1c621845eba95cc633f47b72d.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-433f8ff1c621845eba95cc633f47b72d.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-f829a425d0a064f4a9b4b153d185a3fb.js → native-file-attachment-store-6ed537a199e954caec287e6d28945868.js} +3 -3
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ed537a199e954caec287e6d28945868.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ed537a199e954caec287e6d28945868.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/index.html +1 -1
- package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.gz +0 -0
- package/node_modules/@bytetrue/server/dist/src/server/agent/agent-manager.js +118 -8
- package/node_modules/@bytetrue/server/dist/src/server/agent/providers/claude/agent.js +1 -1
- package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/agent-subagents.js +159 -0
- package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/agent.js +86 -151
- package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/runtime.js +0 -4
- package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/session-descriptor.js +2 -2
- package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/subagent-session-tailer.js +212 -0
- package/node_modules/@bytetrue/server/package.json +5 -5
- package/package.json +7 -7
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-8d553cf4f8b1937dcd7c1da125243e39.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-8d553cf4f8b1937dcd7c1da125243e39.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-9f3696181e42915ed4f9cb01f7595007.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-9f3696181e42915ed4f9cb01f7595007.js.gz +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-f829a425d0a064f4a9b4b153d185a3fb.js.br +0 -0
- package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-f829a425d0a064f4a9b4b153d185a3fb.js.gz +0 -0
- /package/node_modules/@bytetrue/server/dist/server/server/agent/{providers/claude/subagents → provider-subagents}/observation.js +0 -0
- /package/node_modules/@bytetrue/server/dist/src/server/agent/{providers/claude/subagents → provider-subagents}/observation.js +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdtempSync,
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { join
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
import stripAnsi from "strip-ansi";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { importSessionFromPersistence } from "../../provider-session-import.js";
|
|
@@ -19,6 +19,8 @@ import { revertPiConversation } from "./rewind.js";
|
|
|
19
19
|
import { listPiImportableSessions, readPiImportSessionConfig } from "./session-descriptor.js";
|
|
20
20
|
import { PiUsagePoller } from "./usage-poller.js";
|
|
21
21
|
import { buildPiNestedToolCallMetadata, mapToolDetail, parseToolArgs, parseToolResult, resolveToolCallName, } from "./tool-call-mapper.js";
|
|
22
|
+
import { foldSubagentObservations } from "../../provider-subagents/observation.js";
|
|
23
|
+
import { isPiSubagentToolName, observePiSubagentExit, observePiSubagentLaunch, observePiSubagentRecord, replayPiSubagentObservations, } from "./agent-subagents.js";
|
|
22
24
|
const PI_PROVIDER = "pi";
|
|
23
25
|
const DEFAULT_PI_THINKING_LEVEL = "medium";
|
|
24
26
|
const PI_MODEL_PROBE_CACHE_MS = 30000;
|
|
@@ -79,7 +81,7 @@ const PI_CAPABILITIES = {
|
|
|
79
81
|
supportsSessionPersistence: true,
|
|
80
82
|
supportsSessionListing: true,
|
|
81
83
|
supportsDynamicModes: true,
|
|
82
|
-
supportsMcpServers:
|
|
84
|
+
supportsMcpServers: true,
|
|
83
85
|
supportsReasoningStream: true,
|
|
84
86
|
supportsToolInvocations: true,
|
|
85
87
|
supportsRewindConversation: true,
|
|
@@ -95,12 +97,6 @@ const PI_THINKING_OPTIONS = [
|
|
|
95
97
|
{ id: "xhigh", label: "XHigh", description: "Very deep reasoning" },
|
|
96
98
|
{ id: "max", label: "Max", description: "Extreme reasoning" },
|
|
97
99
|
];
|
|
98
|
-
function capabilitiesForClient() {
|
|
99
|
-
return withPiCapabilities(false);
|
|
100
|
-
}
|
|
101
|
-
function capabilitiesForSession(hasMcpConfig) {
|
|
102
|
-
return withPiCapabilities(hasMcpConfig);
|
|
103
|
-
}
|
|
104
100
|
// COMPAT(piSteerFallback): added in v0.5.0, remove after 2027-03-01 once the pi floor
|
|
105
101
|
// supports the steer RPC. Older binaries answer with "Unknown command: steer".
|
|
106
102
|
function isPiDefinitiveSteerRejection(error) {
|
|
@@ -312,7 +308,6 @@ function buildResumeStartInput(input) {
|
|
|
312
308
|
session: input.sessionFile,
|
|
313
309
|
model: input.resumeConfig.model,
|
|
314
310
|
thinkingOptionId: normalizePiThinkingOption(input.resumeConfig.thinkingOptionId) ?? undefined,
|
|
315
|
-
mcpConfigPath: input.mcpConfig?.path,
|
|
316
311
|
extensionPaths: input.byspaceExtension ? [input.byspaceExtension.path] : undefined,
|
|
317
312
|
};
|
|
318
313
|
}
|
|
@@ -327,72 +322,10 @@ function toPiMcpConfig(config) {
|
|
|
327
322
|
return {
|
|
328
323
|
url: config.url,
|
|
329
324
|
...(config.headers ? { headers: config.headers } : {}),
|
|
330
|
-
auth: false,
|
|
331
|
-
oauth: false,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function resolvePiAgentDir(env) {
|
|
335
|
-
const configured = env?.PI_CODING_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim();
|
|
336
|
-
if (!configured) {
|
|
337
|
-
return join(homedir(), ".pi", "agent");
|
|
338
|
-
}
|
|
339
|
-
if (configured === "~") {
|
|
340
|
-
return homedir();
|
|
341
|
-
}
|
|
342
|
-
if (configured.startsWith("~/")) {
|
|
343
|
-
return resolvePath(homedir(), configured.slice(2));
|
|
344
|
-
}
|
|
345
|
-
return resolvePath(configured);
|
|
346
|
-
}
|
|
347
|
-
function readPiGlobalMcpConfig(env) {
|
|
348
|
-
const globalConfigPath = join(resolvePiAgentDir(env), "mcp.json");
|
|
349
|
-
if (!existsSync(globalConfigPath)) {
|
|
350
|
-
return {};
|
|
351
|
-
}
|
|
352
|
-
let globalConfig;
|
|
353
|
-
try {
|
|
354
|
-
globalConfig = JSON.parse(readFileSync(globalConfigPath, "utf8"));
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
if (error instanceof SyntaxError) {
|
|
358
|
-
throw new Error(`Failed to parse Pi MCP config: ${globalConfigPath}`, { cause: error });
|
|
359
|
-
}
|
|
360
|
-
throw error;
|
|
361
|
-
}
|
|
362
|
-
if (!isRecord(globalConfig)) {
|
|
363
|
-
throw new Error(`Pi MCP config must contain a JSON object: ${globalConfigPath}`);
|
|
364
|
-
}
|
|
365
|
-
return globalConfig;
|
|
366
|
-
}
|
|
367
|
-
function createPiMcpConfigFile(servers, options) {
|
|
368
|
-
const globalConfig = options?.piGlobalConfigEnv
|
|
369
|
-
? readPiGlobalMcpConfig(options.piGlobalConfigEnv)
|
|
370
|
-
: {};
|
|
371
|
-
let configuredServers = {};
|
|
372
|
-
if (isRecord(globalConfig.mcpServers)) {
|
|
373
|
-
configuredServers = globalConfig.mcpServers;
|
|
374
|
-
}
|
|
375
|
-
else if (isRecord(globalConfig["mcp-servers"])) {
|
|
376
|
-
configuredServers = globalConfig["mcp-servers"];
|
|
377
|
-
}
|
|
378
|
-
const mcpServers = { ...configuredServers };
|
|
379
|
-
for (const [name, serverConfig] of Object.entries(servers)) {
|
|
380
|
-
mcpServers[name] = toPiMcpConfig(serverConfig);
|
|
381
|
-
}
|
|
382
|
-
const dir = mkdtempSync(join(tmpdir(), "byspace-pi-mcp-"));
|
|
383
|
-
const filePath = join(dir, "mcp.json");
|
|
384
|
-
const mergedConfig = { ...globalConfig, mcpServers };
|
|
385
|
-
delete mergedConfig["mcp-servers"];
|
|
386
|
-
writeFileSync(filePath, `${JSON.stringify(mergedConfig, null, 2)}\n`, {
|
|
387
|
-
encoding: "utf8",
|
|
388
|
-
mode: 0o600,
|
|
389
|
-
});
|
|
390
|
-
return {
|
|
391
|
-
path: filePath,
|
|
392
|
-
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
393
325
|
};
|
|
394
326
|
}
|
|
395
|
-
function createPiBySpaceExtensionFile(systemPrompt) {
|
|
327
|
+
function createPiBySpaceExtensionFile(systemPrompt, mcpServers) {
|
|
328
|
+
const byspaceMcpServers = Object.fromEntries(Object.entries(mcpServers ?? {}).map(([name, config]) => [name, toPiMcpConfig(config)]));
|
|
396
329
|
const dir = mkdtempSync(join(tmpdir(), "byspace-pi-extension-"));
|
|
397
330
|
const filePath = join(dir, "byspace-integration.mjs");
|
|
398
331
|
writeFileSync(filePath, `
|
|
@@ -444,6 +377,50 @@ function createPiBySpaceExtensionFile(systemPrompt) {
|
|
|
444
377
|
}
|
|
445
378
|
|
|
446
379
|
export default function byspaceIntegration(pi) {
|
|
380
|
+
const byspaceMcpServers = ${JSON.stringify(byspaceMcpServers)};
|
|
381
|
+
const rejectedMcpServers = [];
|
|
382
|
+
if (typeof pi.registerMcpServer === "function") {
|
|
383
|
+
for (const [name, config] of Object.entries(byspaceMcpServers)) {
|
|
384
|
+
// A duplicate name or an invalid config throws; the rest of this extension must still load.
|
|
385
|
+
try {
|
|
386
|
+
pi.registerMcpServer(name, config);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
rejectedMcpServers.push({ name, message: error && error.message ? error.message : String(error) });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Pi only connects registered MCP servers while its built-in MCP support is loaded. Another
|
|
394
|
+
// extension that owns the "mcp" command replaces it, leaving these registrations stranded.
|
|
395
|
+
function reportMcpInjection(ctx) {
|
|
396
|
+
for (const rejected of rejectedMcpServers) {
|
|
397
|
+
ctx.ui.notify(
|
|
398
|
+
'BySpace could not register MCP server "' + rejected.name + '": ' + rejected.message,
|
|
399
|
+
"warning",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const registeredCount = Object.keys(byspaceMcpServers).length;
|
|
403
|
+
if (registeredCount === 0 || rejectedMcpServers.length === registeredCount) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (typeof pi.getCommands !== "function") {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const builtinMcpLoaded = pi.getCommands().some(
|
|
410
|
+
(command) =>
|
|
411
|
+
command &&
|
|
412
|
+
command.name === "mcp" &&
|
|
413
|
+
command.sourceInfo &&
|
|
414
|
+
command.sourceInfo.path === "builtin:mcp",
|
|
415
|
+
);
|
|
416
|
+
if (!builtinMcpLoaded) {
|
|
417
|
+
ctx.ui.notify(
|
|
418
|
+
"Pi's built-in MCP support is not loaded, so the MCP servers BySpace registered for this agent are not connected. Another extension may have replaced it.",
|
|
419
|
+
"warning",
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
447
424
|
const submittedUserMessages = [];
|
|
448
425
|
|
|
449
426
|
function emitSubmittedUserEntries(ctx) {
|
|
@@ -476,6 +453,7 @@ function createPiBySpaceExtensionFile(systemPrompt) {
|
|
|
476
453
|
|
|
477
454
|
pi.on("session_start", async (_event, ctx) => {
|
|
478
455
|
emitEntryCapture(ctx, "session_start");
|
|
456
|
+
reportMcpInjection(ctx);
|
|
479
457
|
});
|
|
480
458
|
|
|
481
459
|
pi.on("message_end", async (event) => {
|
|
@@ -525,32 +503,6 @@ function createPiBySpaceExtensionFile(systemPrompt) {
|
|
|
525
503
|
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
526
504
|
};
|
|
527
505
|
}
|
|
528
|
-
function combineCleanup(cleanups) {
|
|
529
|
-
const activeCleanups = cleanups.filter((cleanup) => Boolean(cleanup));
|
|
530
|
-
if (activeCleanups.length === 0) {
|
|
531
|
-
return undefined;
|
|
532
|
-
}
|
|
533
|
-
return () => {
|
|
534
|
-
for (const cleanup of activeCleanups) {
|
|
535
|
-
cleanup();
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
function isPiMcpAdapterCommand(command) {
|
|
540
|
-
if (command.source !== "extension" || !/^mcp(?::\d+)?$/.test(command.name)) {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
if (!command.sourceInfo) {
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
return JSON.stringify(command.sourceInfo).includes("pi-mcp-adapter");
|
|
547
|
-
}
|
|
548
|
-
function withPiCapabilities(supportsMcpServers) {
|
|
549
|
-
return {
|
|
550
|
-
...PI_CAPABILITIES,
|
|
551
|
-
supportsMcpServers,
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
506
|
function isPiRequestAbortError(error) {
|
|
555
507
|
if (error instanceof Error && error.name === "AbortError") {
|
|
556
508
|
return true;
|
|
@@ -1117,6 +1069,11 @@ export class PiRpcAgentSession {
|
|
|
1117
1069
|
async *streamHistory() {
|
|
1118
1070
|
await this.requestEntryCapture("history");
|
|
1119
1071
|
yield* streamPiHistory(this.provider, await this.runtimeSession.getMessages(), this.capturedUserEntries);
|
|
1072
|
+
// Background subagent lifecycle lives in the transcript as tool result details and
|
|
1073
|
+
// subagent-exit custom messages, not in a sidechain — derive it from the same messages.
|
|
1074
|
+
for (const subagentEvent of foldSubagentObservations(replayPiSubagentObservations(await this.runtimeSession.getMessages()))) {
|
|
1075
|
+
yield { type: "provider_subagent", provider: this.provider, event: subagentEvent };
|
|
1076
|
+
}
|
|
1120
1077
|
}
|
|
1121
1078
|
async getRuntimeInfo() {
|
|
1122
1079
|
await this.refreshState();
|
|
@@ -1904,6 +1861,23 @@ export class PiRpcAgentSession {
|
|
|
1904
1861
|
this.emitToolCallEvent(event.toolCallId, toolCall, status, result, error, {
|
|
1905
1862
|
parentToolCallId: event.parentToolCallId,
|
|
1906
1863
|
});
|
|
1864
|
+
this.emitPiSubagentToolObservations(event.toolCallId, toolCall, result);
|
|
1865
|
+
}
|
|
1866
|
+
/** Background subagents announce themselves through tool result details, not the stream. */
|
|
1867
|
+
emitPiSubagentToolObservations(toolCallId, toolCall, result) {
|
|
1868
|
+
if (!isPiSubagentToolName(toolCall.toolName)) {
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
const details = typeof result === "object" && result !== null ? result.details : undefined;
|
|
1872
|
+
const observation = toolCall.toolName === "subagent"
|
|
1873
|
+
? observePiSubagentLaunch({ toolCallId, args: toolCall.args, details })
|
|
1874
|
+
: observePiSubagentRecord(details);
|
|
1875
|
+
if (!observation) {
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
for (const event of foldSubagentObservations([observation])) {
|
|
1879
|
+
this.emit({ type: "provider_subagent", provider: this.provider, event });
|
|
1880
|
+
}
|
|
1907
1881
|
}
|
|
1908
1882
|
emitCompactionTimeline(input) {
|
|
1909
1883
|
const emitOutOfBand = this.outOfBandCompactionEmit;
|
|
@@ -1977,6 +1951,11 @@ export class PiRpcAgentSession {
|
|
|
1977
1951
|
// Pi extension sendMessage() payloads surface as custom messages. display=false
|
|
1978
1952
|
// means context-only (Pi's TUI hides them too). display is optional on older
|
|
1979
1953
|
// runtimes; treat missing as true so exit notifications still surface.
|
|
1954
|
+
if (event.message.customType === "subagent-exit") {
|
|
1955
|
+
for (const subagentEvent of foldSubagentObservations(observePiSubagentExit(event.message.details))) {
|
|
1956
|
+
this.emit({ type: "provider_subagent", provider: this.provider, event: subagentEvent });
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1980
1959
|
if (event.message.display !== false) {
|
|
1981
1960
|
const text = getUserMessageText(event.message.content);
|
|
1982
1961
|
const details = limitTimelineDetails(event.message.details);
|
|
@@ -2077,7 +2056,7 @@ export class PiRpcAgentClient {
|
|
|
2077
2056
|
constructor(options) {
|
|
2078
2057
|
this.availableProvidersCache = null;
|
|
2079
2058
|
this.provider = PI_PROVIDER;
|
|
2080
|
-
this.capabilities =
|
|
2059
|
+
this.capabilities = PI_CAPABILITIES;
|
|
2081
2060
|
this.logger = options.logger;
|
|
2082
2061
|
this.runtimeSettings = options.runtimeSettings;
|
|
2083
2062
|
this.providerParams = PiProviderParamsSchema.parse(options.providerParams ?? {});
|
|
@@ -2087,12 +2066,7 @@ export class PiRpcAgentClient {
|
|
|
2087
2066
|
this.usagePollScheduler = options.usagePollScheduler;
|
|
2088
2067
|
}
|
|
2089
2068
|
async createSession(config, launchContext) {
|
|
2090
|
-
const
|
|
2091
|
-
...this.runtimeSettings?.env,
|
|
2092
|
-
...launchContext?.env,
|
|
2093
|
-
};
|
|
2094
|
-
const mcpConfig = await this.prepareMcpConfig(config.cwd, config.mcpServers, mcpEnv);
|
|
2095
|
-
const byspaceExtension = createPiBySpaceExtensionFile(composeSystemPromptParts(config.systemPrompt, config.daemonAppendSystemPrompt));
|
|
2069
|
+
const byspaceExtension = createPiBySpaceExtensionFile(composeSystemPromptParts(config.systemPrompt, config.daemonAppendSystemPrompt), config.mcpServers);
|
|
2096
2070
|
const launchModel = await this.resolveLaunchModel(config.cwd, launchContext?.env, config.model, {
|
|
2097
2071
|
skipProbe: config.internal === true,
|
|
2098
2072
|
});
|
|
@@ -2104,12 +2078,10 @@ export class PiRpcAgentClient {
|
|
|
2104
2078
|
thinkingOptionId: normalizePiThinkingOption(config.thinkingOptionId) ?? DEFAULT_PI_THINKING_LEVEL,
|
|
2105
2079
|
noSession: config.internal === true,
|
|
2106
2080
|
env: launchContext?.env,
|
|
2107
|
-
mcpConfigPath: mcpConfig?.path,
|
|
2108
2081
|
extensionPaths: byspaceExtension ? [byspaceExtension.path] : undefined,
|
|
2109
2082
|
});
|
|
2110
2083
|
}
|
|
2111
2084
|
catch (error) {
|
|
2112
|
-
mcpConfig?.cleanup();
|
|
2113
2085
|
byspaceExtension?.cleanup();
|
|
2114
2086
|
throw error;
|
|
2115
2087
|
}
|
|
@@ -2118,8 +2090,8 @@ export class PiRpcAgentClient {
|
|
|
2118
2090
|
runtimeSession,
|
|
2119
2091
|
config: launchModel === config.model ? config : { ...config, model: launchModel },
|
|
2120
2092
|
initialState: await runtimeSession.getState(),
|
|
2121
|
-
capabilities:
|
|
2122
|
-
cleanup:
|
|
2093
|
+
capabilities: PI_CAPABILITIES,
|
|
2094
|
+
cleanup: byspaceExtension?.cleanup,
|
|
2123
2095
|
extensionTimeoutMs: this.providerParams.extensionTimeoutMs,
|
|
2124
2096
|
logger: this.logger,
|
|
2125
2097
|
usagePollScheduler: this.usagePollScheduler,
|
|
@@ -2127,7 +2099,6 @@ export class PiRpcAgentClient {
|
|
|
2127
2099
|
}
|
|
2128
2100
|
catch (error) {
|
|
2129
2101
|
await runtimeSession.close().catch(() => undefined);
|
|
2130
|
-
mcpConfig?.cleanup();
|
|
2131
2102
|
byspaceExtension?.cleanup();
|
|
2132
2103
|
throw error;
|
|
2133
2104
|
}
|
|
@@ -2139,12 +2110,7 @@ export class PiRpcAgentClient {
|
|
|
2139
2110
|
}
|
|
2140
2111
|
const persistenceMetadata = parsePersistenceMetadata(handle.metadata);
|
|
2141
2112
|
const resumeConfig = buildResumeConfig(persistenceMetadata, overrides, this.provider);
|
|
2142
|
-
const
|
|
2143
|
-
...this.runtimeSettings?.env,
|
|
2144
|
-
...launchContext?.env,
|
|
2145
|
-
};
|
|
2146
|
-
const mcpConfig = await this.prepareMcpConfig(resumeConfig.cwd, resumeConfig.config.mcpServers, mcpEnv);
|
|
2147
|
-
const byspaceExtension = createPiBySpaceExtensionFile(composeSystemPromptParts(resumeConfig.config.systemPrompt, resumeConfig.config.daemonAppendSystemPrompt));
|
|
2113
|
+
const byspaceExtension = createPiBySpaceExtensionFile(composeSystemPromptParts(resumeConfig.config.systemPrompt, resumeConfig.config.daemonAppendSystemPrompt), resumeConfig.config.mcpServers);
|
|
2148
2114
|
const launchModel = await this.resolveLaunchModel(resumeConfig.cwd, launchContext?.env, resumeConfig.model);
|
|
2149
2115
|
const resumedConfig = launchModel === resumeConfig.model
|
|
2150
2116
|
? resumeConfig
|
|
@@ -2159,12 +2125,10 @@ export class PiRpcAgentClient {
|
|
|
2159
2125
|
resumeConfig: resumedConfig,
|
|
2160
2126
|
sessionFile,
|
|
2161
2127
|
launchContext,
|
|
2162
|
-
mcpConfig,
|
|
2163
2128
|
byspaceExtension,
|
|
2164
2129
|
}));
|
|
2165
2130
|
}
|
|
2166
2131
|
catch (error) {
|
|
2167
|
-
mcpConfig?.cleanup();
|
|
2168
2132
|
byspaceExtension?.cleanup();
|
|
2169
2133
|
throw error;
|
|
2170
2134
|
}
|
|
@@ -2173,8 +2137,8 @@ export class PiRpcAgentClient {
|
|
|
2173
2137
|
runtimeSession,
|
|
2174
2138
|
config: resumedConfig.config,
|
|
2175
2139
|
initialState: await runtimeSession.getState(),
|
|
2176
|
-
capabilities:
|
|
2177
|
-
cleanup:
|
|
2140
|
+
capabilities: PI_CAPABILITIES,
|
|
2141
|
+
cleanup: byspaceExtension?.cleanup,
|
|
2178
2142
|
extensionTimeoutMs: this.providerParams.extensionTimeoutMs,
|
|
2179
2143
|
logger: this.logger,
|
|
2180
2144
|
usagePollScheduler: this.usagePollScheduler,
|
|
@@ -2182,7 +2146,6 @@ export class PiRpcAgentClient {
|
|
|
2182
2146
|
}
|
|
2183
2147
|
catch (error) {
|
|
2184
2148
|
await runtimeSession.close().catch(() => undefined);
|
|
2185
|
-
mcpConfig?.cleanup();
|
|
2186
2149
|
byspaceExtension?.cleanup();
|
|
2187
2150
|
throw error;
|
|
2188
2151
|
}
|
|
@@ -2335,34 +2298,6 @@ export class PiRpcAgentClient {
|
|
|
2335
2298
|
await probeSession.close().catch(() => undefined);
|
|
2336
2299
|
}
|
|
2337
2300
|
}
|
|
2338
|
-
async prepareMcpConfig(cwd, servers, env) {
|
|
2339
|
-
if (!servers || Object.keys(servers).length === 0) {
|
|
2340
|
-
return null;
|
|
2341
|
-
}
|
|
2342
|
-
if (!(await this.detectMcpAdapter(cwd, env))) {
|
|
2343
|
-
return null;
|
|
2344
|
-
}
|
|
2345
|
-
return createPiMcpConfigFile(servers, { piGlobalConfigEnv: env });
|
|
2346
|
-
}
|
|
2347
|
-
async detectMcpAdapter(cwd, env) {
|
|
2348
|
-
const runtimeSession = await this.runtime.startSession({ cwd, env }).catch((error) => {
|
|
2349
|
-
this.logger.debug({ err: error, cwd }, "Pi MCP adapter probe failed to start");
|
|
2350
|
-
return null;
|
|
2351
|
-
});
|
|
2352
|
-
if (!runtimeSession) {
|
|
2353
|
-
return false;
|
|
2354
|
-
}
|
|
2355
|
-
try {
|
|
2356
|
-
return (await runtimeSession.getCommands()).some(isPiMcpAdapterCommand);
|
|
2357
|
-
}
|
|
2358
|
-
catch (error) {
|
|
2359
|
-
this.logger.debug({ err: error, cwd }, "Pi MCP adapter probe failed");
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
finally {
|
|
2363
|
-
await runtimeSession.close().catch(() => undefined);
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
2301
|
async resolvePiLaunch() {
|
|
2367
2302
|
return resolveProviderLaunch({
|
|
2368
2303
|
commandConfig: this.runtimeSettings?.command,
|
|
@@ -20,7 +20,6 @@ export function buildPiLaunch(input) {
|
|
|
20
20
|
modeId: input.session.modeId,
|
|
21
21
|
session: input.session.session,
|
|
22
22
|
noSession: input.session.noSession,
|
|
23
|
-
mcpConfigPath: input.session.mcpConfigPath,
|
|
24
23
|
extensionPaths: input.session.extensionPaths,
|
|
25
24
|
extraArgs: input.session.extraArgs,
|
|
26
25
|
};
|
|
@@ -44,9 +43,6 @@ function appendPiLaunchArgs(argv, session, protocolMode) {
|
|
|
44
43
|
else if (session.session) {
|
|
45
44
|
argv.push("--session", session.session);
|
|
46
45
|
}
|
|
47
|
-
if (session.mcpConfigPath) {
|
|
48
|
-
argv.push("--mcp-config", session.mcpConfigPath);
|
|
49
|
-
}
|
|
50
46
|
for (const extensionPath of session.extensionPaths ?? []) {
|
|
51
47
|
argv.push("--extension", extensionPath);
|
|
52
48
|
}
|
package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/session-descriptor.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function readPiImportSessionConfig(filePath) {
|
|
|
41
41
|
return {};
|
|
42
42
|
return toPiImportSessionConfig(descriptor);
|
|
43
43
|
}
|
|
44
|
-
async function resolvePiSessionsDir(options) {
|
|
44
|
+
export async function resolvePiSessionsDir(options) {
|
|
45
45
|
const env = options.env ?? process.env;
|
|
46
46
|
const homeDir = options.homeDir ?? homedir();
|
|
47
47
|
const baseDir = options.cwd ?? process.cwd();
|
|
@@ -100,7 +100,7 @@ function resolveConfigPath(value, options) {
|
|
|
100
100
|
}
|
|
101
101
|
return path.isAbsolute(value) ? value : path.resolve(options.baseDir, value);
|
|
102
102
|
}
|
|
103
|
-
async function walkJsonlFiles(root) {
|
|
103
|
+
export async function walkJsonlFiles(root) {
|
|
104
104
|
let entries;
|
|
105
105
|
try {
|
|
106
106
|
entries = await readdir(root, { withFileTypes: true });
|
package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/subagent-session-tailer.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { open, stat } from "node:fs/promises";
|
|
2
|
+
import { mapPiSubagentStatus } from "./agent-subagents.js";
|
|
3
|
+
import { PiHistoryMapper } from "./history-mapper.js";
|
|
4
|
+
import { resolvePiSessionsDir, walkJsonlFiles } from "./session-descriptor.js";
|
|
5
|
+
export const PI_SUBAGENT_TERMINAL_STATUSES = ["completed", "failed", "canceled"];
|
|
6
|
+
const DEFAULT_POLL_MS = 1000;
|
|
7
|
+
const LOCATE_RETRY_MS = 5000;
|
|
8
|
+
const TERMINAL_DRAIN_GRACE_MS = 3000;
|
|
9
|
+
/**
|
|
10
|
+
* Extract the tailer-relevant fields from a pi-subagent report record. The subagent id is
|
|
11
|
+
* required (it keys the tailer); session references start the tail, status tracks the end.
|
|
12
|
+
* Status-only snapshots matter too: they flip the entry to terminal so the tail stops.
|
|
13
|
+
*/
|
|
14
|
+
export function parsePiSubagentTailerSnapshot(record) {
|
|
15
|
+
if (typeof record !== "object" || record === null)
|
|
16
|
+
return null;
|
|
17
|
+
const source = record;
|
|
18
|
+
const subagentId = readString(source.id);
|
|
19
|
+
if (!subagentId)
|
|
20
|
+
return null;
|
|
21
|
+
const snapshot = { subagentId };
|
|
22
|
+
for (const key of ["sessionId", "sessionFile", "cwd", "status"]) {
|
|
23
|
+
const value = source[key];
|
|
24
|
+
if (typeof value === "string" && value.trim())
|
|
25
|
+
snapshot[key] = value;
|
|
26
|
+
}
|
|
27
|
+
return snapshot.sessionId || snapshot.sessionFile || snapshot.cwd || snapshot.status
|
|
28
|
+
? snapshot
|
|
29
|
+
: null;
|
|
30
|
+
}
|
|
31
|
+
function readString(value) {
|
|
32
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
33
|
+
}
|
|
34
|
+
async function drainPiSessionTailer(tailer, onTimeline) {
|
|
35
|
+
const info = await stat(tailer.file).catch(() => null);
|
|
36
|
+
if (!info?.isFile())
|
|
37
|
+
return;
|
|
38
|
+
if (info.size < tailer.offset) {
|
|
39
|
+
// File shrank (recreated): restart from the beginning with fresh mapper state.
|
|
40
|
+
tailer.offset = 0;
|
|
41
|
+
tailer.mapper = new PiHistoryMapper("pi");
|
|
42
|
+
}
|
|
43
|
+
if (info.size === tailer.offset)
|
|
44
|
+
return;
|
|
45
|
+
const handle = await open(tailer.file, "r");
|
|
46
|
+
try {
|
|
47
|
+
const length = info.size - tailer.offset;
|
|
48
|
+
const buffer = Buffer.alloc(length);
|
|
49
|
+
// A short read means the file was truncated between stat and read; parse only
|
|
50
|
+
// the bytes that actually arrived.
|
|
51
|
+
const { bytesRead } = await handle.read(buffer, 0, length, tailer.offset);
|
|
52
|
+
const text = buffer.toString("utf8", 0, bytesRead);
|
|
53
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
54
|
+
if (lastNewline === -1)
|
|
55
|
+
return;
|
|
56
|
+
const chunk = text.slice(0, lastNewline + 1);
|
|
57
|
+
tailer.offset += Buffer.byteLength(chunk);
|
|
58
|
+
for (const line of chunk.split("\n")) {
|
|
59
|
+
const trimmed = line.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
continue;
|
|
62
|
+
let entry;
|
|
63
|
+
try {
|
|
64
|
+
entry = JSON.parse(trimmed);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (typeof entry !== "object" || entry === null)
|
|
70
|
+
continue;
|
|
71
|
+
const message = entry.message;
|
|
72
|
+
if (typeof message !== "object" || message === null)
|
|
73
|
+
continue;
|
|
74
|
+
const timestamp = readString(entry.timestamp) ?? undefined;
|
|
75
|
+
const events = tailer.mapper.mapMessages([message]);
|
|
76
|
+
for (const event of events) {
|
|
77
|
+
if (event.type === "timeline") {
|
|
78
|
+
onTimeline(event.item, timestamp);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await handle.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function locatePiSubagentSessionFile(sessionId, options = {}) {
|
|
88
|
+
const sessionsDir = await resolvePiSessionsDir({
|
|
89
|
+
cwd: options.cwd ?? undefined,
|
|
90
|
+
env: options.env ?? process.env,
|
|
91
|
+
homeDir: options.homeDir,
|
|
92
|
+
});
|
|
93
|
+
const suffix = `_${sessionId}.jsonl`;
|
|
94
|
+
const files = await walkJsonlFiles(sessionsDir);
|
|
95
|
+
return files.find((file) => file.endsWith(suffix)) ?? null;
|
|
96
|
+
}
|
|
97
|
+
export class PiSubagentSessionTailers {
|
|
98
|
+
constructor(options) {
|
|
99
|
+
this.entries = new Map();
|
|
100
|
+
this.timer = null;
|
|
101
|
+
this.queue = Promise.resolve();
|
|
102
|
+
this.options = options;
|
|
103
|
+
}
|
|
104
|
+
start() {
|
|
105
|
+
if (this.timer)
|
|
106
|
+
return;
|
|
107
|
+
this.timer = setInterval(() => {
|
|
108
|
+
void this.tick();
|
|
109
|
+
}, DEFAULT_POLL_MS);
|
|
110
|
+
this.timer.unref?.();
|
|
111
|
+
}
|
|
112
|
+
stop() {
|
|
113
|
+
if (this.timer) {
|
|
114
|
+
clearInterval(this.timer);
|
|
115
|
+
this.timer = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
update(agentId, snapshot) {
|
|
119
|
+
const subagentId = snapshot.subagentId;
|
|
120
|
+
const key = `${agentId}\u0000${subagentId}`;
|
|
121
|
+
let entry = this.entries.get(key);
|
|
122
|
+
if (!entry) {
|
|
123
|
+
entry = {
|
|
124
|
+
agentId,
|
|
125
|
+
subagentId,
|
|
126
|
+
sessionId: null,
|
|
127
|
+
sessionFile: null,
|
|
128
|
+
cwd: null,
|
|
129
|
+
tailer: null,
|
|
130
|
+
lastLocateAttempt: 0,
|
|
131
|
+
terminalAt: null,
|
|
132
|
+
};
|
|
133
|
+
this.entries.set(key, entry);
|
|
134
|
+
this.start();
|
|
135
|
+
}
|
|
136
|
+
if (snapshot.sessionId)
|
|
137
|
+
entry.sessionId = snapshot.sessionId;
|
|
138
|
+
if (snapshot.sessionFile)
|
|
139
|
+
entry.sessionFile = snapshot.sessionFile;
|
|
140
|
+
if (snapshot.cwd)
|
|
141
|
+
entry.cwd = snapshot.cwd;
|
|
142
|
+
const status = mapPiSubagentStatus(snapshot.status ?? "");
|
|
143
|
+
if (status &&
|
|
144
|
+
PI_SUBAGENT_TERMINAL_STATUSES.includes(status) &&
|
|
145
|
+
entry.terminalAt === null) {
|
|
146
|
+
entry.terminalAt = Date.now();
|
|
147
|
+
}
|
|
148
|
+
// No reopen-on-running: reports have no ordering guarantee, so a stale running
|
|
149
|
+
// snapshot must not resurrect a finished subagent's tail. New launches are new ids.
|
|
150
|
+
}
|
|
151
|
+
removeAgent(agentId) {
|
|
152
|
+
for (const [key, entry] of this.entries) {
|
|
153
|
+
if (entry.agentId === agentId)
|
|
154
|
+
this.entries.delete(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
dispose() {
|
|
158
|
+
this.stop();
|
|
159
|
+
this.entries.clear();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Drain every entry once. Timer and manual (test) ticks serialize through a promise
|
|
163
|
+
* chain instead of skipping, so an awaited tick never no-ops behind an in-flight one.
|
|
164
|
+
*/
|
|
165
|
+
tick() {
|
|
166
|
+
const run = this.queue.then(() => this.drain());
|
|
167
|
+
this.queue = run.catch(() => { });
|
|
168
|
+
return run;
|
|
169
|
+
}
|
|
170
|
+
async drain() {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
for (const [key, entry] of this.entries) {
|
|
173
|
+
if (!entry.tailer && now - entry.lastLocateAttempt >= LOCATE_RETRY_MS) {
|
|
174
|
+
entry.lastLocateAttempt = now;
|
|
175
|
+
entry.tailer = await this.locate(entry);
|
|
176
|
+
}
|
|
177
|
+
if (entry.tailer) {
|
|
178
|
+
try {
|
|
179
|
+
await drainPiSessionTailer(entry.tailer, (item, timestamp) => {
|
|
180
|
+
this.options.emit(entry.agentId, {
|
|
181
|
+
kind: "timeline",
|
|
182
|
+
id: entry.subagentId,
|
|
183
|
+
item,
|
|
184
|
+
...(timestamp ? { timestamp } : {}),
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Transient read failures (rotation, permissions): retry next tick.
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (entry.terminalAt !== null &&
|
|
193
|
+
now >= entry.terminalAt + (this.options.terminalGraceMs ?? TERMINAL_DRAIN_GRACE_MS)) {
|
|
194
|
+
this.entries.delete(key);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async locate(entry) {
|
|
199
|
+
const file = entry.sessionFile ??
|
|
200
|
+
(entry.sessionId
|
|
201
|
+
? await locatePiSubagentSessionFile(entry.sessionId, {
|
|
202
|
+
cwd: entry.cwd,
|
|
203
|
+
env: this.options.env,
|
|
204
|
+
homeDir: this.options.homeDir,
|
|
205
|
+
})
|
|
206
|
+
: null);
|
|
207
|
+
if (!file)
|
|
208
|
+
return null;
|
|
209
|
+
return { file, offset: 0, mapper: new PiHistoryMapper("pi") };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=subagent-session-tailer.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytetrue/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "BySpace backend server",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/server",
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
"test:e2e:ui": "vitest --ui e2e.test.ts"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@bytetrue/client": "0.
|
|
81
|
-
"@bytetrue/highlight": "0.
|
|
82
|
-
"@bytetrue/protocol": "0.
|
|
83
|
-
"@bytetrue/relay": "0.
|
|
80
|
+
"@bytetrue/client": "0.18.0",
|
|
81
|
+
"@bytetrue/highlight": "0.18.0",
|
|
82
|
+
"@bytetrue/protocol": "0.18.0",
|
|
83
|
+
"@bytetrue/relay": "0.18.0"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@playwright/test": "^1.56.1",
|