Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,17 @@ const writeSettings = async (

const currentUserName = () => "user"

const deriveUserPeerId = (settings: Pick<HonchoSettings, "peerName">) =>
normalizeId(settings.peerName || currentUserName())

const assertDistinctUserAndAgentPeers = (userPeerId: string, rootAgentPeerId: string) => {
if (userPeerId === rootAgentPeerId) {
throw new Error(
`Invalid Honcho config: peerName and aiPeer both resolve to the peer id '${userPeerId}'; they must differ so user and agent memory stay separate.`,
)
}
}

const rootApiKey = (raw: Record<string, unknown>) => {
const legacyApiKey = typeof raw[LEGACY_API_KEY_FIELD] === "string" ? expandEnv(raw[LEGACY_API_KEY_FIELD] as string) : ""
return legacyApiKey
Expand Down Expand Up @@ -754,8 +765,9 @@ const deriveRuntimeHandle = async (
const sessionId = extractSessionId(input)
const repoName = path.basename(rootDir)
const workspaceId = normalizeId(settings.workspace || "opencode")
const userPeerId = normalizeId(`user:${settings.peerName || currentUserName()}`)
const userPeerId = deriveUserPeerId(settings)
const rootAgentPeerId = normalizeId(settings.aiPeer || "opencode")
assertDistinctUserAndAgentPeers(userPeerId, rootAgentPeerId)
const activeAgentPeerId = rootAgentPeerId
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const childAgentPeerId = null
const parentAgentObserverPeerId = null
Expand Down Expand Up @@ -1667,6 +1679,8 @@ export const createHonchoRuntimePlugin =
export const HonchoRuntimePlugin = createHonchoRuntimePlugin()
export const __testing = {
createSessionState,
deriveUserPeerId,
assertDistinctUserAndAgentPeers,
deriveSessionStateKey,
extractCompletedAssistantMessage,
honchoSdkImportPath: "@honcho-ai/sdk",
Expand Down
2 changes: 1 addition & 1 deletion tests/context-injection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const createHonchoFetch = ({ failStableHydration = false } = {}) => {
return jsonResponse({
peer_id: peerId,
target_id: null,
representation: peerId.startsWith("user-")
representation: peerId === "user"
? "The user prefers concise engineering analysis."
: "The assistant is working on opencode-honcho.",
peer_card: ["Keep changes narrowly scoped."],
Expand Down
13 changes: 13 additions & 0 deletions tests/peer-collision.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expect, test } from "bun:test"

import { __testing } from "../dist/index.js"

test("distinct user and agent peers are accepted", () => {
expect(() => __testing.assertDistinctUserAndAgentPeers("rui", "opencode")).not.toThrow()
})

test("colliding user and agent peers are rejected", () => {
expect(() => __testing.assertDistinctUserAndAgentPeers("opencode", "opencode")).toThrow(
/peerName and aiPeer both resolve to the peer id 'opencode'/,
)
})
8 changes: 4 additions & 4 deletions tests/peer-topology.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { __testing } from "../dist/index.js"
test("root sessions keep user and root agent as peers", () => {
const topology = __testing.buildPeerTopology({
config: {},
userPeerId: "user:user",
userPeerId: "user",
rootAgentPeerId: "opencode",
activeAgentPeerId: "opencode",
childAgentPeerId: null,
parentAgentObserverPeerId: null,
})

expect(topology.sessionPeerConfigs).toEqual({
"user:user": { observeMe: true, observeOthers: false },
"user": { observeMe: true, observeOthers: false },
opencode: { observeMe: true, observeOthers: true },
})
expect(topology.describedPeers.childAgentPeer).toBeNull()
Expand All @@ -23,15 +23,15 @@ test("root sessions keep user and root agent as peers", () => {
test("classic peer model keeps delegated sessions on the Claude-style user and ai peers", () => {
const topology = __testing.buildPeerTopology({
config: {},
userPeerId: "user:user",
userPeerId: "user",
rootAgentPeerId: "opencode",
activeAgentPeerId: "opencode:reviewer",
childAgentPeerId: "opencode:reviewer",
parentAgentObserverPeerId: "opencode:root-parent",
})

expect(topology.sessionPeerConfigs).toEqual({
"user:user": { observeMe: true, observeOthers: false },
"user": { observeMe: true, observeOthers: false },
opencode: { observeMe: true, observeOthers: true },
})
expect(topology.describedPeers.childAgentPeer).toBeNull()
Expand Down
15 changes: 15 additions & 0 deletions tests/user-peer-id.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from "bun:test"

import { __testing } from "../dist/index.js"

test("user peer id is the bare peerName with no prefix", () => {
expect(__testing.deriveUserPeerId({ peerName: "rui" })).toBe("rui")
})

test("user peer id falls back to the current user name when peerName is empty", () => {
expect(__testing.deriveUserPeerId({ peerName: "" })).toBe("user")
})

test("user peer id is normalised", () => {
expect(__testing.deriveUserPeerId({ peerName: "Rui Rei" })).toBe("rui-rei")
})