{"version":3,"file":"task.cli.mjs","names":[],"sources":["../../../../../../../../src/extensions/workspaces/sessions/(backend)/tool/_lib/task.cli.ts"],"sourcesContent":["import { defineCliTool, type WithRoot } from '@agimon-ai/doompi-core/extension-file';\nimport { definePiTool, type PiPluginContext } from '@agimon-ai/doompi-core/pi-extension';\nimport type { ToolDefinition, ExtensionContext } from '@earendil-works/pi-coding-agent';\n\nimport { DEFAULT_PROMPT_GUIDELINES } from '../../../../../../constants/promptGuidelines';\nimport type { TaskAction, TaskAssignment, TaskMutationParams } from '../../../../../../models/task';\nimport {\n  MSG_UPSERT_FIELD_MISPLACED,\n  TaskParamsSchema,\n  TOOL_LABEL,\n  TOOL_NAME,\n  taskActionAcceptsField,\n} from '../../../../../../schemas/task';\nimport { DEFAULT_MAX_TASKS } from '../../../../../../services/config';\nimport type { AssignOptions, DelegationManager, DelegationOutcome } from '../../../../../../services/delegation';\nimport { applyTaskMutation, isCommittingOp, type ReducerAction } from '../../../../../../services/reducer';\nimport {\n  type AssignmentItemResult,\n  buildAssignmentResult,\n  buildTextResult,\n  buildToolResult,\n  formatAssignmentResults,\n  formatUpsertFailureText,\n  type ToolResult,\n} from '../../../../../../services/taskResult';\nimport type { TaskStore } from '../../../../../../services/taskStore';\nimport { TASK_EVENT, type TaskFailureReporter } from '../../../../../../types/telemetry';\nimport type { TaskPiScope } from '../../_lib/root.cli';\n\nexport { DEFAULT_PROMPT_GUIDELINES } from '../../../../../../constants/promptGuidelines';\n\n/** @deprecated Task usage policy now lives only in the tool description. */\nexport const DEFAULT_PROMPT_SNIPPET =\n  'Track only complex, multi-step jobs that benefit from persistent progress or delegation; skip simple work';\n\nexport interface TaskToolDependencies {\n  store: TaskStore;\n  delegation: DelegationManager;\n  maxTasks?: number;\n  onChange?: () => void;\n  report?: TaskFailureReporter;\n  waitUntilReady?: (context: ExtensionContext, signal?: AbortSignal) => Promise<void>;\n}\n\nexport default defineCliTool((context: WithRoot<PiPluginContext, TaskPiScope>) =>\n  definePiTool(createTaskTool(context.root.toolDependencies)),\n);\n\nconst ACTION_ATTRIBUTE = 'tool.action';\nconst TASK_ID_ATTRIBUTE = 'task.id';\nconst REDUCER_ACTIONS = new Set<TaskAction>(['upsert', 'list', 'get', 'delete', 'clear']);\n\nclass TaskToolExecutionError extends Error {}\n\nfunction actionableTaskError(action: TaskAction, message: string): TaskToolExecutionError {\n  return new TaskToolExecutionError(\n    [\n      `Task ${action} failed: ${message}`,\n      '',\n      'Options:',\n      '- List the task board and inspect the current ids, states, and blockers.',\n      '- Correct the arguments or state conflict, then retry once.',\n      '- Ask the user before cancelling work or changing dependencies to recover.',\n    ].join('\\n'),\n  );\n}\n\n/**\n * Reject a field that belongs to a different action.\n *\n * The params schema is flat and shared across every action, so this is the only\n * place `{\"action\":\"upsert\",\"id\":3}` can be caught — and it must be, because\n * the entry it carries has no id and would silently create a duplicate task.\n */\nfunction rejectStrayFields(action: TaskAction, params: TaskMutationParams): void {\n  const stray = Object.keys(params).filter((key) => params[key] !== undefined && !taskActionAcceptsField(action, key));\n  if (stray.length === 0) return;\n  const hint = action === 'upsert' ? ` ${MSG_UPSERT_FIELD_MISPLACED}` : '';\n  throw actionableTaskError(action, `${action} does not accept ${stray.join(', ')}.${hint}`);\n}\n\n/** assignments[] is the only assign contract, even when it contains one task. */\nfunction resolveAssignments(params: TaskMutationParams): TaskAssignment[] {\n  const assignments = params.assignments;\n  if (!assignments || assignments.length === 0) {\n    throw actionableTaskError('assign', 'assign requires a non-empty assignments[] array');\n  }\n  return assignments;\n}\n\nfunction assignOptions(request: TaskAssignment, signal: AbortSignal | undefined): AssignOptions {\n  return {\n    agent: request.agent,\n    inlineAgent: request.inlineAgent,\n    instructions: request.instructions,\n    relevantFiles: request.relevantFiles,\n    priorFindings: request.priorFindings,\n    model: request.model,\n    context: request.context,\n    signal,\n  };\n}\n\nasync function executeAssignmentBatch(\n  delegation: DelegationManager,\n  assignments: readonly TaskAssignment[],\n  signal: AbortSignal | undefined,\n  report: TaskFailureReporter | undefined,\n): Promise<AssignmentItemResult[]> {\n  const results: AssignmentItemResult[] = [];\n  for (const [index, assignment] of assignments.entries()) {\n    let outcome: DelegationOutcome;\n    try {\n      outcome = await delegation.assign(assignment.id, assignOptions(assignment, signal));\n    } catch (error) {\n      report?.error(TASK_EVENT.toolFailed, error, {\n        [ACTION_ATTRIBUTE]: 'assign',\n        [TASK_ID_ATTRIBUTE]: assignment.id,\n      });\n      outcome = { ok: false, message: error instanceof Error ? error.message : String(error) };\n    }\n    results.push({ index, id: assignment.id, agent: assignment.agent, ...outcome });\n  }\n  return results;\n}\n\n/** Run an action through the reducer, persisting only when it produced a change. */\nasync function executeReducerAction(\n  store: TaskStore,\n  action: ReducerAction,\n  params: TaskMutationParams,\n  maxTasks: number,\n): Promise<ToolResult> {\n  const { document, value } = await store.mutate((current) => {\n    const result = applyTaskMutation(current, action, params, undefined, maxTasks);\n    return {\n      ...(isCommittingOp(result.op) ? { document: result.document } : {}),\n      value: result,\n    };\n  });\n  if (value.op.kind === 'error') throw actionableTaskError(action, value.op.message);\n  // An upsert that applied nothing is a failed call: it wrote nothing and left\n  // `rev` alone, so it must not read to the model as a success. A batch where\n  // some entries landed returns normally — that is what partial apply means.\n  if (value.op.kind === 'upsert' && value.op.applied === 0) {\n    throw actionableTaskError(action, formatUpsertFailureText(value.op));\n  }\n  // `document` is the committed document, so `details.rev` matches what landed\n  // on disk; `value.document` is the pre-write copy and lags by one.\n  return buildToolResult(action, params, document, value.op);\n}\n\nexport function createTaskTool(\n  dependencies: TaskToolDependencies,\n  renderers: Pick<ToolDefinition<typeof TaskParamsSchema, ToolResult['details']>, 'renderCall' | 'renderResult'> = {},\n): ToolDefinition<typeof TaskParamsSchema, ToolResult['details']> {\n  const { store, delegation, maxTasks = DEFAULT_MAX_TASKS } = dependencies;\n\n  return {\n    name: TOOL_NAME,\n    label: TOOL_LABEL,\n    description:\n      'Shared task list for complex jobs, plus delegation of tracked tasks to background subagents. Use it only when persistent coordination pays off: dependencies, parallel workstreams, or a long-running plan. Do not use it for simple requests, routine edits, straightforward command sequences, or merely because the user listed multiple steps. Actions: upsert (create and change tasks), list, get, delete (tombstone), clear (close the list), assign (delegate through assignments[]), cancel (stop a delegated run). The list is shared across the current session tree, including delegated subagents, while unrelated sessions start empty and stores persist until retention cleanup.',\n    promptGuidelines: DEFAULT_PROMPT_GUIDELINES,\n    parameters: TaskParamsSchema,\n    // Task rows carry their own status colors. Owning the shell prevents Pi's\n    // pending/success/error background fill from obscuring those row states.\n    renderShell: 'self',\n\n    async execute(_toolCallId, params, signal, onUpdate, ctx) {\n      const action = params.action as TaskAction;\n      const mutationParams = params as TaskMutationParams;\n\n      try {\n        await dependencies.waitUntilReady?.(ctx, signal);\n        rejectStrayFields(action, mutationParams);\n        if (REDUCER_ACTIONS.has(action)) {\n          const result = await executeReducerAction(store, action as ReducerAction, mutationParams, maxTasks);\n          dependencies.onChange?.();\n          return result;\n        }\n\n        if (action === 'assign') {\n          const assignments = resolveAssignments(mutationParams);\n          const first = assignments[0];\n          const progress =\n            assignments.length === 1 && first\n              ? `Delegating task #${first.id}...`\n              : `Delegating ${assignments.length} tasks...`;\n          onUpdate?.(buildTextResult(action, mutationParams, store.snapshot, progress));\n          const results = await executeAssignmentBatch(delegation, assignments, signal, dependencies.report);\n          dependencies.onChange?.();\n          const text = formatAssignmentResults(results);\n          const assigned = results.flatMap((item) => (item.ok ? [item.id] : []));\n          if (assigned.length === 0) throw actionableTaskError(action, text);\n          return buildAssignmentResult(mutationParams, store.snapshot, text, {\n            assigned,\n            failed: results.length - assigned.length,\n          });\n        }\n\n        onUpdate?.(\n          buildTextResult(\n            action,\n            mutationParams,\n            store.snapshot,\n            `Requesting cancellation for task #${mutationParams.id ?? 'unknown'}...`,\n          ),\n        );\n        const outcome = await delegation.cancel(mutationParams.id ?? Number.NaN);\n        dependencies.onChange?.();\n        if (!outcome.ok) throw actionableTaskError(action, outcome.message);\n        return buildTextResult(action, mutationParams, store.snapshot, outcome.message, undefined);\n      } catch (error) {\n        // Rethrown so pi still renders the failure to the model; recorded\n        // because this is where a store fault becomes visible to the user.\n        dependencies.report?.error(TASK_EVENT.toolFailed, error, {\n          [ACTION_ATTRIBUTE]: action,\n          ...(mutationParams.id === undefined ? {} : { [TASK_ID_ATTRIBUTE]: mutationParams.id }),\n        });\n        if (error instanceof TaskToolExecutionError) throw error;\n        throw actionableTaskError(action, error instanceof Error ? error.message : String(error));\n      }\n    },\n\n    ...renderers,\n  };\n}\n"],"mappings":"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"}