Kaynağa Gözat

refactor: align instruction and job helper symbols

Tianyi Cui 1 ay önce
ebeveyn
işleme
6c3c3064c3

+ 4 - 4
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<Context> {
   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<Context> {
 async function backgroundPtcModeHarness(cwd: string): Promise<Context> {
   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 })

+ 2 - 2
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,

+ 6 - 6
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<RenderedWorkspaceContext | undefined> {
+): Promise<RenderedAgentInstructions | undefined> {
   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
       ? {}

+ 8 - 8
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<void> => {
-    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<PreStepDecision> => {
     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

+ 16 - 16
packages/context/agent-instructions/src/render.ts

@@ -9,14 +9,14 @@ import type { InstructionFile, LoadedInstructionFile } from './files.ts'
 
 const SYSTEM_REMINDER_OPEN = '<system-reminder>'
 const SYSTEM_REMINDER_CLOSE = '</system-reminder>'
-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
 }

+ 7 - 7
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),
   }
 }

+ 2 - 2
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({

Dosya farkı çok büyük olduğundan ihmal edildi
+ 121 - 121
packages/context/agent-instructions/tests/agent-instructions.spec.ts


+ 2 - 2
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

+ 2 - 2
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
 

+ 2 - 2
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` 在存在挂起等待方时把任务标为已报告,解析每个等待方,记录终止快照,宣布可见集变更,然后通知完成监听器。挂起的等待会在监听器运行前把任务标为已报告,因此完成报告方不会重复通知;销毁时的取消出于同样理由标记——面向正在被销毁的所有者的通知不会有人读到。
 
 ### 销毁
 

+ 12 - 12
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<JobId, TrackedTask>()
+  private store = new Map<JobId, TrackedJob>()
   private counters = new Map<string, number>()
   /**
    * 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<void>((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

+ 11 - 11
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<ToolDefinition['finalizeContent']> = (exec, result) => {
+  const finalizeJobContent: NonNullable<ToolDefinition['finalizeContent']> = (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),
   }))
 }

+ 12 - 12
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<Agent, () => void>()
 const agentScopeFibers = new WeakMap<Agent, { dispose: () => Promise<void> }>()
 
-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 = {}

+ 2 - 2
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 })

+ 11 - 11
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)

+ 2 - 2
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 })

+ 9 - 9
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<ToolPwsh.Config> = {}, dshHome?: string
 }
 
 /** Full harness: the generic job runtime + its controller, then the pwsh tool. */
-async function setupWithTasks(toolConfig: Partial<ToolPwsh.Config> = {}, dshHome?: string) {
+async function setupWithJobs(toolConfig: Partial<ToolPwsh.Config> = {}, 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({

+ 3 - 3
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'),

+ 2 - 2
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,

+ 2 - 2
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') }
 }

+ 2 - 2
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()`.',

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor