diff --git a/packages/junior-plugin-api/src/index.ts b/packages/junior-plugin-api/src/index.ts index 88578c8a6..994d1352a 100644 --- a/packages/junior-plugin-api/src/index.ts +++ b/packages/junior-plugin-api/src/index.ts @@ -88,7 +88,17 @@ export interface AgentPluginToolDefinition { executionMode?: unknown; inputSchema: unknown; prepareArguments?: (args: unknown) => unknown; + /** + * @deprecated Put tool-selection and usage guidance directly in `description` + * and parameter descriptions. Retained for compatibility; may be removed in a + * future major version. + */ promptGuidelines?: string[]; + /** + * @deprecated Put tool-selection and usage guidance directly in `description` + * and parameter descriptions. Retained for compatibility; may be removed in a + * future major version. + */ promptSnippet?: string; execute?: AgentPluginToolExecute; } diff --git a/packages/junior-scheduler/src/schedule-tools.ts b/packages/junior-scheduler/src/schedule-tools.ts index e6db9b6fe..38bdef3ed 100644 --- a/packages/junior-scheduler/src/schedule-tools.ts +++ b/packages/junior-scheduler/src/schedule-tools.ts @@ -38,19 +38,6 @@ export interface SchedulerToolContext { const TASK_ID_PREFIX = "sched"; const MAX_LISTED_TASKS = 50; const DEFAULT_SCHEDULE_TIMEZONE = "America/Los_Angeles"; -const ACTIVE_DESTINATION_GUIDELINE = - "Only manage tasks for the active Slack DM or channel; never target an existing thread, another channel, or another user's DM."; -const ACTIVE_TASK_ID_GUIDELINE = - "Use only task IDs returned from this active destination."; -const RECURRING_GUIDELINE = - "Omit recurrence for one-time requests like 'in 1 minute', 'tomorrow', or a specific date; provide recurrence only for requests that explicitly repeat."; - -const recurrenceInputSchema = Type.Union([ - Type.Literal("daily"), - Type.Literal("weekly"), - Type.Literal("monthly"), - Type.Literal("yearly"), -]); function throwToolInputError(error: string): never { throw new AgentPluginToolInputError(error); @@ -349,22 +336,11 @@ export function createSlackScheduleCreateTaskTool( ) { return tool({ description: - "Create a scheduled Junior task in the active Slack conversation.", - promptSnippet: "create future or recurring Junior work here", - promptGuidelines: [ - "Use only when the user explicitly asks Junior to do work later or on a recurring cadence.", - ACTIVE_DESTINATION_GUIDELINE, - RECURRING_GUIDELINE, - "When the user's scheduling intent is clear, create the task immediately without asking for confirmation.", - "Ask for confirmation only when the task contract, schedule, or active destination is ambiguous.", - "Recurring tasks can run at most once per day; use only daily, weekly, monthly, or yearly recurrence frequencies.", - "Provide next_run_at as an exact ISO timestamp computed from the user's requested schedule.", - "Provide recurrence only for repeating schedules.", - ], + "Create a future or recurring Junior task in the active Slack conversation. Use only when the user explicitly asks Junior to do work later or on a recurring cadence. Only manage tasks for the active Slack DM or channel; never target threads, other channels, or another user's DM. When the task, schedule, and destination are clear, create it without asking for confirmation; ask only when one of those is ambiguous.", inputSchema: Type.Object({ task: Type.String({ minLength: 1, maxLength: 4000 }), schedule: Type.String({ minLength: 1, maxLength: 300 }), - timezone: Type.Optional(Type.String({ minLength: 1, maxLength: 80 })), + timezone: Type.Optional(Type.String({ minLength: 1, maxLength: 80, description: "IANA timezone, e.g. 'America/Los_Angeles'. Defaults to the channel's configured timezone." })), next_run_at: Type.Optional( Type.String({ minLength: 1, @@ -372,7 +348,12 @@ export function createSlackScheduleCreateTaskTool( "Exact next run time as an ISO timestamp, computed from the user's requested schedule.", }), ), - recurrence: Type.Optional(recurrenceInputSchema), + recurrence: Type.Optional(Type.Union([ + Type.Literal("daily"), + Type.Literal("weekly"), + Type.Literal("monthly"), + Type.Literal("yearly"), + ], { description: "Provide only for explicitly repeating schedules; omit for one-time requests like 'in 1 minute', 'tomorrow', or a specific date. Recurring tasks run at most once per day: use daily, weekly, monthly, or yearly only." })), }), execute: async (input) => { const destination = requireActiveDestination(context); @@ -438,12 +419,7 @@ export function createSlackScheduleListTasksTool( ) { return tool({ description: - "List scheduled Junior tasks for the active Slack conversation.", - promptSnippet: "list schedules for this Slack destination", - promptGuidelines: [ - "Use when the user asks what is scheduled here or needs task IDs before editing, deleting, or running schedules.", - ACTIVE_DESTINATION_GUIDELINE, - ], + "List scheduled Junior tasks for the active Slack conversation. Use when the user asks what is scheduled here, or when task IDs are needed before editing, deleting, or running schedules. Only manages tasks for the active Slack DM or channel.", annotations: { readOnlyHint: true, destructiveHint: false }, inputSchema: Type.Object({}), execute: async () => { @@ -471,32 +447,23 @@ export function createSlackScheduleUpdateTaskTool( context: SchedulerToolContext, ) { return tool({ - description: "Edit, pause, resume, or reschedule a Junior scheduled task.", - promptSnippet: "edit/pause/resume one schedule in this Slack destination", - promptGuidelines: [ - ACTIVE_TASK_ID_GUIDELINE, - ACTIVE_DESTINATION_GUIDELINE, - RECURRING_GUIDELINE, - "Do not move scheduled tasks across conversations.", - "Provide next_run_at as an exact ISO timestamp when changing the next run.", - "Set recurrence to null when converting a recurring task to one-time.", - "Set status to active, paused, or blocked when the user asks to resume, pause, or block a task.", - ], + description: + "Edit, pause, resume, or reschedule an existing Junior scheduled task in the active Slack conversation. Use only task IDs returned for this destination. Do not move scheduled tasks across conversations.", inputSchema: Type.Object({ - task_id: Type.String({ minLength: 1 }), + task_id: Type.String({ minLength: 1, description: "ID of the task to update. Must be from this active Slack destination." }), task: Type.Optional(Type.String({ minLength: 1, maxLength: 4000 })), schedule: Type.Optional(Type.String({ minLength: 1, maxLength: 300 })), timezone: Type.Optional(Type.String({ minLength: 1, maxLength: 80 })), - next_run_at: Type.Optional(Type.String({ minLength: 1 })), + next_run_at: Type.Optional(Type.String({ minLength: 1, description: "Exact ISO timestamp when changing the next run time." })), recurrence: Type.Optional( - Type.Union([recurrenceInputSchema, Type.Null()]), + Type.Union([Type.Literal("daily"), Type.Literal("weekly"), Type.Literal("monthly"), Type.Literal("yearly"), Type.Null()], { description: "Provide only for repeating schedules. Omit for one-time requests. Set to null to convert a recurring task to one-time." }), ), status: Type.Optional( Type.Union([ Type.Literal("active"), Type.Literal("paused"), Type.Literal("blocked"), - ]), + ], { description: "Set to active, paused, or blocked to resume, pause, or block the task." }), ), }), execute: async (input) => { @@ -571,11 +538,9 @@ export function createSlackScheduleDeleteTaskTool( ) { return tool({ description: - "Delete a Junior scheduled task from the active Slack conversation.", - promptSnippet: "delete one schedule from this Slack destination", - promptGuidelines: [ACTIVE_TASK_ID_GUIDELINE, ACTIVE_DESTINATION_GUIDELINE], + "Delete one scheduled Junior task from the active Slack conversation. Use only task IDs returned for this destination. Do not delete schedules from threads, other channels, or another user's DM.", inputSchema: Type.Object({ - task_id: Type.String({ minLength: 1 }), + task_id: Type.String({ minLength: 1, description: "ID of the task to delete. Must be from this active Slack destination." }), }), execute: async ({ task_id }) => { const lookup = await getWritableTask({ context, taskId: task_id }); @@ -604,15 +569,9 @@ export function createSlackScheduleRunTaskNowTool( ) { return tool({ description: - "Queue an active Junior scheduled task to run as soon as possible.", - promptSnippet: "run one active schedule now without changing its cadence", - promptGuidelines: [ - ACTIVE_TASK_ID_GUIDELINE, - ACTIVE_DESTINATION_GUIDELINE, - "Use when the user asks to run an existing scheduled task now; do not rewrite the stored calendar cadence.", - ], + "Queue an existing active scheduled Junior task to run as soon as possible, without changing its cadence. Use when the user asks to run an existing scheduled task now. Use only task IDs returned for this destination.", inputSchema: Type.Object({ - task_id: Type.String({ minLength: 1 }), + task_id: Type.String({ minLength: 1, description: "ID of the active task to run now. Must be from this active Slack destination." }), }), execute: async ({ task_id }) => { const lookup = await getWritableTask({ context, taskId: task_id }); diff --git a/packages/junior/src/chat/tools/advisor/tool.ts b/packages/junior/src/chat/tools/advisor/tool.ts index 6d0a8c2d0..95ec72695 100644 --- a/packages/junior/src/chat/tools/advisor/tool.ts +++ b/packages/junior/src/chat/tools/advisor/tool.ts @@ -68,7 +68,7 @@ export interface AdvisorToolRuntimeContext { } const ADVISOR_TOOL_DESCRIPTION = - "Ask a stronger advisor for deep technical guidance. Call this when the task has a hard reasoning core: algorithm design, architecture, concurrency, security-sensitive logic, data modeling, unclear requirements, repeated failures, difficult debugging, broad refactors, or final review of nontrivial work. Pass a focused question plus curated context containing the exact evidence, constraints, current plan, alternatives, command output, code snippets, or diffs the advisor should start from. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, can use read-only inspection tools to verify evidence, can reason deeply, and returns guidance for you to apply and verify. Follow-up calls can build on prior advisor guidance but must include any new evidence or changed constraints. Use it after initial orientation reads when repository context matters, before committing to a non-obvious implementation plan, when changing approach, when stuck, and before declaring complex work complete. Do not use it for greetings, simple deterministic edits, routine formatting, or tasks where the next action is already obvious from fresh tool output."; + "Use this before committing to a non-obvious technical plan or declaring complex work complete. Call proactively after initial orientation when repository context matters — especially for architecture, algorithms, data modeling, concurrency, security-sensitive logic, unclear requirements, broad refactors, difficult debugging, repeated failures, or high-risk changes. Provide a focused question plus curated context: exact evidence, constraints, relevant code snippets, command output, diffs, current plan, and alternatives considered. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, and can use read-only inspection tools to verify evidence. Follow-up calls can build on prior advisor guidance but must include new evidence or changed constraints. Do not use for greetings, simple deterministic edits, routine formatting, or when the next action is obvious from fresh tool output."; const ADVISOR_SYSTEM_PROMPT = [ "You are a senior technical advisor for the executor.", diff --git a/packages/junior/src/chat/tools/definition.ts b/packages/junior/src/chat/tools/definition.ts index ff37df8e6..dbd1c046c 100644 --- a/packages/junior/src/chat/tools/definition.ts +++ b/packages/junior/src/chat/tools/definition.ts @@ -6,7 +6,17 @@ export interface ToolDefinition { description: string; inputSchema: TInputSchema; annotations?: ToolAnnotations; + /** + * @deprecated Put tool-selection and usage guidance directly in `description` + * and parameter descriptions. Retained for plugin compatibility; may be + * removed in a future major version. + */ promptSnippet?: string; + /** + * @deprecated Put tool-selection and usage guidance directly in `description` + * and parameter descriptions. Retained for plugin compatibility; may be + * removed in a future major version. + */ promptGuidelines?: string[]; prepareArguments?: (args: unknown) => Static; executionMode?: ToolExecutionMode; diff --git a/packages/junior/src/chat/tools/sandbox/edit-file.ts b/packages/junior/src/chat/tools/sandbox/edit-file.ts index fdd2b2fdf..b60a82875 100644 --- a/packages/junior/src/chat/tools/sandbox/edit-file.ts +++ b/packages/junior/src/chat/tools/sandbox/edit-file.ts @@ -108,13 +108,7 @@ const editReplacementSchema = Type.Object( export function createEditFileTool() { return tool({ description: - "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff.", - promptSnippet: "existing-file exact edits; returns diff", - promptGuidelines: [ - "prefer over writeFile for targeted changes", - "oldText exact, unique, non-overlapping", - "multiple same-file changes: one edits[] call", - ], + "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files; prefer this over writeFile for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same file: use one edits[] call.", prepareArguments: prepareEditFileArguments, executionMode: "sequential", inputSchema: Type.Object( diff --git a/packages/junior/src/chat/tools/sandbox/write-file.ts b/packages/junior/src/chat/tools/sandbox/write-file.ts index 2fe651f53..a40252f87 100644 --- a/packages/junior/src/chat/tools/sandbox/write-file.ts +++ b/packages/junior/src/chat/tools/sandbox/write-file.ts @@ -5,9 +5,7 @@ import { tool } from "@/chat/tools/definition"; export function createWriteFileTool() { return tool({ description: - "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.", - promptSnippet: "new file or deliberate full-file replacement", - promptGuidelines: ["targeted existing-file changes: editFile"], + "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.", executionMode: "sequential", inputSchema: Type.Object( { diff --git a/packages/junior/src/chat/tools/slack/canvas-tools.ts b/packages/junior/src/chat/tools/slack/canvas-tools.ts index b2eb298ea..805b79937 100644 --- a/packages/junior/src/chat/tools/slack/canvas-tools.ts +++ b/packages/junior/src/chat/tools/slack/canvas-tools.ts @@ -264,13 +264,7 @@ export function createSlackCanvasReadTool() { export function createSlackCanvasEditTool(state: ToolState) { return tool({ description: - "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff.", - promptSnippet: "existing-canvas exact edits; returns diff", - promptGuidelines: [ - "prefer over slackCanvasWrite for targeted changes", - "oldText exact, unique, non-overlapping", - "multiple same-canvas changes: one edits[] call", - ], + "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content; prefer this over slackCanvasWrite for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same canvas: use one edits[] call.", prepareArguments: prepareCanvasEditArguments, executionMode: "sequential", inputSchema: Type.Object( @@ -372,9 +366,7 @@ export function createSlackCanvasEditTool(state: ToolState) { export function createSlackCanvasWriteTool(state: ToolState) { return tool({ description: - "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation. Do not use for targeted edits.", - promptSnippet: "deliberate full-canvas replacement", - promptGuidelines: ["targeted existing-canvas changes: slackCanvasEdit"], + "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation; use slackCanvasEdit for targeted changes to existing canvas content.", executionMode: "sequential", inputSchema: Type.Object( { diff --git a/specs/advisor-tool.md b/specs/advisor-tool.md index 1ce68fb7f..62cf10bf4 100644 --- a/specs/advisor-tool.md +++ b/specs/advisor-tool.md @@ -3,7 +3,7 @@ ## Metadata - Created: 2026-05-06 -- Last Edited: 2026-05-28 +- Last Edited: 2026-05-31 ## Purpose @@ -45,7 +45,7 @@ Input: - `question`: required focused advisor question or decision point. - `context`: required curated evidence packet with the requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions the advisor should start from. -The tool description is the executor-facing trigger policy. It must say the advisor is stronger, tool-backed, does not automatically receive the parent transcript, keeps advisor history for the parent conversation, receives a read-only tool subset, and is for hard reasoning rather than routine work. +The tool description is the executor-facing trigger policy. It must say the advisor should be called proactively before committing to a non-obvious plan or declaring complex work complete — not only when stuck. It must also say the advisor is stronger, tool-backed, does not automatically receive the parent transcript, keeps advisor history for the parent conversation, receives a read-only tool subset, and is for hard reasoning rather than routine work. ## Runtime Contract