Skip to content
Merged
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
24 changes: 24 additions & 0 deletions src/agent/core/interfaces/i-harness-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@
*/
deleteOutcomes(projectId: string, commandType: string): Promise<number>

/**
* Remove the pin for a `(projectId, commandType)` pair.
* Idempotent — returns `true` if a pin existed and was removed,
* `false` if no pin was set.
*/
deletePin(projectId: string, commandType: string): Promise<boolean>

/**
* Delete a single scenario by its `(projectId, commandType, scenarioId)` key.
* Returns `true` when the scenario existed and was deleted; `false` on miss.
Expand All @@ -78,6 +85,23 @@
scenarioId: string,
): Promise<boolean>

/**
* Delete every scenario for a `(projectId, commandType)` pair.
* Returns the number of records deleted. Used by `brv harness reset`.
*/
deleteScenarios(projectId: string, commandType: string): Promise<number>

/**
* Delete a single version by its `(projectId, commandType, versionId)` key.
* Returns `true` when the version existed and was deleted; `false` on miss.
* Used by `brv harness reset` to clear all versions for a pair.
*/
deleteVersion(
projectId: string,
commandType: string,
versionId: string,
): Promise<boolean>

/**
* Return the most-recently-written version for a `(projectId, commandType)`
* pair — ranked by the stored `version` number, not by `heuristic`. This
Expand Down Expand Up @@ -192,7 +216,7 @@
* Persist a new harness version. Templates bootstrap as v1; refinements
* write v2, v3, … each pointing to a parent.
*
* @throws {HarnessStoreError} with code `VERSION_CONFLICT` when a version

Check warning on line 219 in src/agent/core/interfaces/i-harness-store.ts

View workflow job for this annotation

GitHub Actions / lint

The type 'HarnessStoreError' is undefined
* with the same `id`, or the same `(projectId, commandType, version)`
* tuple, already exists.
*/
Expand Down
16 changes: 13 additions & 3 deletions src/agent/infra/agent/cipher-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {AgentState, ExecutionContext, ICipherAgent} from '../../core/interf
import type {IHistoryStorage} from '../../core/interfaces/i-history-storage.js'
import type {ITokenizer} from '../../core/interfaces/i-tokenizer.js'
import type {FileSystemService} from '../file-system/file-system-service.js'
import type {HarnessSynthesizer} from '../harness/harness-synthesizer.js'
import type {MemoryManager} from '../memory/memory-manager.js'
import type {ProcessService} from '../process/process-service.js'
import type {SystemPromptManager} from '../system-prompt/system-prompt-manager.js'
Expand Down Expand Up @@ -513,6 +514,15 @@ export class CipherAgent extends BaseAgent implements ICipherAgent {
}
}

/**
* Public accessor for the harness synthesizer. Used by `brv harness refine`
* via the agent-process task dispatch. Returns `undefined` when harness is
* disabled (synthesizer was never instantiated).
*/
public getHarnessSynthesizer(): HarnessSynthesizer | undefined {
return this.services?.harnessSynthesizer
}

