@bivy/bivy 0.10.1-staging.359 → 0.10.1-staging.360

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.
@@ -185,6 +185,9 @@ function handleCompletedItem(params) {
185
185
  if (!item || typeof item !== "object" || !itemId) return;
186
186
  switch (item.type) {
187
187
  case "agentMessage":
188
+ // Child-thread prose belongs to the delegated agent, not the parent's
189
+ // answer stream. Its lifecycle is represented by the collaboration items.
190
+ if (params?.threadId && threadId && params.threadId !== threadId) return;
188
191
  if (!agentMessageItems.has(itemId) && item.text) {
189
192
  agentMessageItems.add(itemId);
190
193
  bivy({ type: "message.delta", text: String(item.text) });
@@ -317,20 +320,29 @@ function onServerRequest(m) {
317
320
 
318
321
  function onNotification(m) {
319
322
  const { method, params } = m;
323
+ // Codex publishes child-thread prose on the same app-server connection as the
324
+ // parent. It is not a continuation of the parent's assistant message: folding
325
+ // both token streams together produces character-level corruption whenever a
326
+ // delegated agent answers concurrently. Child lifecycle remains visible via
327
+ // collabAgentToolCall/subAgentActivity; only prose/reasoning is scoped here.
328
+ const fromCurrentThread = !params?.threadId || !threadId || params.threadId === threadId;
320
329
  switch (method) {
321
- case "item/agentMessage/delta":
322
- if (typeof params.delta === "string" && params.delta) {
323
- const itemId = paramsItemId(params);
324
- if (itemId) agentMessageItems.add(itemId);
330
+ case "item/agentMessage/delta": {
331
+ const itemId = paramsItemId(params);
332
+ // Remember ignored child items too so their later item/completed echo is
333
+ // not mistaken for a parent message and replayed wholesale.
334
+ if (itemId) agentMessageItems.add(itemId);
335
+ if (fromCurrentThread && typeof params.delta === "string" && params.delta) {
325
336
  bivy({ type: "message.delta", text: params.delta });
326
337
  }
327
338
  return;
339
+ }
328
340
  case "item/plan/delta":
329
- emitReasoning(paramsItemId(params), params.delta);
341
+ if (fromCurrentThread) emitReasoning(paramsItemId(params), params.delta);
330
342
  return;
331
343
  case "item/reasoning/summaryTextDelta":
332
344
  case "item/reasoning/textDelta":
333
- emitReasoning(paramsItemId(params), params.delta);
345
+ if (fromCurrentThread) emitReasoning(paramsItemId(params), params.delta);
334
346
  return;
335
347
  case "item/commandExecution/outputDelta": {
336
348
  const itemId = paramsItemId(params);
@@ -4,7 +4,7 @@ import { defineAgentIntegration } from "../definition.js";
4
4
  import { withExactCapabilitySurface } from "../../runtime/types.js";
5
5
  import { createCredentialStore } from "../../runtime/credentials.js";
6
6
  import { ClaudeCodeRuntime, claudeRuntimeFromEnv, claudeSdkInstalled, } from "./runtime.js";
7
- export const CLAUDE_TESTED_VERSION = "0.3.232";
7
+ export const CLAUDE_TESTED_VERSION = "0.3.233";
8
8
  const CLAUDE_CAPABILITIES = withExactCapabilitySurface({
9
9
  toolInterception: true,
10
10
  modelSelection: true,
@@ -484,7 +484,15 @@ export class PiRuntime {
484
484
  return this.build(sessionManager, options);
485
485
  }
486
486
  openSession(options) {
487
- const sessionManager = SessionManager.open(options.sessionFile, this.options.sessionsDir);
487
+ // Pi intentionally defers writing a new transcript until the first assistant
488
+ // response. A named empty session can therefore outlive its in-memory host
489
+ // (restart/detach) while its metadata path does not exist yet. Recreate that
490
+ // empty shell with Bivy's canonical id instead of letting SessionManager.open
491
+ // assign a new id, which would duplicate the sidebar row. Existing files keep
492
+ // Pi's normal full-fidelity resume path.
493
+ const sessionManager = !fs.existsSync(options.sessionFile) && options.canonicalId
494
+ ? SessionManager.create(options.workspace, this.options.sessionsDir, { id: options.canonicalId })
495
+ : SessionManager.open(options.sessionFile, this.options.sessionsDir);
488
496
  return this.build(sessionManager, options);
489
497
  }
490
498
  /**
@@ -3,7 +3,7 @@
3
3
  export const CERTIFICATION_MATRIX = {
4
4
  schemaVersion: 1,
5
5
  agents: [
6
- { id: "claude-code-sdk", status: "active", executionMode: "protocol", pinnedVersion: "0.3.232", capabilities: ["toolInterception", "modelSelection", "resume"] },
6
+ { id: "claude-code-sdk", status: "active", executionMode: "protocol", pinnedVersion: "0.3.233", capabilities: ["toolInterception", "modelSelection", "resume"] },
7
7
  { id: "codex-approvals", status: "active", executionMode: "protocol", pinnedVersion: "0.147.0", capabilities: ["toolInterception", "modelSelection", "resume"] },
8
8
  { id: "pi", status: "active", executionMode: "protocol", pinnedVersion: "0.84.2", capabilities: ["toolInterception", "modelSelection", "resume"] },
9
9
  { id: "opencode", status: "active", executionMode: "protocol", pinnedVersion: "1.18.18", capabilities: ["toolInterception", "modelSelection", "resume"] },
package/dist/server.js CHANGED
@@ -101,7 +101,7 @@ import { createGithubCommands } from "./controllers/github-commands.js";
101
101
  import { createCredentialCommands } from "./controllers/credential-commands.js";
102
102
  import { createCustomModelCommands } from "./controllers/custom-model-commands.js";
103
103
  import { MetadataStore } from "./metadata.js";
104
- import { resolveResumeRef, resumeRefFor } from "./session-ref.js";
104
+ import { resolveResumeRef, resumeRefFor, storedResumeRef } from "./session-ref.js";
105
105
  import { materializeFork } from "./session/fork.js";
106
106
  import { applyDirtyPatch } from "./session/fork-dirty.js";
107
107
  import { normalizeMessages } from "./session/transcript-normal.js";
@@ -7719,9 +7719,17 @@ async function createSession(workspace = defaultWorkspace, sessionFile, opts = {
7719
7719
  // made Claude Code (and any id-based runtime) sessions un-resumable with
7720
7720
  // "Session file is outside the sessions directory".
7721
7721
  const requestedRef = typeof sessionFile === "string" && sessionFile.trim() ? sessionFile.trim() : undefined;
7722
- const refRuntimeId = opts.runtimeId ?? (requestedRef ? metadata.getSession(requestedRef)?.runtimeId : undefined);
7723
- const requestedSessionFile = requestedRef
7724
- ? resolveResumeRef({ ref: requestedRef, resumesByPath: runtimeResumesByPath(refRuntimeId), sessionsDir })
7722
+ const requestedMeta = requestedRef ? metadata.getSession(requestedRef) : undefined;
7723
+ const refRuntimeId = opts.runtimeId ?? requestedMeta?.runtimeId;
7724
+ // Some callers (notably the local CLI HTTP API) name a closed session by its
7725
+ // Bivy id and enter createSession directly rather than resolveOrResumeSession.
7726
+ // Translate that id to the owning runtime's durable ref before applying the
7727
+ // path guard/opening it. Without this, a valid empty Pi session was interpreted
7728
+ // as a relative filename and rejected as outside pi/sessions; id-based native
7729
+ // refs could likewise resume the wrong id. Explicit path refs remain unchanged.
7730
+ const durableRef = requestedRef ? storedResumeRef(requestedRef, requestedMeta) : undefined;
7731
+ const requestedSessionFile = durableRef
7732
+ ? resolveResumeRef({ ref: durableRef, resumesByPath: runtimeResumesByPath(refRuntimeId), sessionsDir })
7725
7733
  : undefined;
7726
7734
  const storedMeta = requestedSessionFile ? metadata.getSession(requestedSessionFile) : undefined;
7727
7735
  const restoredWorktree = requestedSessionFile ? restoredWorktreeFromMetadata(storedMeta) : undefined;
@@ -40,6 +40,20 @@ export function resolveResumeRef(opts) {
40
40
  }
41
41
  return resolved;
42
42
  }
43
+ /**
44
+ * Replace a client-supplied session id with the runtime resume ref stored for
45
+ * that exact metadata row. Callers that already supply a path keep that path:
46
+ * metadata stores are commonly indexed by both id and path, and treating a path
47
+ * lookup as an id lookup would make an explicit ref unexpectedly change.
48
+ *
49
+ * This is intentionally runtime-neutral. Pi stores a transcript path, while
50
+ * Codex/OpenCode can store a provider-native thread id; both are opaque refs at
51
+ * this layer.
52
+ */
53
+ export function storedResumeRef(requestedRef, meta) {
54
+ const stored = meta?.path?.trim();
55
+ return meta?.id === requestedRef && stored ? stored : requestedRef;
56
+ }
43
57
  /**
44
58
  * Decide what to resume a client-named session from when the node isn't holding
45
59
  * it in memory (a restart, an idle close, or a session this process never
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.10.1-staging.359",
3
+ "version": "0.10.1-staging.360",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",
@@ -38,7 +38,7 @@
38
38
  "postinstall": "node bin/patch-pi-dependencies.mjs"
39
39
  },
40
40
  "dependencies": {
41
- "@anthropic-ai/claude-agent-sdk": "0.3.232",
41
+ "@anthropic-ai/claude-agent-sdk": "0.3.233",
42
42
  "@earendil-works/pi-ai": "0.84.2",
43
43
  "@earendil-works/pi-coding-agent": "0.84.2",
44
44
  "@modelcontextprotocol/sdk": "1.30.0",