@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.
Files changed (54) hide show
  1. package/node_modules/@bytetrue/client/package.json +3 -3
  2. package/node_modules/@bytetrue/highlight/package.json +1 -1
  3. package/node_modules/@bytetrue/protocol/package.json +1 -1
  4. package/node_modules/@bytetrue/relay/package.json +1 -1
  5. package/node_modules/@bytetrue/server/dist/server/server/agent/agent-manager.d.ts +30 -0
  6. package/node_modules/@bytetrue/server/dist/server/server/agent/agent-manager.js +118 -8
  7. package/node_modules/@bytetrue/server/dist/server/server/agent/mcp-shared.d.ts +1 -1
  8. package/node_modules/@bytetrue/server/dist/server/server/agent/{providers/claude/subagents → provider-subagents}/observation.d.ts +2 -2
  9. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/agent.js +1 -1
  10. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/subagents/live-source.d.ts +1 -1
  11. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/subagents/replay-source.d.ts +1 -1
  12. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/claude/subagents/workflow-replay-source.d.ts +1 -1
  13. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/omp/rpc-types.d.ts +6 -6
  14. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent-subagents.d.ts +45 -0
  15. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent-subagents.js +159 -0
  16. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent.d.ts +2 -2
  17. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/agent.js +86 -151
  18. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/runtime.d.ts +0 -2
  19. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/runtime.js +0 -4
  20. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/session-descriptor.d.ts +2 -0
  21. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/session-descriptor.js +2 -2
  22. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/subagent-session-tailer.d.ts +47 -0
  23. package/node_modules/@bytetrue/server/dist/server/server/agent/providers/pi/subagent-session-tailer.js +212 -0
  24. package/node_modules/@bytetrue/server/dist/server/server/bootstrap.d.ts +6 -0
  25. package/node_modules/@bytetrue/server/dist/server/server/bootstrap.js +49 -0
  26. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/{index-8d553cf4f8b1937dcd7c1da125243e39.js → index-5762beef0f3c7a285c88f6cb383d0f16.js} +485 -484
  27. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-5762beef0f3c7a285c88f6cb383d0f16.js.br +0 -0
  28. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-5762beef0f3c7a285c88f6cb383d0f16.js.gz +0 -0
  29. 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
  30. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-433f8ff1c621845eba95cc633f47b72d.js.br +0 -0
  31. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-433f8ff1c621845eba95cc633f47b72d.js.gz +0 -0
  32. 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
  33. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ed537a199e954caec287e6d28945868.js.br +0 -0
  34. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-6ed537a199e954caec287e6d28945868.js.gz +0 -0
  35. package/node_modules/@bytetrue/server/dist/server/web-ui/index.html +1 -1
  36. package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.br +0 -0
  37. package/node_modules/@bytetrue/server/dist/server/web-ui/index.html.gz +0 -0
  38. package/node_modules/@bytetrue/server/dist/src/server/agent/agent-manager.js +118 -8
  39. package/node_modules/@bytetrue/server/dist/src/server/agent/providers/claude/agent.js +1 -1
  40. package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/agent-subagents.js +159 -0
  41. package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/agent.js +86 -151
  42. package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/runtime.js +0 -4
  43. package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/session-descriptor.js +2 -2
  44. package/node_modules/@bytetrue/server/dist/src/server/agent/providers/pi/subagent-session-tailer.js +212 -0
  45. package/node_modules/@bytetrue/server/package.json +5 -5
  46. package/package.json +7 -7
  47. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-8d553cf4f8b1937dcd7c1da125243e39.js.br +0 -0
  48. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/index-8d553cf4f8b1937dcd7c1da125243e39.js.gz +0 -0
  49. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-9f3696181e42915ed4f9cb01f7595007.js.br +0 -0
  50. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-9f3696181e42915ed4f9cb01f7595007.js.gz +0 -0
  51. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-f829a425d0a064f4a9b4b153d185a3fb.js.br +0 -0
  52. package/node_modules/@bytetrue/server/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-f829a425d0a064f4a9b4b153d185a3fb.js.gz +0 -0
  53. /package/node_modules/@bytetrue/server/dist/server/server/agent/{providers/claude/subagents → provider-subagents}/observation.js +0 -0
  54. /package/node_modules/@bytetrue/server/dist/src/server/agent/{providers/claude/subagents → provider-subagents}/observation.js +0 -0
@@ -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
@@ -34,6 +34,12 @@ import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js
34
34
  import { type ManagedProcessRegistry } from "./managed-processes/managed-processes.js";
35
35
  import { type HostnamesConfig } from "./hostnames.js";
36
36
  import { type DaemonAuthConfig } from "./auth.js";
37
+ /**
38
+ * Local, token-gated snapshots reported by the BySpace-installed subagent extension;
39
+ * deliberately skips daemon auth. The manager is created after route mounting, so it resolves
40
+ * through a getter filled in once construction finishes (503 until then).
41
+ */
42
+ export declare function createPiSubagentReportRouteHandler(getAgentManager: () => AgentManager | null): express.RequestHandler;
37
43
  export declare function createTerminalActivityRouteHandler(terminalManager: TerminalManager): express.RequestHandler;
38
44
  export type DaemonLifecycleIntent = {
39
45
  type: "shutdown";
@@ -175,6 +175,13 @@ function resolveAgentMcpClientHost(host) {
175
175
  }
176
176
  return host;
177
177
  }
178
+ function createLocalApiBaseUrl(listenTarget) {
179
+ if (!listenTarget || listenTarget.type !== "tcp") {
180
+ return null;
181
+ }
182
+ const host = resolveAgentMcpClientHost(listenTarget.host);
183
+ return `http://${formatHostForHttpUrl(host)}:${listenTarget.port}`;
184
+ }
178
185
  function createAgentMcpBaseUrl(listenTarget) {
179
186
  if (!listenTarget || listenTarget.type !== "tcp") {
180
187
  return null;
@@ -203,6 +210,40 @@ const LOOPBACK_REMOTE_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1
203
210
  function isLoopbackRemoteAddress(remoteAddress) {
204
211
  return remoteAddress !== undefined && LOOPBACK_REMOTE_ADDRESSES.has(remoteAddress);
205
212
  }
213
+ const PiSubagentReportSchema = z.object({
214
+ token: z.string().min(1),
215
+ observations: z.array(z.record(z.string(), z.unknown())).min(1),
216
+ });
217
+ /**
218
+ * Local, token-gated snapshots reported by the BySpace-installed subagent extension;
219
+ * deliberately skips daemon auth. The manager is created after route mounting, so it resolves
220
+ * through a getter filled in once construction finishes (503 until then).
221
+ */
222
+ export function createPiSubagentReportRouteHandler(getAgentManager) {
223
+ return async (req, res) => {
224
+ const agentManager = getAgentManager();
225
+ if (!agentManager) {
226
+ res.status(503).json({ error: "Daemon is still starting" });
227
+ return;
228
+ }
229
+ if (!isLoopbackRemoteAddress(req.socket.remoteAddress)) {
230
+ res.status(403).json({ error: "Forbidden" });
231
+ return;
232
+ }
233
+ const parsed = PiSubagentReportSchema.safeParse(req.body);
234
+ if (!parsed.success) {
235
+ res.status(400).json({ error: "Invalid subagent report" });
236
+ return;
237
+ }
238
+ const { token, observations } = parsed.data;
239
+ const outcome = agentManager.applyPiSubagentReport(token, observations);
240
+ if (outcome === "unauthorized") {
241
+ res.status(403).json({ error: "Forbidden" });
242
+ return;
243
+ }
244
+ res.status(204).end();
245
+ };
246
+ }
206
247
  export function createTerminalActivityRouteHandler(terminalManager) {
207
248
  return async (req, res) => {
208
249
  if (!isLoopbackRemoteAddress(req.socket.remoteAddress)) {
@@ -520,6 +561,12 @@ export async function createBySpaceDaemon(config, rootLogger, dependencies = {})
520
561
  });
521
562
  // Local, harmless, and token-gated; deliberately skips daemon auth.
522
563
  app.post("/api/terminal-activity", express.json(), createTerminalActivityRouteHandler(terminalManager));
564
+ // Subagent progress snapshots reported by the BySpace-installed subagent extension. Local,
565
+ // token-gated per agent launch; deliberately skips daemon auth. The route must mount before
566
+ // the bearer middleware below, but the manager is created later in this function, so it
567
+ // resolves through a holder filled in right after construction.
568
+ let piSubagentReportAgentManager = null;
569
+ app.post("/api/subagent-report", express.json(), createPiSubagentReportRouteHandler(() => piSubagentReportAgentManager));
523
570
  // Serve the bundled browser web UI when enabled. Mounted after service-proxy
524
571
  // classification and host/CORS handling, but before daemon bearer auth, so
525
572
  // static app files load without the daemon password while API/WebSocket calls
@@ -667,6 +714,7 @@ export async function createBySpaceDaemon(config, rootLogger, dependencies = {})
667
714
  resolveBySpaceToolPolicy: (provider) => resolveBySpaceToolPolicy(provider, daemonConfigStore.get().providers),
668
715
  logger,
669
716
  });
717
+ piSubagentReportAgentManager = agentManager;
670
718
  const detachAgentStoragePersistence = attachAgentStoragePersistence(logger, agentManager, agentStorage);
671
719
  await agentStorage.initialize();
672
720
  logger.info({ elapsed: elapsed() }, "Agent storage initialized");
@@ -1155,6 +1203,7 @@ export async function createBySpaceDaemon(config, rootLogger, dependencies = {})
1155
1203
  agentMcpBaseUrl =
1156
1204
  !mcpEnabled || config.mcpInjectIntoAgents === false ? null : mcpBaseUrl;
1157
1205
  agentManager.setMcpBaseUrl(agentMcpBaseUrl);
1206
+ agentManager.setPiSubagentReportBaseUrl(createLocalApiBaseUrl(boundListenTarget));
1158
1207
  agentManager.setBySpaceToolsEnabled(mcpEnabled && config.mcpInjectIntoAgents !== false);
1159
1208
  daemonConfigStore.onFieldChange("mcp.enabled", (value) => {
1160
1209
  mcpEnabled = value !== false;