/**
* Get an existing session or create a new one.
*/
Expand Down Expand Up @@ -572,7 +582,7 @@ export class CipherAgent extends BaseAgent implements ICipherAgent {

protected override async initializeServices(): Promise<CipherAgentServices> {
// Pass pre-created event bus to service initializer
return createCipherAgentServices(this.config, this._agentEventBus)
return createCipherAgentServices(this.config, this._agentEventBus, () => this.buildHttpConfig())
}

/**
Expand Down Expand Up @@ -660,6 +670,8 @@ export class CipherAgent extends BaseAgent implements ICipherAgent {
this.rebindCurateTools(services, httpConfig, sessionLLMConfig)
}

// === Protected Methods (implement abstract from BaseAgent) ===

/**
* Reset the agent to initial state.
* Resets execution state only. To reset sessions, use resetSession(sessionId).
Expand All @@ -671,8 +683,6 @@ export class CipherAgent extends BaseAgent implements ICipherAgent {
}
}

// === Protected Methods (implement abstract from BaseAgent) ===

/**
* Reset a specific session's conversation history.
* @param sessionId - The session ID to reset
Expand Down
4 changes: 4 additions & 0 deletions src/agent/infra/agent/service-initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,10 @@
* @param agentEventBus - Pre-created event bus from agent constructor
* @returns Initialized shared services
*/
export async function createCipherAgentServices(

Check warning on line 138 in src/agent/infra/agent/service-initializer.ts

View workflow job for this annotation

GitHub Actions / lint

Async function 'createCipherAgentServices' has a complexity of 21. Maximum allowed is 20
config: ValidatedAgentConfig,
agentEventBus: AgentEventBus,
httpConfigProvider?: () => ByteRoverHttpConfig,
): Promise<CipherAgentServices> {
// 1. Logger (uses provided event bus )
const logger = new EventBasedLogger(agentEventBus, 'CipherAgent')
Expand Down Expand Up @@ -333,6 +334,9 @@
: config.providerApiKey,
baseUrl: config.providerBaseUrl,
headers: config.providerHeaders,
// byterover provider needs httpConfig for API routing (sessionKey,
// projectId, spaceId, teamId). Resolved lazily from CipherAgent.
httpConfig: httpConfigProvider ? {...httpConfigProvider()} : undefined,
httpReferer: config.httpReferer,
maxTokens: 4096,
model: refinementModel,
Expand Down
53 changes: 53 additions & 0 deletions src/agent/infra/harness/harness-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ export class HarnessStore implements IHarnessStore {

// ── scenarios ─────────────────────────────────────────────────────────────

async deletePin(projectId: string, commandType: string): Promise<boolean> {
const key = this.pinKey(projectId, commandType)
const exists = await this.keyStorage.get(key)
if (exists === undefined) return false
await this.keyStorage.delete(key)
return true
}

async deleteScenario(
projectId: string,
commandType: string,
Expand Down Expand Up @@ -149,6 +157,51 @@ export class HarnessStore implements IHarnessStore {

// ── versions ───────────────────────────────────────────────────────────────

async deleteScenarios(projectId: string, commandType: string): Promise<number> {
const keys: StorageKey[] = []
for (const projectType of ProjectTypeSchema.options) {
// eslint-disable-next-line no-await-in-loop
const entries = await this.keyStorage.listWithValues<EvaluationScenario>([
HARNESS_PREFIX,
SCENARIO_PREFIX,
projectType,
projectId,
commandType,
])
for (const entry of entries) keys.push(entry.key)
}

if (keys.length === 0) return 0

const operations: BatchOperation[] = keys.map((key) => ({key, type: 'delete' as const}))
await this.keyStorage.batch(operations)
this.logger.debug('HarnessStore.deleteScenarios cleared partition', {
commandType,
deleted: keys.length,
projectId,
})

return keys.length
}

async deleteVersion(
projectId: string,
commandType: string,
versionId: string,
): Promise<boolean> {
const key = this.versionKey(projectId, commandType, versionId)
const exists = await this.keyStorage.exists(key)
if (!exists) return false

await this.keyStorage.delete(key)
this.logger.debug('HarnessStore.deleteVersion removed entry', {
commandType,
projectId,
versionId,
})
return true
}

async getLatest(projectId: string, commandType: string): Promise<HarnessVersion | undefined> {
// Delegate to `listVersions` rather than re-deriving the "max version"
// comparator — a future change to the sort key can't silently break
Expand Down
14 changes: 6 additions & 8 deletions src/agent/infra/llm/agent-llm-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {CompactionService} from './context/compaction/compaction-service.js
import type {ICompressionStrategy} from './context/compression/types.js'

import {getErrorMessage} from '../../../server/utils/error-helpers.js'
import {sanitizeProjectPath} from '../../../server/utils/path-utils.js'
import {AgentStateMachine} from '../../core/domain/agent/agent-state-machine.js'
import {AgentState, TerminationReason} from '../../core/domain/agent/agent-state.js'
import {LlmGenerationError, LlmMaxIterationsError, LlmResponseParsingError} from '../../core/domain/errors/llm-error.js'
Expand Down Expand Up @@ -901,14 +902,11 @@ export class AgentLLMService implements ILLMService {
}

try {
// Slug/path gap workaround (known issue — see
// outcome-collection.test.ts:32): the recorder derives projectId
// from `environmentContext.workingDirectory`, so we use the same
// source here. `bootstrapIfNeeded` takes both `projectId` (for
// store key partitioning) and `workingDirectory` (for filesystem
// detection); at present they're the same value, aliased for
// readability at the call site.
const projectId = this.workingDirectory
// projectId is the sanitized working directory — FileKeyStorage
// rejects path separators in key segments, so the raw absolute
// path cannot be used as a store partition key. `workingDirectory`
// stays unsanitized for filesystem detection (template language).
const projectId = sanitizeProjectPath(this.workingDirectory)
const {workingDirectory} = this

await harnessBootstrap.bootstrapIfNeeded(projectId, commandType, workingDirectory)
Expand Down
3 changes: 2 additions & 1 deletion src/agent/infra/sandbox/sandbox-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { HarnessOutcomeRecorder } from '../harness/harness-outcome-recorder
import type { SessionManager } from '../session/session-manager.js'
import type { ISearchKnowledgeService, ToolsSDK } from './tools-sdk.js'

import { sanitizeProjectPath } from '../../../server/utils/path-utils.js'
import { ProjectTypeSchema } from '../../core/domain/harness/types.js'
import {HarnessEvaluatorError} from '../harness/harness-evaluator-errors.js'
import { OpsCounter } from '../harness/ops-counter.js'
Expand Down Expand Up @@ -232,7 +233,7 @@ export class SandboxService implements ISandboxService {
conversationTurn: config?.conversationTurn,
executionTimeMs: result.executionTime,
harnessVersionId: this.harnessVersionIdBySession.get(sessionId),
projectId: this.environmentContext.workingDirectory,
projectId: sanitizeProjectPath(this.environmentContext.workingDirectory),
projectType: this.resolveProjectType(),
result,
sessionId,
Expand Down
Loading
Loading