diff --git a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts index ef7ec771b7..bc798fef1f 100644 --- a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts @@ -23,9 +23,9 @@ import Sandbox from '@deepseek-ai/dsh-sandbox-local' import SandboxPolicy from '@deepseek-ai/dsh-sandbox-policy' import LocalFileSystem from '@deepseek-ai/dsh-fs-local' import * as ToolFs from '@deepseek-ai/dsh-tool-fs' -import * as WorkspaceContext from '@deepseek-ai/dsh-agent-instructions' +import * as AgentInstructions from '@deepseek-ai/dsh-agent-instructions' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import CordisHostRunner from '@deepseek-ai/dsh-cordis-host-runner' import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis' @@ -80,7 +80,7 @@ async function workspacePtcModeHarness(): Promise { await harness.plugin(AgentRegistry) await harness.plugin(LocalFileSystem, { cwd: '/' }) await harness.plugin(ToolFs) - await harness.plugin(WorkspaceContext, { maxBytes: 65536 }) + await harness.plugin(AgentInstructions, { maxBytes: 65536 }) await harness.plugin(AgentLoop, { agents: [] }) await harness.plugin(LlmDeepSeek, { models: [{ id: 'deepseek-v4-flash' }] }) await mountRuntime(harness) @@ -140,7 +140,7 @@ async function typedPtcModeHarness(): Promise { async function backgroundPtcModeHarness(cwd: string): Promise { const harness = await typedPtcModeHarness() await harness.plugin(LocalJobRegistry) - await harness.plugin(ToolTasks, {}) + await harness.plugin(ToolJobs, {}) if (harness.get('subprocess') === undefined) await harness.plugin(LocalSubprocessRuntime) await harness.plugin(BashEnvPlugin) await harness.plugin(LocalBashExecutor, { cwd, timeoutMs: 30_000 }) diff --git a/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts index 6fc5216298..cb4fdea338 100644 --- a/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts @@ -25,7 +25,7 @@ import { } from '@deepseek-ai/dsh-session' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' import { logPath } from '../../../../../../packages/session/session-persistence-jsonl/src/format.ts' -import { renderWorkspaceContext } from '@deepseek-ai/dsh-agent-instructions' +import { renderAgentInstructions } from '@deepseek-ai/dsh-agent-instructions' import { resolveConfig, workspaceBaselineIdentity } from '@deepseek-ai/dsh-agent-instructions/src/config.ts' import { describe, expect, it } from 'vitest' @@ -73,7 +73,7 @@ async function seedVisibleBaseline( delegationDepth: 0, } const files = options.files ?? [{ name: 'AGENTS.md', content: oldInstruction }] - const baseline = renderWorkspaceContext(files.map(file => ({ + const baseline = renderAgentInstructions(files.map(file => ({ absolutePath: join(cwd, file.name), displayPath: file.name, content: file.content, diff --git a/packages/context/agent-instructions/src/files.ts b/packages/context/agent-instructions/src/files.ts index b91cab12a1..fe1816dc74 100644 --- a/packages/context/agent-instructions/src/files.ts +++ b/packages/context/agent-instructions/src/files.ts @@ -14,8 +14,8 @@ import { resolveConfig, resolveDiscoveryConfig, type ResolvedConfig } from './co import { trimmedInstructionDigest } from './digest.ts' import { decodeScopeKey, - renderWorkspaceInstructionSet, - type RenderedWorkspaceContext, + renderAgentInstructionSet, + type RenderedAgentInstructions, USER_GLOBAL_DIRECTORY, USER_GLOBAL_FILE, } from './render.ts' @@ -64,7 +64,7 @@ interface LoadOptions extends DiscoverOptions { /** Rendered baseline plus the successfully read and byte-budget-retained files. */ export interface RenderedInstructionSet { - rendered: RenderedWorkspaceContext + rendered: RenderedAgentInstructions /** Successfully read candidates before content deduplication and byte budgeting. */ observed: LoadedInstructionFile[] /** Candidates retained by content deduplication and byte budgeting. */ @@ -401,7 +401,7 @@ export function dedupInstructionFilesByDirectory(files: LoadedInstructionFile[]) export async function loadBaselineInstructions( options: LoadOptions, fileSystem?: FileSystem, -): Promise { +): Promise { return (await loadBaselineInstructionSet(options, fileSystem))?.rendered } @@ -434,7 +434,7 @@ export async function loadBaselineInstructionSet( const deduped = dedupInstructionFilesByDirectory(loaded) if (deduped.length === 0) { if (options.replacePreviousBaseline !== true) return undefined - const { rendered, included } = renderWorkspaceInstructionSet([], { + const { rendered, included } = renderAgentInstructionSet([], { maxBytes: config.maxBytes, replacePreviousBaseline: true, }) @@ -444,7 +444,7 @@ export async function loadBaselineInstructionSet( included, } } - const { rendered, included } = renderWorkspaceInstructionSet(deduped, { + const { rendered, included } = renderAgentInstructionSet(deduped, { maxBytes: config.maxBytes, ...options.replacePreviousBaseline === undefined ? {} diff --git a/packages/context/agent-instructions/src/index.ts b/packages/context/agent-instructions/src/index.ts index f775c30a9c..82cd0451f3 100644 --- a/packages/context/agent-instructions/src/index.ts +++ b/packages/context/agent-instructions/src/index.ts @@ -23,7 +23,7 @@ import { baselineInstructionState, name, reconcileInstructionContext, - workspaceContextMessage, + agentInstructionsMessage, type InstructionVersionCache, type AgentInstructionSource, } from './state.ts' @@ -40,8 +40,8 @@ export type { InstructionFile, LoadedInstructionFile, } from './files.ts' -export { renderWorkspaceContext } from './render.ts' -export type { RenderedWorkspaceContext, TruncatedInstruction } from './render.ts' +export { renderAgentInstructions } from './render.ts' +export type { RenderedAgentInstructions, TruncatedInstruction } from './render.ts' function visibleBaselineSource( agent: Agent, @@ -62,7 +62,7 @@ function visibleBaselineSource( return undefined } -function isWorkspaceContext(message: UserMessage): boolean { +function isAgentInstructionsMessage(message: UserMessage): boolean { return message.source.kind === 'agent-instructions' } @@ -162,7 +162,7 @@ export function apply(ctx: Context, config: Config): void { } for (const [scope, state] of baseline.versions) versionStates?.set(scope, state) if (!keepVisibleBaseline && instructions !== undefined && instructions.rendered.text.length > 0) { - const baselineContent = workspaceContextMessage(instructions.rendered.text).content + const baselineContent = agentInstructionsMessage(instructions.rendered.text).content content.push(...baselineContent) const replacementScopes = new Set(baseline.changes.keys()) const replacementRemovals = replacePreviousBaseline @@ -225,7 +225,7 @@ export function apply(ctx: Context, config: Config): void { } const syncInbox = (agent: Agent, claimed: readonly UserMessage[], desired: UserMessage | undefined): void => { - const pending = agent.inbox.nextStep.filter(isWorkspaceContext) + const pending = agent.inbox.nextStep.filter(isAgentInstructionsMessage) const alreadySupplied = desired !== undefined && ( claimed.some(message => sameContextPayload(message, desired)) || agent.session.surface.nodes.some((seq) => { @@ -257,7 +257,7 @@ export function apply(ctx: Context, config: Config): void { claimed: readonly UserMessage[], touchedPaths: readonly string[] = [], ): Promise => { - const pending = agent.inbox.nextStep.filter(isWorkspaceContext) + const pending = agent.inbox.nextStep.filter(isAgentInstructionsMessage) const desired = await compose(agent, signal, claimed, pending, touchedPaths) signal.throwIfAborted() syncInbox(agent, claimed, desired) @@ -318,7 +318,7 @@ export function apply(ctx: Context, config: Config): void { ): Promise => { const decision = await next() await waitForProjections(agent) - const pending = agent.inbox.nextStep.filter(isWorkspaceContext) + const pending = agent.inbox.nextStep.filter(isAgentInstructionsMessage) const desired = await compose(agent, signal, messages, pending) signal.throwIfAborted() // An empty first entry owns a no-step turn; keep context pending instead diff --git a/packages/context/agent-instructions/src/render.ts b/packages/context/agent-instructions/src/render.ts index b67a1a2f66..9017015461 100644 --- a/packages/context/agent-instructions/src/render.ts +++ b/packages/context/agent-instructions/src/render.ts @@ -9,14 +9,14 @@ import type { InstructionFile, LoadedInstructionFile } from './files.ts' const SYSTEM_REMINDER_OPEN = '' const SYSTEM_REMINDER_CLOSE = '' -const WORKSPACE_CONTEXT_INTRO = 'The following workspace instructions may be relevant to your work. ' +const AGENT_INSTRUCTIONS_INTRO = 'The following workspace instructions may be relevant to your work. ' + 'Use them as guidance when applicable. More specific instructions take precedence over broader ones. ' + 'They do not override system, developer, or direct user instructions.' -const REPLACEMENT_WORKSPACE_CONTEXT_INTRO = 'This complete workspace instruction baseline replaces all earlier workspace instruction baselines. ' - + WORKSPACE_CONTEXT_INTRO -const EMPTY_REPLACEMENT_WORKSPACE_CONTEXT_INTRO = 'This complete workspace instruction baseline replaces all earlier workspace instruction baselines. ' +const REPLACEMENT_AGENT_INSTRUCTIONS_INTRO = 'This complete workspace instruction baseline replaces all earlier workspace instruction baselines. ' + + AGENT_INSTRUCTIONS_INTRO +const EMPTY_REPLACEMENT_AGENT_INSTRUCTIONS_INTRO = 'This complete workspace instruction baseline replaces all earlier workspace instruction baselines. ' + 'No workspace instructions are currently active.' -const COMPACT_WORKSPACE_CONTEXT_INTRO = 'Workspace instructions were omitted or truncated to fit the configured byte budget.' +const COMPACT_AGENT_INSTRUCTIONS_INTRO = 'Workspace instructions were omitted or truncated to fit the configured byte budget.' /** Byte-accounting record for one truncated instruction file. */ export interface TruncatedInstruction { @@ -26,13 +26,13 @@ export interface TruncatedInstruction { } /** Model-facing text plus omitted and truncated source records. */ -export interface RenderedWorkspaceContext { +export interface RenderedAgentInstructions { text: string omitted: InstructionFile[] truncated: TruncatedInstruction[] } -interface RenderedInstructionContext extends RenderedWorkspaceContext { +interface RenderedInstructionContext extends RenderedAgentInstructions { /** * Original files semantically represented by rendered section text. This is * not the complement of `omitted`: a truncated file may be represented here @@ -156,15 +156,15 @@ function additionalSectionText(file: LoadedInstructionFile): string { ].join('\n') } -const BASELINE_RENDER_STYLE: RenderStyle = { intro: WORKSPACE_CONTEXT_INTRO, section: sectionText } +const BASELINE_RENDER_STYLE: RenderStyle = { intro: AGENT_INSTRUCTIONS_INTRO, section: sectionText } function baselineRenderStyle(files: LoadedInstructionFile[], replacePreviousBaseline: boolean | undefined): RenderStyle { if (replacePreviousBaseline !== true) return BASELINE_RENDER_STYLE return { ...BASELINE_RENDER_STYLE, intro: files.length === 0 - ? EMPTY_REPLACEMENT_WORKSPACE_CONTEXT_INTRO - : REPLACEMENT_WORKSPACE_CONTEXT_INTRO, + ? EMPTY_REPLACEMENT_AGENT_INSTRUCTIONS_INTRO + : REPLACEMENT_AGENT_INSTRUCTIONS_INTRO, } } @@ -299,7 +299,7 @@ function renderInstructionContext( const omitted = files.slice(0, -1).map(file => ({ absolutePath: file.absolutePath, displayPath: file.displayPath })) const originalBytes = byteLength(mostSpecific.content) - for (const candidateStyle of [style, { ...style, intro: COMPACT_WORKSPACE_CONTEXT_INTRO }]) { + for (const candidateStyle of [style, { ...style, intro: COMPACT_AGENT_INSTRUCTIONS_INTRO }]) { const truncatedFile = truncateToFit(mostSpecific, [], maxBytes, omitted, candidateStyle) const includedBytes = byteLength(truncatedFile.content) const truncated = [{ @@ -338,10 +338,10 @@ function renderInstructionContext( * @returns bounded public rendering plus files with surviving content, including genuinely empty files. * @internal */ -export function renderWorkspaceInstructionSet( +export function renderAgentInstructionSet( files: LoadedInstructionFile[], options: { maxBytes: number; replacePreviousBaseline?: boolean }, -): { rendered: RenderedWorkspaceContext; included: LoadedInstructionFile[] } { +): { rendered: RenderedAgentInstructions; included: LoadedInstructionFile[] } { const style = baselineRenderStyle(files, options.replacePreviousBaseline) const { represented, ...rendered } = renderInstructionContext(files, options.maxBytes, style) return { rendered, included: represented } @@ -353,9 +353,9 @@ export function renderWorkspaceInstructionSet( * @param options - rendering byte budget and whether this baseline supersedes a visible predecessor. * @returns bounded baseline prompt text and budget diagnostics. */ -export function renderWorkspaceContext( +export function renderAgentInstructions( files: LoadedInstructionFile[], options: { maxBytes: number; replacePreviousBaseline?: boolean }, -): RenderedWorkspaceContext { - return renderWorkspaceInstructionSet(files, options).rendered +): RenderedAgentInstructions { + return renderAgentInstructionSet(files, options).rendered } diff --git a/packages/context/agent-instructions/src/state.ts b/packages/context/agent-instructions/src/state.ts index 8a66f7d02f..b7638f86eb 100644 --- a/packages/context/agent-instructions/src/state.ts +++ b/packages/context/agent-instructions/src/state.ts @@ -78,7 +78,7 @@ export interface ReconciledInstructionContext { versionUpdates: InstructionVersionUpdate[] } -function workspaceContextHook(text: string, changes: AgentInstructionChange[]): UserMessage { +function agentInstructionsHook(text: string, changes: AgentInstructionChange[]): UserMessage { return createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'agent-instructions', form: 'instructions', changes }, @@ -90,14 +90,14 @@ function workspaceContextHook(text: string, changes: AgentInstructionChange[]): * @param text - complete plugin-owned system-reminder text. * @returns a user-role prefix message. */ -export function workspaceContextMessage(text: string): Message { +export function agentInstructionsMessage(text: string): Message { return createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'plugin', plugin: name }, }) } -function isWorkspaceContextSource( +function isAgentInstructionsSource( source: unknown, ): source is { kind: 'agent-instructions'; changes: unknown[] } { return typeof source === 'object' && source !== null @@ -141,14 +141,14 @@ function visibleInstructionChanges( for (const seq of agent.session.surface.nodes) { // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. const event = agent.session.eventAt(seq) - if (event?.type !== 'user/message' || !isWorkspaceContextSource(event.data.source)) continue + if (event?.type !== 'user/message' || !isAgentInstructionsSource(event.data.source)) continue const changes = workspaceInstructionChanges(event.data.source) for (const change of changes) { visible.set(change.scope, change) } } for (const message of authorityMessages) { - if (!isWorkspaceContextSource(message.source)) continue + if (!isAgentInstructionsSource(message.source)) continue for (const change of workspaceInstructionChanges(message.source)) { visible.set(change.scope, change) } @@ -283,7 +283,7 @@ export async function reconcileInstructionContext( } for (const message of options.scopeMessages) { /* v8 ignore next -- the plugin passes its workspace-only pending projection. */ - if (!isWorkspaceContextSource(message.source)) continue + if (!isAgentInstructionsSource(message.source)) continue for (const change of workspaceInstructionChanges(message.source)) { if (!options.includeBaselineScopes && baselineScopes.has(change.scope)) continue scopes.add(change.scope) @@ -428,7 +428,7 @@ export async function reconcileInstructionContext( // next pass retry instead of spamming notice-only contexts. if (rendered.text.length === 0 || rendered.changes.length === 0) return undefined return { - context: workspaceContextHook(rendered.text, rendered.changes), + context: agentInstructionsHook(rendered.text, rendered.changes), versionUpdates: retainedInstructionVersionUpdates(versionUpdates, rendered.changes), } } diff --git a/packages/context/agent-instructions/tests/agent-instructions.e2e.ts b/packages/context/agent-instructions/tests/agent-instructions.e2e.ts index 7e35bdee21..4d91d49e29 100644 --- a/packages/context/agent-instructions/tests/agent-instructions.e2e.ts +++ b/packages/context/agent-instructions/tests/agent-instructions.e2e.ts @@ -9,7 +9,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' -import * as WorkspaceContext from '@deepseek-ai/dsh-agent-instructions' +import * as AgentInstructions from '@deepseek-ai/dsh-agent-instructions' import { candidateScopeKey } from '../src/render.ts' import LocalFileSystem from '@deepseek-ai/dsh-fs-local' import * as ToolFs from '@deepseek-ai/dsh-tool-fs' @@ -39,7 +39,7 @@ async function harness(): Promise<{ ctx: Context; agent: Agent }> { }) await ctx.plugin(LocalFileSystem, { cwd: '/' }) await ctx.plugin(ToolFs) - await ctx.plugin(WorkspaceContext, { maxBytes: 65536 }) + await ctx.plugin(AgentInstructions, { maxBytes: 65536 }) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LlmDeepSeek, { models: [{ id: 'deepseek-v4-flash' }] }) const handle = await ctx.agents.create({ diff --git a/packages/context/agent-instructions/tests/agent-instructions.spec.ts b/packages/context/agent-instructions/tests/agent-instructions.spec.ts index d477cf666e..877c4f4058 100644 --- a/packages/context/agent-instructions/tests/agent-instructions.spec.ts +++ b/packages/context/agent-instructions/tests/agent-instructions.spec.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os' import { afterAll, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' -import * as workspaceContext from '@deepseek-ai/dsh-agent-instructions' +import * as AgentInstructions from '@deepseek-ai/dsh-agent-instructions' import LlmRuntime, { createUserMessage, ToolCallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId, SessionSeq, type SessionEvent, type SurfaceIntent, type UserMessage } from '@deepseek-ai/dsh-session' import AgentRegistry, { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' @@ -32,7 +32,7 @@ import * as ToolFs from '@deepseek-ai/dsh-tool-fs' import { discoverBaselineInstructionFiles, loadBaselineInstructions, - renderWorkspaceContext, + renderAgentInstructions, } from '@deepseek-ai/dsh-agent-instructions' import { applyInstructionVersionUpdates, @@ -41,7 +41,7 @@ import { type InstructionVersionCache, } from '../src/state.ts' import { resolveConfig } from '../src/config.ts' -import { candidateScopeKey, renderInstructionChanges, renderWorkspaceInstructionSet, USER_GLOBAL_DIRECTORY, USER_GLOBAL_FILE } from '../src/render.ts' +import { candidateScopeKey, renderInstructionChanges, renderAgentInstructionSet, USER_GLOBAL_DIRECTORY, USER_GLOBAL_FILE } from '../src/render.ts' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import { mountAgentLoopTestDependencies, @@ -193,23 +193,23 @@ class BlockingReadFileSystem extends RecordingFileSystem { } } -async function mountWorkspaceContextPlugin(ctx: Context, config: workspaceContext.Config): Promise>> { +async function mountAgentInstructionsPlugin(ctx: Context, config: AgentInstructions.Config): Promise>> { if (ctx.get('sessionProjections') === undefined) await ctx.plugin(SessionProjectionRegistry) ctx.sessionProjections.register(turnBoundaryProjectionDefinition) - return ctx.plugin(workspaceContext, config) + return ctx.plugin(AgentInstructions, config) } -async function mountWorkspaceContext(ctx: Context, config: workspaceContext.Config): Promise>> { +async function mountAgentInstructions(ctx: Context, config: AgentInstructions.Config): Promise>> { await ctx.plugin(LocalFileSystem, { cwd: '/' }) - return mountWorkspaceContextPlugin(ctx, config) + return mountAgentInstructionsPlugin(ctx, config) } -async function mountFileToolsAndWorkspaceContext(ctx: Context, config: workspaceContext.Config): Promise>> { +async function mountFileToolsAndAgentInstructions(ctx: Context, config: AgentInstructions.Config): Promise>> { await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) await ctx.plugin(LocalFileSystem, { cwd: '/' }) await ctx.plugin(ToolFs) - return mountWorkspaceContextPlugin(ctx, config) + return mountAgentInstructionsPlugin(ctx, config) } async function stubAgent(cwd?: string, seed: readonly SessionEvent[] = []): Promise { @@ -255,7 +255,7 @@ function blocksText(blocks: { type: string; text?: string }[] | undefined): stri return blocks?.map(block => block.type === 'text' ? block.text ?? '' : '').join('\n') ?? '' } -async function workspaceContextOf(agent: Agent): Promise { +async function agentInstructionsOf(agent: Agent): Promise { return vi.waitFor(() => { const context = agent.inbox.nextStep.find(message => message.source.kind === 'agent-instructions') @@ -264,16 +264,16 @@ async function workspaceContextOf(agent: Agent): Promise { }, { timeout: 10_000 }) } -async function syncWorkspaceContext(ctx: Context, agent: Agent): Promise { +async function syncAgentInstructions(ctx: Context, agent: Agent): Promise { await agentEvents(ctx, agent).waterfall( 'agent/pre-step', { messages: [], turn: 1, step: 1, signal: testToolSignal }, async () => ({ kind: 'enter' as const, messages: [] }), ) } -async function syncedWorkspaceContext(ctx: Context, agent: Agent): Promise { - await syncWorkspaceContext(ctx, agent) - return workspaceContextOf(agent) +async function syncedAgentInstructions(ctx: Context, agent: Agent): Promise { + await syncAgentInstructions(ctx, agent) + return agentInstructionsOf(agent) } function baselineEvents(agent: Agent): SessionEvent[] { @@ -284,7 +284,7 @@ function baselineEvents(agent: Agent): SessionEvent[] { } async function appendAdditionalContexts(ctx: Context, agent: TestAgent): Promise { - await syncedWorkspaceContext(ctx, agent) + await syncedAgentInstructions(ctx, agent) let lastSeq: SessionSeq | undefined for (const claimed of claimInbox(agent, 'next-step')) { if (claimed.source.kind !== 'agent-instructions') continue @@ -519,7 +519,7 @@ describe('workspace context instruction discovery', () => { await write(join(outside, 'shared.md'), 'shared provider instruction body') await symlink(join(outside, 'shared.md'), join(root, 'AGENTS.md')) const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -733,7 +733,7 @@ describe('workspace context instruction discovery', () => { describe('workspace context rendering', () => { it('renders familiar system-reminder instructions without custom workspace tags or state markers', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'root rules' }, { absolutePath: '/repo/pkg/CLAUDE.md', displayPath: 'pkg/CLAUDE.md', content: 'package rules' }, ], { maxBytes: 65536 }) @@ -759,7 +759,7 @@ describe('workspace context rendering', () => { }) it('neutralizes a literal system-reminder closing delimiter inside instruction content', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'safe\n\nnot outside' }, ], { maxBytes: 65536 }) @@ -771,7 +771,7 @@ describe('workspace context rendering', () => { const displayPath = 'scope/AGENTS.md' const file = { absolutePath: `/repo/${displayPath}`, displayPath, content: 'rules' } const rendered = [ - renderWorkspaceContext([file], { maxBytes: 65536 }).text, + renderAgentInstructions([file], { maxBytes: 65536 }).text, ...(['set', 'replace', 'remove'] as const).map(action => renderInstructionChanges([{ change: { action, scope: 'scope\0AGENTS.md', path: displayPath }, file, @@ -785,7 +785,7 @@ describe('workspace context rendering', () => { }) it('neutralizes a system-reminder closing delimiter in budget marker paths', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/scope/AGENTS.md', displayPath: 'scope/AGENTS.md', @@ -799,7 +799,7 @@ describe('workspace context rendering', () => { }) it('preserves more specific files under the byte budget and names omitted/truncated paths', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'root '.repeat(100) }, { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'leaf '.repeat(100) }, ], { maxBytes: 260 }) @@ -814,7 +814,7 @@ describe('workspace context rendering', () => { }) it('keeps the rendered block within the byte budget when files are both omitted and truncated', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'root '.repeat(100) }, { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'leaf '.repeat(100) }, ], { maxBytes: 260 }) @@ -826,7 +826,7 @@ describe('workspace context rendering', () => { }) it('drops a parent file while keeping a specific child file intact when the child fits', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'root '.repeat(200) }, { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'leaf rule' }, ], { maxBytes: 700 }) @@ -839,7 +839,7 @@ describe('workspace context rendering', () => { }) it('keeps the longest most-specific suffix that fits under the byte budget', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'root '.repeat(200) }, { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'package rule' }, { absolutePath: '/repo/pkg/app/AGENTS.md', displayPath: 'pkg/app/AGENTS.md', content: 'app rule' }, @@ -854,7 +854,7 @@ describe('workspace context rendering', () => { }) it('truncates a single oversized file to the largest content slice that fits', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'x'.repeat(1000) }, ], { maxBytes: 700 }) @@ -867,7 +867,7 @@ describe('workspace context rendering', () => { }) it('omits all text when the render budget is disabled', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/AGENTS.md', displayPath: 'AGENTS.md', content: 'root rules' }, ], { maxBytes: 0 }) @@ -879,7 +879,7 @@ describe('workspace context rendering', () => { }) it('falls back to a compact truncation notice when even the empty heading cannot fit', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'x'.repeat(1000) }, ], { maxBytes: 100 }) @@ -889,7 +889,7 @@ describe('workspace context rendering', () => { }) it('keeps the empty instruction heading when it fits beside the compact notice', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'x'.repeat(1000) }, ], { maxBytes: 120 }) @@ -905,7 +905,7 @@ describe('workspace context rendering', () => { it('represents a genuinely empty instruction when its compact heading fits', () => { const file = { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: '' } - const rendered = renderWorkspaceInstructionSet([file], { maxBytes: 117 }) + const rendered = renderAgentInstructionSet([file], { maxBytes: 117 }) expect(rendered.rendered.text).toContain('truncated pkg/AGENTS.md from 0 to 0 bytes') expect(rendered.rendered.text).toContain('Instructions from: pkg/AGENTS.md') @@ -919,7 +919,7 @@ describe('workspace context rendering', () => { content: '', } - const rendered = renderWorkspaceInstructionSet([file], { maxBytes: 300 }) + const rendered = renderAgentInstructionSet([file], { maxBytes: 300 }) expect(rendered.rendered.text).toContain('') expect(rendered.rendered.text).toContain('Workspace instructions were omitted or truncated') @@ -931,7 +931,7 @@ describe('workspace context rendering', () => { }) it('truncates the compact notice itself when the render budget is smaller than the notice', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/pkg/AGENTS.md', displayPath: 'pkg/AGENTS.md', content: 'x'.repeat(1000) }, ], { maxBytes: 20 }) @@ -1011,7 +1011,7 @@ describe('workspace context rendering', () => { }) it('keeps compact truncation notices within budget when a multibyte display path is cut', () => { - const rendered = renderWorkspaceContext([ + const rendered = renderAgentInstructions([ { absolutePath: '/repo/路径/AGENTS.md', displayPath: '路径/AGENTS.md', content: 'x'.repeat(1000) }, ], { maxBytes: 51 }) @@ -1023,26 +1023,26 @@ describe('workspace context request injection', () => { it('requires an explicit maxBytes configuration', async () => { const ctx = new Context() - await expect(mountWorkspaceContextPlugin(ctx, {} as workspaceContext.Config)).rejects.toThrow(/maxBytes/) + await expect(mountAgentInstructionsPlugin(ctx, {} as AgentInstructions.Config)).rejects.toThrow(/maxBytes/) }) it('mounts without requiring a filesystem provider', async () => { const ctx = new Context() try { - await mountWorkspaceContextPlugin(ctx, { maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { maxBytes: 65536 }) } finally { await ctx.fiber.dispose() } }) it('requires projections without making the optional filesystem a static dependency', () => { - expect(workspaceContext.inject).toEqual(['sessionProjections']) + expect(AgentInstructions.inject).toEqual(['sessionProjections']) }) it('rejects a file-touch projection when the turn boundary unit is absent', async () => { const ctx = new Context() await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(workspaceContext, { maxBytes: 65536 }) + await ctx.plugin(AgentInstructions, { maxBytes: 65536 }) const exec = stubToolExecution({ callId: ToolCallId('missing-turn-boundary'), name: 'read', @@ -1063,7 +1063,7 @@ describe('workspace context request injection', () => { it('does not inject baseline context when no filesystem provider is present', async () => { const ctx = new Context() try { - await mountWorkspaceContextPlugin(ctx, { maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { maxBytes: 65536 }) const agent = await stubAgent('/virtual/repo') await composeBaselinePrefix(ctx, agent) @@ -1081,7 +1081,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -1120,7 +1120,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = await composeBaselinePrefix(ctx, agent) @@ -1142,7 +1142,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await composeBaselinePrefix(ctx, original) @@ -1170,7 +1170,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'repo rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await composeBaselinePrefix(ctx, original) @@ -1197,7 +1197,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'root '.repeat(200)) await write(join(cwd, 'AGENTS.md'), 'package rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 700 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 700 }) const original = await stubAgent(cwd) await composeBaselinePrefix(ctx, original) @@ -1226,7 +1226,7 @@ describe('workspace context request injection', () => { await mkdir(cwd, { recursive: true }) await write(join(root, 'AGENTS.md'), 'root '.repeat(200)) const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 700 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 700 }) const original = await stubAgent(cwd) await composeBaselinePrefix(ctx, original) @@ -1259,11 +1259,11 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'agents rule') await write(join(root, 'CLAUDE.md'), 'claude rule') - await mountWorkspaceContext(originalCtx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(originalCtx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await composeBaselinePrefix(originalCtx, original) - await mountWorkspaceContext(resumedCtx, { + await mountAgentInstructions(resumedCtx, { dshHome: home, maxBytes: 65536, instructionFileCandidates: ['CLAUDE.md', 'AGENTS.md'], @@ -1308,7 +1308,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'agents rule') await write(join(root, 'CLAUDE.md'), 'claude rule') - await mountWorkspaceContext(agentsCtx, { + await mountAgentInstructions(agentsCtx, { dshHome: home, maxBytes: 65536, instructionFileCandidates: ['AGENTS.md'], @@ -1316,7 +1316,7 @@ describe('workspace context request injection', () => { const original = await stubAgent(root) await composeBaselinePrefix(agentsCtx, original) - await mountWorkspaceContext(claudeCtx, { + await mountAgentInstructions(claudeCtx, { dshHome: home, maxBytes: 65536, instructionFileCandidates: ['CLAUDE.md'], @@ -1331,7 +1331,7 @@ describe('workspace context request injection', () => { { action: 'set', scope: sk('.', 'CLAUDE.md'), path: 'CLAUDE.md' }, ]) - await mountWorkspaceContext(restoredCtx, { + await mountAgentInstructions(restoredCtx, { dshHome: home, maxBytes: 65536, instructionFileCandidates: ['AGENTS.md'], @@ -1362,11 +1362,11 @@ describe('workspace context request injection', () => { try { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'agents rule') - await mountWorkspaceContext(originalCtx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(originalCtx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await composeBaselinePrefix(originalCtx, original) - await mountWorkspaceContext(resumedCtx, { + await mountAgentInstructions(resumedCtx, { dshHome: home, maxBytes: 65536, instructionFileCandidates: ['POLICY.md'], @@ -1403,7 +1403,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - const fiber = await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + const fiber = await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await agentEvents(ctx, original).waterfall( 'agent/pre-step', @@ -1414,7 +1414,7 @@ describe('workspace context request injection', () => { expect(inserted?.source).toMatchObject({ kind: 'agent-instructions', baseline: true }) await fiber.dispose() - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const resumed = await stubAgent(root, original.session.snapshotEvents()) agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' }) const claimed = claimInbox(resumed, 'next-step') @@ -1448,7 +1448,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'old repo rule') const ctx = new Context() - const fiber = await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + const fiber = await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await agentEvents(ctx, original).waterfall( 'agent/pre-step', @@ -1460,7 +1460,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'new repo rule') await fiber.dispose() - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const resumed = await stubAgent(root, original.session.snapshotEvents()) agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' }) const staleClaim = claimInbox(resumed, 'next-step') @@ -1501,7 +1501,7 @@ describe('workspace context request injection', () => { try { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') - await mountWorkspaceContext(originalCtx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(originalCtx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await agentEvents(originalCtx, original).waterfall( 'agent/pre-step', @@ -1513,7 +1513,7 @@ describe('workspace context request injection', () => { await originalCtx.fiber.dispose() if (provideFs) await resumedCtx.plugin(LocalFileSystem, { cwd: '/' }) - await mountWorkspaceContextPlugin(resumedCtx, { dshHome: home, maxBytes }) + await mountAgentInstructionsPlugin(resumedCtx, { dshHome: home, maxBytes }) const resumed = await stubAgent(root, original.session.snapshotEvents()) agentEvents(resumedCtx, resumed).emit('agent/session-start', { source: 'resume' }) const claimed = claimInbox(resumed, 'next-step') @@ -1540,7 +1540,7 @@ describe('workspace context request injection', () => { try { await mkdir(join(root, '.git'), { recursive: true }) const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) agent.session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'stale nested instructions' }], @@ -1574,7 +1574,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) agent.session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'stale nested instructions' }], @@ -1613,7 +1613,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const prompt = createUserMessage({ content: [{ type: 'text', text: 'current prompt' }], @@ -1648,14 +1648,14 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'repo rule') await write(join(home, 'AGENTS.md'), 'global rule') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) await write(join(home, 'AGENTS.md'), 'updated global rule') - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) - const pending = await workspaceContextOf(agent) + const pending = await agentInstructionsOf(agent) expect(pending?.source).toMatchObject({ kind: 'agent-instructions', changes: [{ action: 'replace', scope: sk(USER_GLOBAL_DIRECTORY, USER_GLOBAL_FILE) }], @@ -1674,7 +1674,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const downstream = { kind: 'reject' as const } @@ -1701,14 +1701,14 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'repo rule') await write(join(root, 'file.txt'), 'hello') const ctx = new Context() - const fiber = await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + const fiber = await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) // Hot remount over the live session: the durable baseline remains // visible, so the fresh mount does not append a duplicate. await fiber.dispose() - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) await composeBaselinePrefix(ctx, agent) expect(baselineEvents(agent)).toHaveLength(1) @@ -1721,7 +1721,7 @@ describe('workspace context request injection', () => { arguments: { file_path: 'file.txt' }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'replace', scope: sk('.', 'AGENTS.md'), path: 'AGENTS.md' }], }) } finally { @@ -1738,7 +1738,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() await ctx.plugin(LocalFileSystem, { cwd: '/' }) - const fiber = await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + const fiber = await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) const baseline = baselineEvents(agent)[0] @@ -1753,7 +1753,7 @@ describe('workspace context request injection', () => { }) await fiber.dispose() - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) await composeBaselinePrefix(ctx, agent) expect(baselineEvents(agent)).toHaveLength(2) @@ -1771,7 +1771,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'first post-compaction request rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) const baseline = baselineEvents(agent)[0] @@ -1814,7 +1814,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'old root rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await composeBaselinePrefix(ctx, original) @@ -1854,7 +1854,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'root '.repeat(200)) await write(join(cwd, 'AGENTS.md'), 'package rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 700 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 700 }) const agent = await stubAgent(cwd) await composeBaselinePrefix(ctx, agent) @@ -1874,7 +1874,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) ctx.on('agent/pre-step', async (_payload, next) => { const decision = await next() if (decision.kind === 'reject') return decision @@ -1906,7 +1906,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'old root rule') await write(join(root, 'file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -1916,11 +1916,11 @@ describe('workspace context request injection', () => { callId: ToolCallId('read-after-baseline-change'), name: 'read', arguments: { file_path: 'file.txt' }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'replace', scope: sk('.', 'AGENTS.md'), path: 'AGENTS.md' }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('Updated instructions from: AGENTS.md') - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('new root rule with more detail') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('Updated instructions from: AGENTS.md') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('new root rule with more detail') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -1935,7 +1935,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.md'), 'root rule') await write(join(root, 'file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -1945,10 +1945,10 @@ describe('workspace context request injection', () => { callId: ToolCallId('read-after-baseline-remove'), name: 'read', arguments: { file_path: 'file.txt' }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'remove', scope: sk('.', 'AGENTS.md'), path: 'AGENTS.md' }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('Instructions removed: AGENTS.md') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('Instructions removed: AGENTS.md') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -1961,7 +1961,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'shared root and global rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: root, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: root, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -1981,7 +1981,7 @@ describe('workspace context request injection', () => { await write(join(root, 'CLAUDE.md'), ' shared repo rule\n\n') await write(join(root, 'file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2005,7 +2005,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'x'.repeat(1000)) const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2040,7 +2040,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'ctx.fs rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2063,7 +2063,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'provider-only rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2088,7 +2088,7 @@ describe('workspace context request injection', () => { fs.missingOnResolve.add(join(cwd, '.git')) fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'provider parent rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(cwd) await composeBaselinePrefix(ctx, agent) @@ -2129,7 +2129,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'far too large' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536, maxSourceBytes: 4 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536, maxSourceBytes: 4 }) const prefix = await composeBaselinePrefix(ctx, await stubAgent(root)) @@ -2154,7 +2154,7 @@ describe('workspace context request injection', () => { fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(instructionPath, { type: 'file', content: 'far too large' }) fs.omitSizes.add(instructionPath) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536, maxSourceBytes: 4 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536, maxSourceBytes: 4 }) const prefix = await composeBaselinePrefix(ctx, await stubAgent(root)) @@ -2177,7 +2177,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as BlockingReadFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'blocked' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const controller = new AbortController() const reason = new Error('cancel prefix') const pending = agentEvents(ctx, await stubAgent(root)).waterfall( @@ -2211,7 +2211,7 @@ describe('workspace context request injection', () => { fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(home, 'AGENTS.md'), { type: 'file', content: 'ctx global rule' }) fs.entries.set(join(root, 'CLAUDE.md'), { type: 'file', content: 'ctx claude rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2237,7 +2237,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'directory' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2260,7 +2260,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2283,7 +2283,7 @@ describe('workspace context request injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.throwOnStat.add(join(root, 'AGENTS.md')) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2305,7 +2305,7 @@ describe('workspace context request injection', () => { fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.throwOnStat.add(join(root, 'AGENTS.md')) fs.entries.set(join(root, 'CLAUDE.md'), { type: 'file', content: 'claude sibling rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2330,7 +2330,7 @@ describe('workspace context request injection', () => { fs.throwOnStat.add(join(cwd, '.git')) fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'AGENTS.md'), { type: 'file', content: 'ancestor rule must not load' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(cwd) await expect(composeBaselinePrefix(ctx, agent)) @@ -2353,7 +2353,7 @@ describe('workspace context request injection', () => { await write(join(repoA, 'AGENTS.md'), 'repo A only') await write(join(repoB, 'AGENTS.md'), 'repo B only') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agentA = await stubAgent(repoA) const agentB = await stubAgent(repoB) @@ -2381,7 +2381,7 @@ describe('workspace context request injection', () => { await write(join(cwd, 'AGENTS.md'), 'child schema default rule') const ctx = new Context() await ctx.plugin(LocalFileSystem, { cwd: '/' }) - await mountWorkspaceContextPlugin(ctx, { maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { maxBytes: 65536 }) const agent = await stubAgent(cwd) await composeBaselinePrefix(ctx, agent) @@ -2402,7 +2402,7 @@ describe('workspace context request injection', () => { await write(join(root, 'AGENTS.local.md'), 'local rule') const ctx = new Context() await ctx.plugin(LocalFileSystem, { cwd: '/' }) - await mountWorkspaceContextPlugin(ctx, { maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2422,7 +2422,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - const fiber = await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + const fiber = await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) await fiber.dispose() const agent = await stubAgent(root) @@ -2442,7 +2442,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 0 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 0 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2461,7 +2461,7 @@ describe('workspace context request injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'repo rule') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: -1 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: -1 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2479,7 +2479,7 @@ describe('workspace context request injection', () => { try { await mkdir(join(root, '.git'), { recursive: true }) const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) @@ -2635,7 +2635,7 @@ describe('dynamic nested workspace context injection', () => { await ctx.plugin(AgentRegistry) await ctx.plugin(LocalFileSystem, { cwd: '/' }) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) await ctx.plugin(AgentLoop, { agents: [] }) ctx.llm.registerAdapter(['mock'], adapter) const agent = await ctx.agentLoop.create(SessionId('workspace-context-abort'), { provider: 'mock', model: 'mock' }, { cwd: root }) @@ -2711,7 +2711,7 @@ describe('dynamic nested workspace context injection', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'file', content: 'nested' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const controller = new AbortController() const reason = new Error('cancel dynamic reconciliation') controller.abort(reason) @@ -2748,7 +2748,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const result = await ctx.tools.execute({ @@ -2760,14 +2760,14 @@ describe('dynamic nested workspace context injection', () => { }) expect(result.isError).toBe(false) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions' }) - const queuedSource = ((await syncedWorkspaceContext(ctx, agent))).source + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions' }) + const queuedSource = ((await syncedAgentInstructions(ctx, agent))).source expect(queuedSource).toMatchObject({ kind: 'agent-instructions', form: 'instructions' }) expect(queuedSource.kind === 'agent-instructions' && queuedSource.changes.some(change => change.action === 'set' && change.scope === sk('pkg', 'AGENTS.md') && change.path === join('pkg', 'AGENTS.md'))).toBe(true) - const source = ((await syncedWorkspaceContext(ctx, agent))).source + const source = ((await syncedAgentInstructions(ctx, agent))).source const firstChange = source?.kind === 'agent-instructions' ? source.changes.find(change => change.scope === sk('pkg', 'AGENTS.md')) : undefined @@ -2775,7 +2775,7 @@ describe('dynamic nested workspace context injection', () => { ? firstChange.digest : undefined expect(changeDigest).toMatch(/^[a-f0-9]{40}$/) - const text = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const text = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(text).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) expect(text).toContain('nested package rule') expect(text).not.toContain(' { try { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const controller = new AbortController() @@ -2806,7 +2806,7 @@ describe('dynamic nested workspace context injection', () => { }), { content: [{ type: 'text', text: 'ok' }], isError: false, value: null }) controller.abort(new Error('tool execution ended')) - expect(blocksText((await workspaceContextOf(agent)).content)).toContain('nested package rule') + expect(blocksText((await agentInstructionsOf(agent)).content)).toContain('nested package rule') } finally { await ctx.fiber.dispose() await rm(root, { recursive: true, force: true }) @@ -2823,7 +2823,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/CLAUDE.local.md'), 'local package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536, instructionFileCandidates: ['CLAUDE.local.md', 'AGENTS.md', 'CLAUDE.md'], @@ -2838,7 +2838,7 @@ describe('dynamic nested workspace context injection', () => { agent, }) - const text = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const text = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(text).toContain(`Additional instructions from: ${join('pkg', 'CLAUDE.local.md')}`) expect(text).toContain('local package rule') expect(text).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) @@ -2860,7 +2860,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.local.md'), 'nested local rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -2871,7 +2871,7 @@ describe('dynamic nested workspace context injection', () => { agent, }) - const source = ((await syncedWorkspaceContext(ctx, agent))).source + const source = ((await syncedAgentInstructions(ctx, agent))).source const changes = source?.kind === 'agent-instructions' ? source.changes : [] @@ -2879,7 +2879,7 @@ describe('dynamic nested workspace context injection', () => { expect.objectContaining({ action: 'set', path: join('pkg', 'AGENTS.md') }), expect.objectContaining({ action: 'set', path: join('pkg', 'AGENTS.local.md') }), ])) - const text = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const text = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(text).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) expect(text).toContain('nested base rule') expect(text).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.local.md')}`) @@ -2899,7 +2899,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.local.md'), 'nested local rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536, localInstructionFileCandidates: [], @@ -2914,7 +2914,7 @@ describe('dynamic nested workspace context injection', () => { agent, }) - const text = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const text = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(text).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) expect(text).not.toContain(join('pkg', 'AGENTS.local.md')) } finally { @@ -2931,7 +2931,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = await ctx.tools.execute({ @@ -2974,7 +2974,7 @@ describe('dynamic nested workspace context injection', () => { fs.omitSizes.add(instructionPath) fs.entries.set(join(root, 'pkg/file.txt'), { type: 'file', content: 'hello' }) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = await ctx.tools.execute({ @@ -3011,7 +3011,7 @@ describe('dynamic nested workspace context injection', () => { fs.entries.set(instructionPath, { type: 'file', content: 'same package rule', version: FsVersion('revision-1') }) fs.entries.set(join(root, 'pkg/file.txt'), { type: 'file', content: 'hello' }) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -3024,7 +3024,7 @@ describe('dynamic nested workspace context injection', () => { signal: testToolSignal, callId: ToolCallId('read-after-same-digest-version-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) const afterRefresh = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('read-after-version-cache-refresh'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, @@ -3056,7 +3056,7 @@ describe('dynamic nested workspace context injection', () => { fs.entries.set(instructionPath, { type: 'file', content: 'shared path, separate sessions' }) fs.entries.set(join(root, 'pkg/file.txt'), { type: 'file', content: 'hello' }) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const firstAgent = await stubAgent(root) const secondAgent = await stubAgent(root) @@ -3071,8 +3071,8 @@ describe('dynamic nested workspace context injection', () => { expect(first.additionalContexts).toBeUndefined() expect(second.additionalContexts).toBeUndefined() - expect(((await syncedWorkspaceContext(ctx, firstAgent))).source.kind).toBe('agent-instructions') - expect(((await syncedWorkspaceContext(ctx, secondAgent))).source.kind).toBe('agent-instructions') + expect(((await syncedAgentInstructions(ctx, firstAgent))).source.kind).toBe('agent-instructions') + expect(((await syncedAgentInstructions(ctx, secondAgent))).source.kind).toBe('agent-instructions') expect(fs.readTargets.filter(path => path === instructionPath)).toHaveLength(2) } finally { await ctx.fiber.dispose() @@ -3089,7 +3089,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'old package rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -3103,12 +3103,12 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-after-change'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions', form: 'instructions', changes: [{ action: 'replace', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toBe([ + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toBe([ '', `Updated instructions from: ${join('pkg', 'AGENTS.md')}`, '', @@ -3132,14 +3132,14 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/CLAUDE.md'), 'sibling package rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('read-both-siblings'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const firstText = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(firstText).toContain('native package rule') expect(firstText).toContain('sibling package rule') await appendAdditionalContexts(ctx, agent) @@ -3150,11 +3150,11 @@ describe('dynamic nested workspace context injection', () => { }) // Removing one candidate only removes its own scope; the sibling scope is untouched. - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'remove', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain(`Instructions removed: ${join('pkg', 'AGENTS.md')}`) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).not.toContain('sibling package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain(`Instructions removed: ${join('pkg', 'AGENTS.md')}`) + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).not.toContain('sibling package rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3170,7 +3170,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/CLAUDE.md'), 'nested rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -3178,10 +3178,10 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-nested-dup-siblings'), name: 'read', arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'set', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - const text = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const text = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(text.match(/nested rule/g)).toHaveLength(1) expect(text).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) expect(text).not.toContain(join('pkg', 'CLAUDE.md')) @@ -3392,14 +3392,14 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/CLAUDE.md'), 'initial divergent nested rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('read-before-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const firstText = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(firstText).toContain('canonical nested rule') expect(firstText).toContain('initial divergent nested rule') await appendAdditionalContexts(ctx, agent) @@ -3409,7 +3409,7 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-after-dup-convergence'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - const convergence = await syncedWorkspaceContext(ctx, agent) + const convergence = await syncedAgentInstructions(ctx, agent) expect(convergence.source).toMatchObject({ changes: [{ action: 'remove', scope: sk('pkg', 'CLAUDE.md'), path: join('pkg', 'CLAUDE.md') }], }) @@ -3429,7 +3429,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/CLAUDE.md'), 'secondary nested rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -3444,13 +3444,13 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-after-earlier-converges'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [ { action: 'replace', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }, { action: 'remove', scope: sk('pkg', 'CLAUDE.md'), path: join('pkg', 'CLAUDE.md') }, ], }) - const text = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const text = blocksText(((await syncedAgentInstructions(ctx, agent))).content) expect(text).toContain(`Instructions removed: ${join('pkg', 'CLAUDE.md')}`) expect(text).toContain(`Updated instructions from: ${join('pkg', 'AGENTS.md')}`) } finally { @@ -3467,7 +3467,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'package rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -3481,12 +3481,12 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-after-remove'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions', form: 'instructions', changes: [{ action: 'remove', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toBe([ + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toBe([ '', `Instructions removed: ${join('pkg', 'AGENTS.md')}`, '', @@ -3507,14 +3507,14 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'package rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('read-before-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const firstText = blocksText(((await syncedAgentInstructions(ctx, agent))).content) await appendAdditionalContexts(ctx, agent) expect(firstText).toContain('package rule') @@ -3529,10 +3529,10 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-after-symlink-dir'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'remove', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain(`Instructions removed: ${join('pkg', 'AGENTS.md')}`) + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain(`Instructions removed: ${join('pkg', 'AGENTS.md')}`) } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3547,7 +3547,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'first package rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ @@ -3568,11 +3568,11 @@ describe('dynamic nested workspace context injection', () => { callId: ToolCallId('read-after-tombstone'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ changes: [{ action: 'set', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('restored package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('restored package rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3592,7 +3592,7 @@ describe('dynamic nested workspace context injection', () => { fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'file', content: 'provider package rule' }) fs.entries.set(join(root, 'pkg/file.txt'), { type: 'file', content: 'hello' }) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = await ctx.tools.execute({ @@ -3623,7 +3623,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = await ctx.tools.execute({ signal: testToolSignal, @@ -3659,7 +3659,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'old nested rule') await write(join(root, 'pkg/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, @@ -3690,7 +3690,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = await ctx.tools.execute({ signal: testToolSignal, @@ -3727,7 +3727,7 @@ describe('dynamic nested workspace context injection', () => { expect(first.additionalContexts).toBeUndefined() expect(visibleBeforeCompact.additionalContexts).toBeUndefined() expect(afterCompact.additionalContexts).toBeUndefined() - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('nested package rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3742,7 +3742,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'AGENTS.md'), 'root rule') await write(join(root, 'file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await composeBaselinePrefix(ctx, agent) const baseline = baselineEvents(agent)[0] @@ -3770,7 +3770,7 @@ describe('dynamic nested workspace context injection', () => { arguments: { file_path: 'file.txt' }, agent, }) - const rearmedContext = (await syncedWorkspaceContext(ctx, agent)) + const rearmedContext = (await syncedAgentInstructions(ctx, agent)) await appendAdditionalContexts(ctx, agent) const afterRearm = await ctx.tools.execute({ signal: testToolSignal, @@ -3802,7 +3802,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/sub/AGENTS.md'), 'subtree rule') await write(join(root, 'pkg/sub/file.txt'), 'subtree file') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, @@ -3811,7 +3811,7 @@ describe('dynamic nested workspace context injection', () => { arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const firstText = blocksText(((await syncedAgentInstructions(ctx, agent))).content) await appendAdditionalContexts(ctx, agent) await ctx.tools.execute({ @@ -3823,7 +3823,7 @@ describe('dynamic nested workspace context injection', () => { }) expect(firstText).toContain('package note') - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('subtree rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('subtree rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3840,7 +3840,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/sub/AGENTS.md'), 'subtree rule') await write(join(root, 'pkg/sub/file.txt'), 'subtree file') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 700 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 700 }) const agent = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, @@ -3849,7 +3849,7 @@ describe('dynamic nested workspace context injection', () => { arguments: { file_path: join('pkg', 'sub', 'file.txt') }, agent, }) - const firstText = blocksText(((await syncedWorkspaceContext(ctx, agent))).content) + const firstText = blocksText(((await syncedAgentInstructions(ctx, agent))).content) await appendAdditionalContexts(ctx, agent) await ctx.tools.execute({ @@ -3862,7 +3862,7 @@ describe('dynamic nested workspace context injection', () => { expect(firstText).toContain(join('pkg', 'AGENTS.md')) expect(firstText).toContain('subtree rule') - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('parent rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('parent rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3877,7 +3877,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) agent.session.append('user/message', createUserMessage({ content: [ @@ -3912,7 +3912,7 @@ describe('dynamic nested workspace context injection', () => { agent, }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('nested package rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3928,7 +3928,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const rootResult = await ctx.tools.execute({ @@ -3947,7 +3947,7 @@ describe('dynamic nested workspace context injection', () => { }) expect(rootResult.additionalContexts).toBeUndefined() - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('nested package rule') } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -3971,7 +3971,7 @@ describe('dynamic nested workspace context injection', () => { fs.entries.set(join(root, 'pkg/deep/file.txt'), { type: 'file', content: 'hello' }) fs.throwOnRead.add(nested) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const result = await ctx.tools.execute({ @@ -3981,7 +3981,7 @@ describe('dynamic nested workspace context injection', () => { arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(result.isError).toBe(false) expect(result.additionalContexts).toBeUndefined() @@ -4003,7 +4003,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) ctx.on('tools/post-execute', async () => ({ kind: 'accept' as const, @@ -4037,14 +4037,14 @@ describe('dynamic nested workspace context injection', () => { }) expect(blocksText(result.content)).toContain('downstream replacement') expect(result.additionalContexts).toHaveLength(1) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions' }) - expect(((await syncedWorkspaceContext(ctx, agent))).source).toMatchObject({ + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions' }) + expect(((await syncedAgentInstructions(ctx, agent))).source).toMatchObject({ kind: 'agent-instructions', form: 'instructions', changes: [{ action: 'set', scope: sk('pkg', 'AGENTS.md'), path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule') - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).not.toContain('downstream context') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('nested package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).not.toContain('downstream context') expect(result.additionalContexts?.[0]).toEqual({ id: expect.any(String) as unknown, role: 'user', @@ -4065,7 +4065,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) ctx.on('tools/post-execute', async () => ({ kind: 'block' as const, @@ -4085,7 +4085,7 @@ describe('dynamic nested workspace context injection', () => { expect(result.isError).toBe(true) expect(blocksText(result.content)).toBe('blocked downstream') expect(result.additionalContexts).toBeUndefined() - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(agent.inbox.nextStep).toEqual([]) } finally { await rm(root, { recursive: true, force: true }) @@ -4112,7 +4112,7 @@ describe('dynamic nested workspace context injection', () => { ? { kind: 'block' as const, feedback: [{ type: 'text' as const, text: 'outer policy block' }] } : downstream }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const blocked = await ctx.tools.execute({ @@ -4122,7 +4122,7 @@ describe('dynamic nested workspace context injection', () => { arguments: { file_path: join('pkg', 'deep', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(agent.inbox.nextStep).toEqual([]) shouldBlock = false @@ -4137,7 +4137,7 @@ describe('dynamic nested workspace context injection', () => { expect(blocked.isError).toBe(true) expect(blocked.additionalContexts).toBeUndefined() expect(accepted.isError).toBe(false) - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('nested package rule') } finally { await ctx.fiber.dispose() await rm(root, { recursive: true, force: true }) @@ -4181,7 +4181,7 @@ describe('dynamic nested workspace context injection', () => { ? { kind: 'block' as const, feedback: [{ type: 'text' as const, text: 'outer composite block' }] } : downstream }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const blocked = await ctx.tools.execute({ @@ -4191,7 +4191,7 @@ describe('dynamic nested workspace context injection', () => { expect(blocked.isError).toBe(true) expect(blocked.additionalContexts).toBeUndefined() - expect(blocksText(((await syncedWorkspaceContext(ctx, agent))).content)).toContain('nested package rule') + expect(blocksText(((await syncedAgentInstructions(ctx, agent))).content)).toContain('nested package rule') } finally { await ctx.fiber.dispose() await rm(root, { recursive: true, force: true }) @@ -4205,7 +4205,7 @@ describe('dynamic nested workspace context injection', () => { const ctx = new Context() try { await ctx.plugin(RecordingFileSystem) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'file', content: 'nested package rule' }) @@ -4252,12 +4252,12 @@ describe('dynamic nested workspace context injection', () => { agent, }), { content: [], isError: false, value: null }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(agent.inbox.nextStep).toEqual([]) const stepEnd = agent.session.append('step/end', { turn: 1, step: 1 }) ctx.emit('session/event', agent.session, stepEnd) - expect(blocksText((await syncedWorkspaceContext(ctx, agent)).content)) + expect(blocksText((await syncedAgentInstructions(ctx, agent)).content)) .toContain('nested package rule') } finally { await ctx.fiber.dispose() @@ -4280,7 +4280,7 @@ describe('dynamic nested workspace context injection', () => { agent.session.append('step/start', { turn: 1, step: 1 }) agent.session.append('step/end', { turn: 1, step: 1 }) agent.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) ctx.emit('tools/result', stubToolExecution({ signal: testToolSignal, @@ -4290,7 +4290,7 @@ describe('dynamic nested workspace context injection', () => { agent, }), { content: [], isError: false, value: null }) - expect(blocksText((await syncedWorkspaceContext(ctx, agent)).content)) + expect(blocksText((await syncedAgentInstructions(ctx, agent)).content)) .toContain('nested package rule') } finally { await ctx.fiber.dispose() @@ -4303,7 +4303,7 @@ describe('dynamic nested workspace context injection', () => { const ctx = new Context() try { await ctx.plugin(RecordingFileSystem) - await mountWorkspaceContextPlugin(ctx, { maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { maxBytes: 65536 }) const fs = ctx.fs as RecordingFileSystem const agent = await stubAgent('/') const plainResult = { callId: ToolCallId('plain'), content: [], isError: false as const, value: null } @@ -4352,7 +4352,7 @@ describe('dynamic nested workspace context injection', () => { const ctx = new Context() try { await ctx.plugin(RecordingFileSystem) - await mountWorkspaceContextPlugin(ctx, { maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { maxBytes: 65536 }) const fs = ctx.fs as RecordingFileSystem const root = resolve('/') const agent = await stubAgent(root) @@ -4386,7 +4386,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 0 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 0 }) const result = await ctx.tools.execute({ signal: testToolSignal, @@ -4418,19 +4418,19 @@ describe('dynamic nested workspace context injection', () => { fs.entries.set(instructionPath, { type: 'file', content: 'x'.repeat(1000) }) fs.entries.set(join(root, 'pkg/file.txt'), { type: 'file', content: 'hello' }) await ctx.plugin(ToolFs) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 20 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 20 }) const agent = await stubAgent(root) const first = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('read-tiny-budget-1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) const second = await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('read-tiny-budget-2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(first.additionalContexts).toBeUndefined() expect(second.additionalContexts).toBeUndefined() @@ -4452,7 +4452,7 @@ describe('dynamic nested workspace context injection', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const result = await ctx.tools.execute({ signal: testToolSignal, @@ -4478,7 +4478,7 @@ describe('dynamic nested workspace context injection', () => { await write(join(root, 'pkg/AGENTS.md'), 'nested package rule') await write(join(root, 'pkg/deep/file.txt'), 'hello') const ctx = new Context() - const fiber = await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + const fiber = await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) await fiber.dispose() const agent = await stubAgent(root) @@ -4515,13 +4515,13 @@ describe('workspace context inbox synchronization', () => { await mkdir(join(root, '.git'), { recursive: true }) await write(join(root, 'AGENTS.md'), 'duplicate baseline') const ctx = new Context() - await mountWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) const desired = agent.inbox.nextStep[0]! agent.inbox.append('next-step', createUserMessage({ content: desired.content, source: desired.source })) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(agent.inbox.nextStep).toHaveLength(1) expect(agent.inbox.nextStep[0]?.id).toBe(desired.id) @@ -4540,14 +4540,14 @@ describe('workspace context inbox synchronization', () => { const fs = ctx.fs as RecordingFileSystem fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'file', content: 'tiny-budget rule' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 1 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 1 }) const agent = await stubAgent(root) ctx.emit('tools/result', stubToolExecution({ signal: testToolSignal, callId: ToolCallId('tiny-budget-touch'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }), acceptedResult) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) // One byte cannot semantically represent the transition, so nothing is // emitted and nothing commits — the uncommitted version retries on the @@ -4568,13 +4568,13 @@ describe('workspace context inbox synchronization', () => { await write(join(root, 'pkg/AGENTS.md'), 'pending version one') await write(join(root, 'pkg/file.txt'), 'file') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('pending-v1'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(blocksText(agent.inbox.nextStep[0]?.content)).toContain('pending version one') const duplicate = createUserMessage({ content: agent.inbox.nextStep[0]!.content, @@ -4587,7 +4587,7 @@ describe('workspace context inbox synchronization', () => { signal: testToolSignal, callId: ToolCallId('pending-v2'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(agent.inbox.nextStep).toHaveLength(1) expect(agent.inbox.nextStep[0]?.id).not.toBe(duplicate.id) expect(blocksText(agent.inbox.nextStep[0]?.content)).toContain('pending version two with more detail') @@ -4598,7 +4598,7 @@ describe('workspace context inbox synchronization', () => { signal: testToolSignal, callId: ToolCallId('pending-delete'), name: 'read', arguments: { file_path: join('pkg', 'file.txt') }, agent, }) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) expect(agent.inbox.nextStep).toEqual([]) } finally { await rm(root, { recursive: true, force: true }) @@ -4616,7 +4616,7 @@ describe('workspace context inbox synchronization', () => { fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'a/AGENTS.md'), { type: 'file', content: 'restored A' }) fs.entries.set(join(root, 'b/AGENTS.md'), { type: 'file', content: 'restored B' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = stubToolExecution({ signal: testToolSignal, @@ -4635,7 +4635,7 @@ describe('workspace context inbox synchronization', () => { signal: testToolSignal, callId: ToolCallId('projected-after-abort'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent, }), acceptedResult) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) const text = blocksText(agent.inbox.nextStep[0]?.content) expect(text).toContain('restored A') expect(text).toContain('restored B') @@ -4656,7 +4656,7 @@ describe('workspace context inbox synchronization', () => { fs.entries.set(join(root, '.git'), { type: 'directory' }) fs.entries.set(join(root, 'a/AGENTS.md'), { type: 'file', content: 'scope A' }) fs.entries.set(join(root, 'b/AGENTS.md'), { type: 'file', content: 'scope B' }) - await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 }) + await mountAgentInstructionsPlugin(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(root) const first = stubToolExecution({ signal: testToolSignal, @@ -4669,7 +4669,7 @@ describe('workspace context inbox synchronization', () => { ctx.emit('tools/result', first, acceptedResult) ctx.emit('tools/result', second, acceptedResult) - await syncWorkspaceContext(ctx, agent) + await syncAgentInstructions(ctx, agent) await vi.waitFor(() => { expect(agent.inbox.nextStep).toHaveLength(1) @@ -4694,20 +4694,20 @@ describe('workspace context inbox synchronization', () => { await write(join(root, 'b/AGENTS.md'), 'fresh scope B') await write(join(root, 'b/file.txt'), 'b') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const original = await stubAgent(root) await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('recover-pending-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent: original, }) - await syncWorkspaceContext(ctx, original) + await syncAgentInstructions(ctx, original) const resumed = await stubAgent(root, original.session.snapshotEvents()) await ctx.tools.execute({ signal: testToolSignal, callId: ToolCallId('recover-pending-b'), name: 'read', arguments: { file_path: join('b', 'file.txt') }, agent: resumed, }) - await syncWorkspaceContext(ctx, resumed) + await syncAgentInstructions(ctx, resumed) await vi.waitFor(() => { expect(resumed.inbox.nextStep).toHaveLength(1) @@ -4729,9 +4729,9 @@ describe('workspace context inbox synchronization', () => { await write(join(root, 'pkg/AGENTS.md'), 'old claimed rule') await write(join(root, 'pkg/file.txt'), 'file') const ctx = new Context() - await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 }) + await mountFileToolsAndAgentInstructions(ctx, { dshHome: home, maxBytes: 65536 }) const agent = await stubAgent(join(root, 'pkg')) - await syncedWorkspaceContext(ctx, agent) + await syncedAgentInstructions(ctx, agent) const claimed = claimInbox(agent, 'next-step') await write(join(root, 'pkg/AGENTS.md'), 'new claimed rule with more detail') const downstream = { kind: 'enter' as const, messages: claimed } @@ -4757,12 +4757,12 @@ describe('workspace context inbox synchronization', () => { describe('workspace context plugin export shape', () => { it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/Config/apply', () => { - expect('default' in workspaceContext).toBe(false) - expect(typeof workspaceContext.apply).toBe('function') + expect('default' in AgentInstructions).toBe(false) + expect(typeof AgentInstructions.apply).toBe('function') const loader = Object.create(Loader.prototype) as Loader - const unwrapped = loader.unwrapExports(workspaceContext) as Record - expect(unwrapped).toBe(workspaceContext) + const unwrapped = loader.unwrapExports(AgentInstructions) as Record + expect(unwrapped).toBe(AgentInstructions) expect(unwrapped.name).toBe('agent-instructions') expect(unwrapped.Config).toBeDefined() expect(typeof unwrapped.apply).toBe('function') diff --git a/packages/jobs/jobs-local/README.i18n.yaml b/packages/jobs/jobs-local/README.i18n.yaml index 9b44375ea3..963541f876 100644 --- a/packages/jobs/jobs-local/README.i18n.yaml +++ b/packages/jobs/jobs-local/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/jobs/jobs-local/README.md -README.md: 89c0f2018fd2cf26f9dc130e79ca6a568bc34b9e -README.zh.md: c8e5568dec32317c4c3e2da5a03fcd3cdaaf1067 +README.md: 51fbffc4892f6a4781343e13863770fb4e8e8e6a +README.zh.md: 710c02c923994df596e544345cac4cd4d0bd2809 diff --git a/packages/jobs/jobs-local/README.md b/packages/jobs/jobs-local/README.md index 89c0f2018f..51fbffc489 100644 --- a/packages/jobs/jobs-local/README.md +++ b/packages/jobs/jobs-local/README.md @@ -69,7 +69,7 @@ This section explains the design decisions behind the registry and points at the ### Design philosophy -- **In-memory records, fresh snapshots.** `LocalJobRegistry` keeps one `TrackedTask` per job and projects a new read-only snapshot per call; callers never receive live state. +- **In-memory records, fresh snapshots.** `LocalJobRegistry` keeps one `TrackedJob` per job and projects a new read-only snapshot per call; callers never receive live state. - **Owner-relative layers, one process-wide registry.** Controllers, completion listeners, and change observers are filed into the scope that registered them (`ScopedLayers`), and reads union the global layer with the owner's scope chain — so one preset's job controls never hold `start()` open for an agent whose own composition loads none, and a settlement reaches only the listeners its owner's composition registered. - **Preflight before start.** `start()` checks controller service, spec validity, live ownership, and capacity before invoking the producer, so a rejection leaves no job id or execution resource; registration commits without a later failable step. - **First-wins settlement, completion last.** The earliest terminal outcome records once, releases waiters, and notifies listeners once with per-listener containment; completion is announced after the record is committed and the visible-set change published, because a reporter may open a model turn synchronously. @@ -88,7 +88,7 @@ This section explains the design decisions behind the registry and points at the ### Admission and settlement -`activeTaskCount` counts authoritative records per exact owner or in the shared unowned bucket. `settle` marks a job reported when waiters are pending, resolves every waiter, records the terminal snapshot, announces the visible-set change, then notifies completion listeners. Pending waits mark the job reported before listeners run so completion reporters do not duplicate notices; a teardown cancel marks it for the same reason — nothing will read a notice addressed to an owner being destroyed. +`activeJobCount` counts authoritative records per exact owner or in the shared unowned bucket. `settle` marks a job reported when waiters are pending, resolves every waiter, records the terminal snapshot, announces the visible-set change, then notifies completion listeners. Pending waits mark the job reported before listeners run so completion reporters do not duplicate notices; a teardown cancel marks it for the same reason — nothing will read a notice addressed to an owner being destroyed. ### Teardown diff --git a/packages/jobs/jobs-local/README.zh.md b/packages/jobs/jobs-local/README.zh.md index c8e5568dec..710c02c923 100644 --- a/packages/jobs/jobs-local/README.zh.md +++ b/packages/jobs/jobs-local/README.zh.md @@ -69,7 +69,7 @@ kind: "package-reference" ### 设计理念 -- **内存记录,全新快照。** `LocalJobRegistry` 为每个任务保存一条 `TrackedTask`,每次调用都投影出新的只读快照;调用方永远不会拿到实时状态。 +- **内存记录,全新快照。** `LocalJobRegistry` 为每个任务保存一条 `TrackedJob`,每次调用都投影出新的只读快照;调用方永远不会拿到实时状态。 - **按所有者分层,一个进程级注册表。** 控制器、完成监听器与变更观察者归档到注册方所在的 scope(`ScopedLayers`),读取把全局层与所有者的 scope 链求并集——因此某个 preset 的任务控制绝不会为自身组合未加载任何控制器的 agent 保持 `start()` 可用,一次结算也只会抵达其所有者所属组合注册的监听器。 - **启动前先预检。** `start()` 在调用生产方之前检查控制器服务、spec 有效性、仍存活的所有权与容量,因此拒绝不会留下 job id 或执行资源;注册一旦提交,后续不再有可失败步骤。 - **结算首次优先,完成最后。** 最早的终止结果只记录一次,释放等待方,并只通知监听器一次,各监听器故障单独隔离;完成在记录提交且可见集变更发布之后才宣布,因为报告方可能同步开启一个模型轮次。 @@ -88,7 +88,7 @@ kind: "package-reference" ### 准入与结算 -`activeTaskCount` 按精确所有者或共享无主桶统计权威记录。`settle` 在存在挂起等待方时把任务标为已报告,解析每个等待方,记录终止快照,宣布可见集变更,然后通知完成监听器。挂起的等待会在监听器运行前把任务标为已报告,因此完成报告方不会重复通知;销毁时的取消出于同样理由标记——面向正在被销毁的所有者的通知不会有人读到。 +`activeJobCount` 按精确所有者或共享无主桶统计权威记录。`settle` 在存在挂起等待方时把任务标为已报告,解析每个等待方,记录终止快照,宣布可见集变更,然后通知完成监听器。挂起的等待会在监听器运行前把任务标为已报告,因此完成报告方不会重复通知;销毁时的取消出于同样理由标记——面向正在被销毁的所有者的通知不会有人读到。 ### 销毁 diff --git a/packages/jobs/jobs-local/src/index.ts b/packages/jobs/jobs-local/src/index.ts index 8c71ae1b79..0e0f161eaf 100644 --- a/packages/jobs/jobs-local/src/index.ts +++ b/packages/jobs/jobs-local/src/index.ts @@ -25,7 +25,7 @@ import type { export const TASK_WAIT_TIMEOUT = 'TASK_WAIT_TIMEOUT' /** Default maximum number of active jobs in one exact-owner bucket. */ -const DEFAULT_MAX_CONCURRENT_TASKS_PER_OWNER = 10 +const DEFAULT_MAX_CONCURRENT_JOBS_PER_OWNER = 10 /** Configuration for the process-local job registry. */ export interface Config { @@ -37,7 +37,7 @@ export interface Config { } /** The registry's mutable per-job record (never handed out — see {@link LocalJobRegistry.snapshot}). */ -interface TrackedTask { +interface TrackedJob { id: JobId kind: JobKind label: string @@ -94,12 +94,12 @@ export class LocalJobRegistry extends JobRegistry { .step(1) .min(1) .max(Number.MAX_SAFE_INTEGER) - .default(DEFAULT_MAX_CONCURRENT_TASKS_PER_OWNER), + .default(DEFAULT_MAX_CONCURRENT_JOBS_PER_OWNER), }) /** Schemastery-defaulted active-job limit. */ private readonly maxConcurrentJobsPerOwner: number - private store = new Map() + private store = new Map() private counters = new Map() /** * Surfaces and listeners layered by the scope that registered them, in the @@ -140,7 +140,7 @@ export class LocalJobRegistry extends JobRegistry { } if (spec.owner !== undefined) this.ensureOwnerCleanup(spec.owner) - const active = this.activeTaskCount(spec.owner) + const active = this.activeJobCount(spec.owner) if (active >= this.maxConcurrentJobsPerOwner) { throw new Error( `background job limit reached for this owner (limit: ${this.maxConcurrentJobsPerOwner}); use job_kill to stop an unneeded job, wait for it to finish, then retry`, @@ -154,7 +154,7 @@ export class LocalJobRegistry extends JobRegistry { let markSettled!: () => void const settled = new Promise((resolve) => { markSettled = resolve }) - const job: TrackedTask = { + const job: TrackedJob = { id, kind: spec.kind, label: spec.label, @@ -319,7 +319,7 @@ export class LocalJobRegistry extends JobRegistry { } /** Count authoritative active records for one exact owner or the shared unowned bucket. */ - private activeTaskCount(owner: Agent | undefined): number { + private activeJobCount(owner: Agent | undefined): number { let count = 0 for (const job of this.store.values()) { if (job.owner === owner && (job.status === 'running' || job.status === 'stopping')) count += 1 @@ -342,7 +342,7 @@ export class LocalJobRegistry extends JobRegistry { } /** Look up a job or fail loud. */ - private expect(id: JobId): TrackedTask { + private expect(id: JobId): TrackedJob { const job = this.store.get(id) if (job === undefined) throw new Error(`unknown job ${id}`) return job @@ -353,14 +353,14 @@ export class LocalJobRegistry extends JobRegistry { * whose session id matches (`!== undefined` semantics — an unowned job is * open, and a no-agent caller can never match an owned one). */ - private assertAccess(job: TrackedTask, caller?: Agent): void { + private assertAccess(job: TrackedJob, caller?: Agent): void { if (job.owner !== undefined && job.owner.id !== caller?.id) { throw new Error(`job ${job.id} belongs to another session`) } } /** Project a fresh read-only snapshot from the mutable record. */ - private snapshot(job: TrackedTask): JobSnapshot { + private snapshot(job: TrackedJob): JobSnapshot { const ownerSession = job.owner?.id return { id: job.id, @@ -413,7 +413,7 @@ export class LocalJobRegistry extends JobRegistry { * synchronously: every other observer of this settlement must already have * seen the committed record. */ - private settle(job: TrackedTask, outcome: JobOutcome): void { + private settle(job: TrackedJob, outcome: JobOutcome): void { if (isTerminal(job.status)) return job.status = outcome.status job.detail = outcome.detail @@ -504,7 +504,7 @@ export class LocalJobRegistry extends JobRegistry { * force-fails the record and reports a possible orphan; a cancel that returns * without settling remains indistinguishable from a slow stop and may stall. */ - private cancelForTeardown(jobs: TrackedTask[], reason: string): void { + private cancelForTeardown(jobs: TrackedJob[], reason: string): void { for (const job of jobs) { if (isTerminal(job.status)) continue // Teardown cancellation is a kill without a caller, so it claims the diff --git a/packages/jobs/tool-jobs/src/index.ts b/packages/jobs/tool-jobs/src/index.ts index 325766ee2b..277d7181a4 100644 --- a/packages/jobs/tool-jobs/src/index.ts +++ b/packages/jobs/tool-jobs/src/index.ts @@ -63,7 +63,7 @@ export interface PublicJobSnapshot { } /** Shared schema for job-control outputs. */ -const PUBLIC_TASK_SCHEMA = { +const PUBLIC_JOB_SCHEMA = { type: 'object', additionalProperties: false, properties: { @@ -197,7 +197,7 @@ function validateJobId(value: string): JobId { } /** Pending presentation shared by the three generic job controls. */ -function presentTaskCall(title: string, kind: 'read' | 'execute', rawInput?: string): GenericCallView { +function presentJobCall(title: string, kind: 'read' | 'execute', rawInput?: string): GenericCallView { return { card: 'generic', title, kind, ...rawInput !== undefined ? { rawInput } : {} } } @@ -234,7 +234,7 @@ export function apply(ctx: Context, config: Config): void { if (maxBytes !== undefined) outputLimits.set(exec, maxBytes) return next() }, { prepend: true }) - const finalizeTaskContent: NonNullable = (exec, result) => { + const finalizeJobContent: NonNullable = (exec, result) => { const maxBytes = outputLimits.get(exec) ?? visibleOutputLimit(ctx, exec) outputLimits.delete(exec) if (maxBytes === undefined) return undefined @@ -310,14 +310,14 @@ export function apply(ctx: Context, config: Config): void { wait: { type: 'boolean', description: 'Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive.' }, timeout_ms: { type: 'number', description: 'Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum.' }, }, - finalizeContent: finalizeTaskContent, + finalizeContent: finalizeJobContent, output: { schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string', required: true }, - job: { ...PUBLIC_TASK_SCHEMA, required: true }, + job: { ...PUBLIC_JOB_SCHEMA, required: true }, }, }, render: (_args, value) => { @@ -335,7 +335,7 @@ export function apply(ctx: Context, config: Config): void { const read = ctx.jobs.read(id, exec.agent) return { text: read.text, job: publicJob(read.snapshot) } }, - presentCall: args => presentTaskCall(`Read output from background job ${args.job_id}`, 'read', args.job_id), + presentCall: args => presentJobCall(`Read output from background job ${args.job_id}`, 'read', args.job_id), })) ctx.tools.register(defineTool({ @@ -343,7 +343,7 @@ export function apply(ctx: Context, config: Config): void { description: 'List your background jobs (running and finished) with their ids, kinds, and statuses.', parameters: {}, output: { - schema: { type: 'array', items: PUBLIC_TASK_SCHEMA }, + schema: { type: 'array', items: PUBLIC_JOB_SCHEMA }, render: (_args, jobs) => [{ type: 'text', text: jobs.length === 0 @@ -355,7 +355,7 @@ export function apply(ctx: Context, config: Config): void { const jobs = ctx.jobs.list(exec.agent) return Promise.resolve(jobs.map(publicJob)) }, - presentCall: () => presentTaskCall('List background jobs', 'read'), + presentCall: () => presentJobCall('List background jobs', 'read'), })) ctx.tools.register(defineTool({ @@ -365,7 +365,7 @@ export function apply(ctx: Context, config: Config): void { job_id: { type: 'string', required: true, description: 'Job id returned by the tool that started the background work.' }, reason: { type: 'string', description: 'Optional short reason, recorded in the log and forwarded to the job.' }, }, - finalizeContent: finalizeTaskContent, + finalizeContent: finalizeJobContent, output: { schema: { type: 'object', @@ -376,7 +376,7 @@ export function apply(ctx: Context, config: Config): void { required: true, enum: ['cancellation-requested', 'already-finished'], }, - job: { ...PUBLIC_TASK_SCHEMA, required: true }, + job: { ...PUBLIC_JOB_SCHEMA, required: true }, }, }, render: (_args, value) => [{ @@ -396,6 +396,6 @@ export function apply(ctx: Context, config: Config): void { job: snapshot, }) }, - presentCall: args => presentTaskCall(`Kill background job ${args.job_id}`, 'execute', args.job_id), + presentCall: args => presentJobCall(`Kill background job ${args.job_id}`, 'execute', args.job_id), })) } diff --git a/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts b/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts index 4c6a81e48a..d146cab224 100644 --- a/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts +++ b/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts @@ -11,7 +11,7 @@ import { bindScopeParent, createScope, scopeOf } from '@deepseek-ai/dsh-scope' import { JobId } from '@deepseek-ai/dsh-jobs' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' import type { JobHooks, JobOutcome, JobSnapshot, JobStart } from '@deepseek-ai/dsh-jobs' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import { statusLine } from '@deepseek-ai/dsh-tool-jobs' const testToolSignal = new AbortController().signal @@ -19,13 +19,13 @@ const testToolSignal = new AbortController().signal const agentRegistryDisposers = new WeakMap void>() const agentScopeFibers = new WeakMap Promise }>() -async function setup(config: ToolTasks.Config = {}) { +async function setup(config: ToolJobs.Config = {}) { const ctx = new Context() await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) const agentsFiber = await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - const toolsFiber = await ctx.plugin(ToolTasks, config) + const toolsFiber = await ctx.plugin(ToolJobs, config) return { ctx, agentsFiber, toolsFiber } } @@ -124,15 +124,15 @@ describe('tool-jobs setup', () => { await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) await ctx.plugin(LocalJobRegistry) - await expect(ctx.plugin(ToolTasks, { waitTimeoutMs: 100, maxWaitTimeoutMs: 50 })) + await expect(ctx.plugin(ToolJobs, { waitTimeoutMs: 100, maxWaitTimeoutMs: 50 })) .rejects.toThrow('waitTimeoutMs (100) exceeds maxWaitTimeoutMs (50)') }) it('defaults delivery to wakeup and rejects an unknown lane', () => { - expect(ToolTasks.Config({}).completionDelivery).toBe('wakeup') - expect(ToolTasks.Config({}).maxConsecutiveWakes).toBe(3) - expect(() => ToolTasks.Config({ completionDelivery: 'loud' as never })).toThrow() - expect(() => ToolTasks.Config({ maxConsecutiveWakes: 0 })).toThrow() + expect(ToolJobs.Config({}).completionDelivery).toBe('wakeup') + expect(ToolJobs.Config({}).maxConsecutiveWakes).toBe(3) + expect(() => ToolJobs.Config({ completionDelivery: 'loud' as never })).toThrow() + expect(() => ToolJobs.Config({ maxConsecutiveWakes: 0 })).toThrow() }) it('rejects a wake budget that cannot bound anything', async () => { @@ -143,7 +143,7 @@ describe('tool-jobs setup', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(LocalJobRegistry) try { - await ctx.plugin(ToolTasks, { maxConsecutiveWakes }) + await ctx.plugin(ToolJobs, { maxConsecutiveWakes }) return 'loaded' } catch (error: unknown) { return String(error) @@ -170,7 +170,7 @@ describe('tool-jobs setup', () => { await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) await ctx.plugin(LocalJobRegistry) - ToolTasks.apply(ctx, {}) + ToolJobs.apply(ctx, {}) expect(ctx.tools.get('job_output')).toBeDefined() expect(() => ctx.jobs.start(producer().spec)).not.toThrow() }) @@ -521,8 +521,8 @@ describe('completion notices across scoped mounts', () => { const standingA = createScope(ctx, {}) const standingB = createScope(ctx, {}) - await standingA.ctx.plugin(ToolTasks) - await standingB.ctx.plugin(ToolTasks) + await standingA.ctx.plugin(ToolJobs) + await standingB.ctx.plugin(ToolJobs) // The agent joins preset A exactly as `agentPresets.compose` binds it. const agentKey = {} diff --git a/packages/shell/tool-bash/tests/integration.spec.ts b/packages/shell/tool-bash/tests/integration.spec.ts index cf2a4e1d2e..013eaacf58 100644 --- a/packages/shell/tool-bash/tests/integration.spec.ts +++ b/packages/shell/tool-bash/tests/integration.spec.ts @@ -10,7 +10,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local' import * as ToolBash from '@deepseek-ai/dsh-tool-bash' @@ -31,7 +31,7 @@ async function harness(adapter: MockAdapter, sessionRoot?: string, dshHome?: str } await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(LocalSubprocessRuntime) await ctx.plugin(BashEnvPlugin, dshHome === undefined ? {} : { dshHome }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) diff --git a/packages/shell/tool-bash/tests/tools.spec.ts b/packages/shell/tool-bash/tests/tools.spec.ts index 2e50982c3e..81fc5a3b27 100644 --- a/packages/shell/tool-bash/tests/tools.spec.ts +++ b/packages/shell/tool-bash/tests/tools.spec.ts @@ -13,7 +13,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import { turnBoundaryProjectionDefinition } from '@deepseek-ai/dsh-agent-loop' import { SessionId } from '@deepseek-ai/dsh-session' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import ApprovalService from '@deepseek-ai/dsh-user-approval' import type { ApprovalOutcome } from '@deepseek-ai/dsh-user-approval' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' @@ -48,13 +48,13 @@ async function setup() { } /** Full harness: the generic job runtime + its controller, then the bash tool. */ -async function setupWithTasks() { +async function setupWithJobs() { const ctx = new Context() await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(LocalSubprocessRuntime) ;(ctx.subprocess as LocalSubprocessRuntime).internals = { spillDir } await ctx.plugin(BashEnvPlugin) @@ -192,7 +192,7 @@ async function setupSandboxed(withApproval = false) { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(SessionProjectionRegistry) ctx.sessionProjections.register(turnBoundaryProjectionDefinition) await ctx.plugin(SandboxPolicyService, {}) @@ -456,7 +456,7 @@ describe('bash tool', () => { describe('background execution through the job runtime', () => { it('run_in_background acks with the job id, readable through the REAL job_output tool', async () => { - const ctx = await setupWithTasks() + const ctx = await setupWithJobs() const started = await call(ctx, 'bash', { command: 'echo bg-ok', description: 'test command', run_in_background: true }) expect(started.isError).toBe(false) if (started.isError) throw new Error('expected background bash success') @@ -471,7 +471,7 @@ describe('background execution through the job runtime', () => { }) it('a running background job is killable through the REAL job_kill tool', async () => { - const ctx = await setupWithTasks() + const ctx = await setupWithJobs() await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true }) const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' }) @@ -483,7 +483,7 @@ describe('background execution through the job runtime', () => { }) it('a self-signal background exit is reported as killed through the REAL job_output tool', async () => { - const ctx = await setupWithTasks() + const ctx = await setupWithJobs() await call(ctx, 'bash', { command: 'kill -TERM $$', description: 'test command', run_in_background: true }) const final = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }) @@ -492,7 +492,7 @@ describe('background execution through the job runtime', () => { it('a background job started by an agent is registered with that agent as owner', async () => { // The producer must forward exec.agent as the job owner. - const ctx = await setupWithTasks() + const ctx = await setupWithJobs() const agent = registerFakeAgent(ctx, 'sess-owner') const started = await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true }, agent) expect(text(started)).toBe('started background job bash-1') @@ -507,7 +507,7 @@ describe('background execution through the job runtime', () => { }) it('fails loud when the job runtime is not loaded', async () => { - const ctx = await setup() // no LocalJobRegistry / ToolTasks + const ctx = await setup() // no LocalJobRegistry / ToolJobs const result = await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true }) expect(result.isError).toBe(true) expect(text(result)).toContain('background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs') @@ -519,7 +519,7 @@ describe('background execution through the job runtime', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(CountingStartExecutor) await ctx.plugin(BashEnvPlugin) await ctx.plugin(ToolBash) @@ -1125,7 +1125,7 @@ describe('the model-facing bash tool builds its request from named args only (no await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(BashEnvPlugin, { dshHome: recordingDshHome }) await ctx.plugin(RecordingBashExecutor) await ctx.plugin(ToolBash) diff --git a/packages/shell/tool-pwsh/tests/integration.spec.ts b/packages/shell/tool-pwsh/tests/integration.spec.ts index 84d308e1b3..35fed9d39b 100644 --- a/packages/shell/tool-pwsh/tests/integration.spec.ts +++ b/packages/shell/tool-pwsh/tests/integration.spec.ts @@ -19,7 +19,7 @@ import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED } from '@deepseek-ai/dsh-tools' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local' import { PwshLocalExecutor, resolvePwshPath } from '@deepseek-ai/dsh-pwsh-local' import * as ToolPwsh from '@deepseek-ai/dsh-tool-pwsh' @@ -61,7 +61,7 @@ describe.skipIf(!hasPwsh)('pwsh tool over the real pwsh executor', () => { await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(LocalSubprocessRuntime) await ctx.plugin(BashEnvPlugin) await ctx.plugin(PwshLocalExecutor, { timeoutMs: 20_000, graceMs: 200 }) diff --git a/packages/shell/tool-pwsh/tests/tools.spec.ts b/packages/shell/tool-pwsh/tests/tools.spec.ts index e88ac50141..98a86121be 100644 --- a/packages/shell/tool-pwsh/tests/tools.spec.ts +++ b/packages/shell/tool-pwsh/tests/tools.spec.ts @@ -19,7 +19,7 @@ import { ToolCallId } from '@deepseek-ai/dsh-llm' import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import AgentRegistry from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' import { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session' @@ -148,13 +148,13 @@ async function setup(toolConfig: Partial = {}, dshHome?: string } /** Full harness: the generic job runtime + its controller, then the pwsh tool. */ -async function setupWithTasks(toolConfig: Partial = {}, dshHome?: string) { +async function setupWithJobs(toolConfig: Partial = {}, dshHome?: string) { const ctx = new Context() await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(BashEnvPlugin, dshHome === undefined ? {} : { dshHome }) await ctx.plugin(FakeBash) await ctx.plugin(ToolPwsh, toolConfig) @@ -216,7 +216,7 @@ async function setupSandboxed(withApproval = false) { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) await ctx.plugin(BashEnvPlugin) await ctx.plugin(SessionProjectionRegistry) // The loop's turnBoundary unit (the open-turn fold) is not mounted in this @@ -740,7 +740,7 @@ describe('sandbox escalation through ctx.approval', () => { describe('background execution through the job runtime', () => { it('run_in_background acks with the job id, readable through the REAL job_output tool', async () => { - const { ctx } = await setupWithTasks() + const { ctx } = await setupWithJobs() const started = await call(ctx, 'pwsh', { command: 'Write-Output bg-ok', description: 'test command', run_in_background: true }) expect(started.isError).toBe(false) if (started.isError) throw new Error('expected background pwsh success') @@ -755,7 +755,7 @@ describe('background execution through the job runtime', () => { }) it('a running background job is killable through the REAL job_kill tool', async () => { - const { ctx, bash } = await setupWithTasks() + const { ctx, bash } = await setupWithJobs() bash.backgroundHandler = () => killableProcess() await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true }) @@ -768,7 +768,7 @@ describe('background execution through the job runtime', () => { }) it('a background job started by an agent is registered with that agent as owner', async () => { - const { ctx } = await setupWithTasks() + const { ctx } = await setupWithJobs() const agent = registerFakeAgent(ctx, 'sess-owner') const started = await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true }, agent) expect(text(started)).toBe('started background job pwsh-1') @@ -783,14 +783,14 @@ describe('background execution through the job runtime', () => { }) it('fails loud when the job runtime is not loaded', async () => { - const { ctx } = await setup() // no LocalJobRegistry / ToolTasks + const { ctx } = await setup() // no LocalJobRegistry / ToolJobs const result = await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true }) expect(result.isError).toBe(true) expect(text(result)).toContain('background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs') }) it('a pre-aborted call is skipped before the process starts', async () => { - const { ctx, bash } = await setupWithTasks() + const { ctx, bash } = await setupWithJobs() const controller = new AbortController() controller.abort() const result = await ctx.tools.execute({ diff --git a/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts b/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts index 1e79190deb..d070fa00d8 100644 --- a/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts +++ b/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts @@ -17,7 +17,7 @@ import SubagentRuntime from '@deepseek-ai/dsh-subagent' import type { SubagentStartRequest } from '@deepseek-ai/dsh-subagent' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn-in-process' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import { loadStoredSession } from '../../subagent/tests/persistence-helpers.ts' import * as mock from './scripted-provider.ts' @@ -819,7 +819,7 @@ describe('dsh-tool-subagent background mode', () => { const ctx = await setup(toolConfig, mockConfig) await ctx.plugin(AgentRegistry) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks, {}) + await ctx.plugin(ToolJobs, {}) return ctx } @@ -1191,7 +1191,7 @@ describe('dsh-tool-subagent continuable background mode', () => { await ctx.plugin(SubagentRuntime) await ctx.plugin(SubagentSpawn, { providerName: 'spawn' }) await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks, {}) + await ctx.plugin(ToolJobs, {}) await ctx.plugin(tool, { provider: 'spawn', backgroundMode: 'continuable' }) ctx.llm.registerAdapter(['mock'], new MockAdapter([ textResponse('continuable answer'), diff --git a/packages/terminal/tool-terminal/src/index.ts b/packages/terminal/tool-terminal/src/index.ts index 880cfcd31f..1865ebe165 100644 --- a/packages/terminal/tool-terminal/src/index.ts +++ b/packages/terminal/tool-terminal/src/index.ts @@ -105,7 +105,7 @@ const SESSION_SNAPSHOT_SCHEMA = { properties: SESSION_SNAPSHOT_PROPERTIES, } as const -const BACKGROUND_TASK_OUTPUT_SCHEMA = { +const BACKGROUND_JOB_OUTPUT_SCHEMA = { type: 'object', additionalProperties: false, properties: { @@ -210,7 +210,7 @@ export function apply(ctx: Context, config: Config = {}): void { output: { schema: { oneOf: [ - BACKGROUND_TASK_OUTPUT_SCHEMA, + BACKGROUND_JOB_OUTPUT_SCHEMA, { type: 'object', additionalProperties: false, diff --git a/packages/terminal/tool-terminal/tests/tools.spec.ts b/packages/terminal/tool-terminal/tests/tools.spec.ts index 8c0c7f6292..e7a7467204 100644 --- a/packages/terminal/tool-terminal/tests/tools.spec.ts +++ b/packages/terminal/tool-terminal/tests/tools.spec.ts @@ -10,7 +10,7 @@ import type { ToolSdkSchema } from '@deepseek-ai/dsh-tools/src/ts-types.ts' import TerminalSessionService, { TerminalSessionId } from '@deepseek-ai/dsh-terminal' import type { TerminalBackend, TerminalBackendSession, TerminalSendOperation, TerminalSendRequest, TerminalSessionStatus, TerminalSignal } from '@deepseek-ai/dsh-terminal' import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import * as ToolPty from '@deepseek-ai/dsh-tool-terminal' import { unsupportedInbox } from '@deepseek-ai/dsh-agent-loop-testkit' @@ -114,7 +114,7 @@ async function setupBase(jobs: boolean) { ctx.terminals.registerBackend(stub.backend) if (jobs) { await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) } return { ctx, stub, agent: fakeAgent(ctx, jobs ? 'with-tasks' : 'foreground') } } diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 5988f69575..5b7bb6fb0b 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -58,7 +58,7 @@ import Lsp from '@deepseek-ai/dsh-lsp' import * as ToolLsp from '@deepseek-ai/dsh-tool-lsp' import * as ToolSkill from '@deepseek-ai/dsh-tool-skill' import * as ToolSessionQuery from '@deepseek-ai/dsh-tool-session-query' -import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs' +import * as ToolJobs from '@deepseek-ai/dsh-tool-jobs' import type TeamService from '@deepseek-ai/dsh-experimental-agent-team' import * as ToolTeam from '@deepseek-ai/dsh-experimental-tool-agent-team' import * as ToolTodo from '@deepseek-ai/dsh-tool-todo' @@ -515,7 +515,7 @@ const TOOL_PACKAGES: ToolPackage[] = [ writes: ['tool/call', 'tool/result', 'user/message via agent.inject() for background completion notices'], async mount(ctx) { await ctx.plugin(LocalJobRegistry) - await ctx.plugin(ToolTasks) + await ctx.plugin(ToolJobs) }, note: 'The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers\' `ctx.jobs.start()`.',