FN-7127: standardize task show tool naming
Unify task-show references so planning, triage, docs, and telemetry use fn_task_show consistently. - Replace legacy fn_task_get guidance and examples with fn_task_show across agent prompts, mission planning, triage, and docs. - Keep fn_task_get as a deprecated read-only recognition alias for historical or in-flight calls. - Update tests and changesets to cover the unified tool name and usage telemetry. Files changed: .changeset/FN-7118-shared-task-read-tools.md | 2 +- .changeset/FN-7127-unify-task-show-tool-name.md | 7 ++++++ docs/cli-reference.md | 2 +- docs/missions.md | 2 +- .../cli/skill/fusion/references/engine-tools.md | 2 +- packages/core/src/__tests__/usage-events.test.ts | 1 + packages/core/src/agent-prompts.ts | 12 +++++----- .../__tests__/milestone-slice-interview.test.ts | 2 +- .../src/__tests__/mission-interview.test.ts | 2 +- .../src/__tests__/planning-board-tools.test.ts | 6 ++--- .../dashboard/src/milestone-slice-interview.ts | 4 ++-- packages/dashboard/src/mission-interview.ts | 2 +- packages/dashboard/src/planning-board-tools.ts | 10 +++++--- packages/dashboard/src/planning.ts | 2 +- .../src/__tests__/agent-task-read-tools.test.ts | 28 +++++++++++++++------- packages/engine/src/__tests__/triage.test.ts | 6 ++--- packages/engine/src/agent-tools.ts | 5 +++- packages/engine/src/gating-classifications.ts | 8 +++++-- packages/engine/src/triage.ts | 12 ++++++---- 19 files changed, 74 insertions(+), 41 deletions(-) Fusion-Task-Id: FN-7127 Fusion-Task-Lineage: adf5a127-2741-4c88-97b3-aab239ab3960 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
summary: Permanent and custom agents can list, show, and search tasks during heartbeat runs.
|
summary: Permanent and custom agents can list, show, and search tasks during heartbeat runs.
|
||||||
category: feature
|
category: feature
|
||||||
dev: Adds shared read-only task tool factories (createTaskListTool/createTaskShowTool/createTaskSearchTool/createTaskReadTools), wires them into createSharedHeartbeatWorkTools, classifies fn_task_search/fn_task_get read-only, and adds cross-surface drift tests.
|
dev: Adds shared read-only task tool factories (createTaskListTool/createTaskShowTool/createTaskSearchTool/createTaskReadTools), wires them into createSharedHeartbeatWorkTools, classifies fn_task_search and the legacy task-get alias read-only, and adds cross-surface drift tests.
|
||||||
|
|||||||
7
.changeset/FN-7127-unify-task-show-tool-name.md
Normal file
7
.changeset/FN-7127-unify-task-show-tool-name.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: The task detail tool is now named fn_task_show consistently across triage, planning, chat, and CLI surfaces.
|
||||||
|
category: internal
|
||||||
|
dev: Renames the legacy fn_task_get registration to canonical fn_task_show in createTriageTools (engine) and createPlanningBoardTools (dashboard), updates all prompt references and the FN-7118 cross-surface drift test, and retains fn_task_get in BOTH READONLY_FN_TOOLS and COORDINATION_EXEMPT_TOOLS as a deprecated recognition alias for backward-compatible action-gate classification and analytics.
|
||||||
@@ -495,7 +495,7 @@ Use planning mode to turn a rough idea into a triage task through an interactive
|
|||||||
|
|
||||||
When supported by your configured runtime/model provider, planning sessions can also use builtin `WebSearch` and `WebFetch` tools for live context gathering.
|
When supported by your configured runtime/model provider, planning sessions can also use builtin `WebSearch` and `WebFetch` tools for live context gathering.
|
||||||
|
|
||||||
Planning sessions also have read-only board tools: `fn_task_list` (list active backlog tasks) and `fn_task_get` (read full task details, including PROMPT.md) so interviews can avoid duplicate in-flight plans and anchor questions to existing work. `fn_task_list` output is bounded and falls back to a defensive formatter if the runtime task-list clamp helper is unavailable, so board reads return text instead of failing during ambient planning or heartbeat checks. `fn_task_list` also accepts `includeDeleted: true` to surface soft-deleted blockers when diagnosing stalled dependency chains, and `fn_task_show` now auto-falls back to include soft-deleted tasks with a `[SOFT-DELETED at ...]` marker.
|
Planning sessions also have read-only board tools: `fn_task_list` (list active backlog tasks) and `fn_task_show` (read full task details, including PROMPT.md) so interviews can avoid duplicate in-flight plans and anchor questions to existing work. `fn_task_list` output is bounded and falls back to a defensive formatter if the runtime task-list clamp helper is unavailable, so board reads return text instead of failing during ambient planning or heartbeat checks. `fn_task_list` also accepts `includeDeleted: true` to surface soft-deleted blockers when diagnosing stalled dependency chains, and `fn_task_show` now auto-falls back to include soft-deleted tasks with a `[SOFT-DELETED at ...]` marker.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fn task plan [description]
|
fn task plan [description]
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ The dashboard supports mission planning workflows where you can:
|
|||||||
- Persisted missions with `interviewState: "in_progress"` remain visible as interview-styled mission cards in the main mission list so planning work does not disappear after reloads
|
- Persisted missions with `interviewState: "in_progress"` remain visible as interview-styled mission cards in the main mission list so planning work does not disappear after reloads
|
||||||
- Resume in-progress mission interview sessions directly from separate transient session rows in the main missions list (`mission_interview` sessions in `generating`, `awaiting_input`, `error`, or `complete`) before a mission record is created; `complete` means the plan summary is ready for review/approval but has not been converted into a mission yet
|
- Resume in-progress mission interview sessions directly from separate transient session rows in the main missions list (`mission_interview` sessions in `generating`, `awaiting_input`, `error`, or `complete`) before a mission record is created; `complete` means the plan summary is ready for review/approval but has not been converted into a mission yet
|
||||||
- Mission interview closes are non-destructive: the modal now uses a single close action for header close, backdrop click, and Escape. Closing preserves the in-progress `mission_interview` session, and Missions re-fetches project-scoped transient rows (including on the mobile stacked Missions view) so resume/retry remains discoverable without losing persisted `interviewState: "in_progress"` mission cards. Resume-launched interview modals also expose a **Send to background** action that performs the same non-destructive park without cancelling the session. Deletion remains an explicit sidebar action.
|
- Mission interview closes are non-destructive: the modal now uses a single close action for header close, backdrop click, and Escape. Closing preserves the in-progress `mission_interview` session, and Missions re-fetches project-scoped transient rows (including on the mobile stacked Missions view) so resume/retry remains discoverable without losing persisted `interviewState: "in_progress"` mission cards. Resume-launched interview modals also expose a **Send to background** action that performs the same non-destructive park without cancelling the session. Deletion remains an explicit sidebar action.
|
||||||
- Mission interview, milestone interview, and slice interview agents have read-only board visibility via `fn_task_list` and `fn_task_get`, so they can reference active backlog context and avoid duplicating in-flight tasks while asking planning questions
|
- Mission interview, milestone interview, and slice interview agents have read-only board visibility via `fn_task_list` and `fn_task_show`, so they can reference active backlog context and avoid duplicating in-flight tasks while asking planning questions
|
||||||
|
|
||||||
### Mission Interview Drafts
|
### Mission Interview Drafts
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ These tools are **not** part of the user-invokable extension surface. They are i
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `fn_task_list` | List active tasks during specification (duplicate check, discovery) | none |
|
| `fn_task_list` | List active tasks during specification (duplicate check, discovery) | none |
|
||||||
| `fn_task_search` | Keyword search tasks (including done/archived by default) for duplicate detection | `query` (string), `limit?` (number), `includeDone?` (boolean), `includeArchived?` (boolean) |
|
| `fn_task_search` | Keyword search tasks (including done/archived by default) for duplicate detection | `query` (string), `limit?` (number), `includeDone?` (boolean), `includeArchived?` (boolean) |
|
||||||
| `fn_task_get` | Fetch full task detail including PROMPT.md | `id` (string) |
|
| `fn_task_show` | Fetch full task detail including PROMPT.md | `id` (string) |
|
||||||
| `fn_review_spec` | Spawn spec reviewer and return `APPROVE`/`REVISE`/`RETHINK`/`UNAVAILABLE` | none |
|
| `fn_review_spec` | Spawn spec reviewer and return `APPROVE`/`REVISE`/`RETHINK`/`UNAVAILABLE` | none |
|
||||||
|
|
||||||
## Executor-only runtime tools (`executor.ts`)
|
## Executor-only runtime tools (`executor.ts`)
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ describe("usage_events", () => {
|
|||||||
["semantic_search", "read"],
|
["semantic_search", "read"],
|
||||||
["fn_task_list", "read"],
|
["fn_task_list", "read"],
|
||||||
["fn_task_show", "read"],
|
["fn_task_show", "read"],
|
||||||
|
// FNXC:UsageAnalytics 2026-06-27-00:00: Keep legacy `fn_task_get` categorized as read-only so historical usage events remain comparable after live surfaces move to `fn_task_show`.
|
||||||
["fn_task_get", "read"],
|
["fn_task_get", "read"],
|
||||||
["fn_task_search", "read"],
|
["fn_task_search", "read"],
|
||||||
["fn_list_agents", "read"],
|
["fn_list_agents", "read"],
|
||||||
|
|||||||
@@ -372,12 +372,12 @@ If this task REMOVES existing functionality (deleting modules, settings, API end
|
|||||||
|
|
||||||
## Duplicate check
|
## Duplicate check
|
||||||
Before writing a spec, call \`fn_task_list\` to find existing active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
Before writing a spec, call \`fn_task_list\` to find existing active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
||||||
For any likely match in \`done\` or \`archived\`, call \`fn_task_get\` to inspect details before deciding.
|
For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` to inspect details before deciding.
|
||||||
If an existing task already covers the same work, do NOT write a PROMPT.md. Instead write exactly:
|
If an existing task already covers the same work, do NOT write a PROMPT.md. Instead write exactly:
|
||||||
\`DUPLICATE: {existing-task-id}\`
|
\`DUPLICATE: {existing-task-id}\`
|
||||||
|
|
||||||
## Dependency awareness
|
## Dependency awareness
|
||||||
When adding a dependency in \`## Dependencies\`, first call \`fn_task_get\` for that task and read its PROMPT.md.
|
When adding a dependency in \`## Dependencies\`, first call \`fn_task_show\` for that task and read its PROMPT.md.
|
||||||
Use that context to align file paths, APIs, assumptions, and completion expectations. If the dependency has no PROMPT.md yet, note that explicitly.
|
Use that context to align file paths, APIs, assumptions, and completion expectations. If the dependency has no PROMPT.md yet, note that explicitly.
|
||||||
|
|
||||||
## Decision-only task flag (noCommitsExpected)
|
## Decision-only task flag (noCommitsExpected)
|
||||||
@@ -610,13 +610,13 @@ tests. Manual verification is NOT a test.
|
|||||||
|
|
||||||
## Duplicate check
|
## Duplicate check
|
||||||
Before writing a spec, first call \`fn_task_list\` to see active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
Before writing a spec, first call \`fn_task_list\` to see active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
||||||
For any likely match in \`done\` or \`archived\`, call \`fn_task_get\` to inspect details before deciding.
|
For any likely match in \`done\` or \`archived\`, call \`fn_task_show\` to inspect details before deciding.
|
||||||
If a task already covers the same work (even if worded differently), do NOT
|
If a task already covers the same work (even if worded differently), do NOT
|
||||||
write a PROMPT.md. Instead, write a single line to the output file:
|
write a PROMPT.md. Instead, write a single line to the output file:
|
||||||
\`DUPLICATE: {existing-task-id}\`
|
\`DUPLICATE: {existing-task-id}\`
|
||||||
|
|
||||||
## Dependency awareness
|
## Dependency awareness
|
||||||
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_get\` on that task ID to read its PROMPT.md.
|
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_show\` on that task ID to read its PROMPT.md.
|
||||||
Use what you learn — file scope, APIs, patterns, completion criteria — to make the new spec accurate: reference the right paths, avoid conflicting assumptions, and describe what the dependency must deliver before this task starts.
|
Use what you learn — file scope, APIs, patterns, completion criteria — to make the new spec accurate: reference the right paths, avoid conflicting assumptions, and describe what the dependency must deliver before this task starts.
|
||||||
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
|
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
|
||||||
|
|
||||||
@@ -656,7 +656,7 @@ For tasks you assess as Size M or L, consider whether splitting into 2-5 child t
|
|||||||
You have these extra tools during triage:
|
You have these extra tools during triage:
|
||||||
- \`fn_task_list\` — list existing active tasks
|
- \`fn_task_list\` — list existing active tasks
|
||||||
- \`fn_task_search\` — keyword search across tasks, including done and archived tasks
|
- \`fn_task_search\` — keyword search across tasks, including done and archived tasks
|
||||||
- \`fn_task_get\` — inspect a task and its PROMPT.md
|
- \`fn_task_show\` — inspect a task and its PROMPT.md
|
||||||
- \`fn_task_create\` — create a child/follow-up task while triaging
|
- \`fn_task_create\` — create a child/follow-up task while triaging
|
||||||
- \`fn_task_document_write\` — save a planning document (e.g., key="plan")
|
- \`fn_task_document_write\` — save a planning document (e.g., key="plan")
|
||||||
- \`fn_task_document_read\` — read back a previously saved document
|
- \`fn_task_document_read\` — read back a previously saved document
|
||||||
@@ -744,7 +744,7 @@ If the task targets a different task ID (audit, forensic walk, historical reconc
|
|||||||
- Authoritative target-task artifacts live at the **project root**: \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, agent logs).
|
- Authoritative target-task artifacts live at the **project root**: \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, agent logs).
|
||||||
- Authoritative task DB rows live at the **project root** SQLite file: \`<rootDir>/.fusion/fusion.db\` (WAL mode). Read via \`TaskStore\` APIs; do not instruct direct SQL surgery.
|
- Authoritative task DB rows live at the **project root** SQLite file: \`<rootDir>/.fusion/fusion.db\` (WAL mode). Read via \`TaskStore\` APIs; do not instruct direct SQL surgery.
|
||||||
- \`.fusion/\` is gitignored, so a fresh worktree from \`main\` does **not** include \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`. The running worktree's own \`.fusion/\` (if present) is scratch/session state for the running task only, not source of truth.
|
- \`.fusion/\` is gitignored, so a fresh worktree from \`main\` does **not** include \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`. The running worktree's own \`.fusion/\` (if present) is scratch/session state for the running task only, not source of truth.
|
||||||
- Prefer \`fn_task_get\` / \`fn_task_list\` when the target task ID is known; fall back to project-root filesystem reads only when tools cannot provide needed evidence.
|
- Prefer \`fn_task_show\` / \`fn_task_list\` when the target task ID is known; fall back to project-root filesystem reads only when tools cannot provide needed evidence.
|
||||||
|
|
||||||
<!-- Frontend UX criteria are applied deterministically by packages/core/src/frontend-ux-policy.ts and mirror the "frontend-ux-design" reviewer persona in packages/core/src/types.ts. -->`;;
|
<!-- Frontend UX criteria are applied deterministically by packages/core/src/frontend-ux-policy.ts and mirror the "frontend-ux-design" reviewer persona in packages/core/src/types.ts. -->`;;
|
||||||
|
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ describe("milestone-slice-interview module", () => {
|
|||||||
const createFnAgentCallArg = await waitForCreateFnAgentOptions() as { customTools?: Array<{ name: string }> };
|
const createFnAgentCallArg = await waitForCreateFnAgentOptions() as { customTools?: Array<{ name: string }> };
|
||||||
const customToolNames = createFnAgentCallArg.customTools?.map((tool) => tool.name) ?? [];
|
const customToolNames = createFnAgentCallArg.customTools?.map((tool) => tool.name) ?? [];
|
||||||
expect(customToolNames).toContain("fn_task_list");
|
expect(customToolNames).toContain("fn_task_list");
|
||||||
expect(customToolNames).toContain("fn_task_get");
|
expect(customToolNames).toContain("fn_task_show");
|
||||||
expect(session?.targetId).toBe("ms-123");
|
expect(session?.targetId).toBe("ms-123");
|
||||||
expect(session?.targetTitle).toBe("Launch Platform");
|
expect(session?.targetTitle).toBe("Launch Platform");
|
||||||
expect(session?.missionContext).toBe("Mission: Launch Platform v2");
|
expect(session?.missionContext).toBe("Mission: Launch Platform v2");
|
||||||
|
|||||||
@@ -912,7 +912,7 @@ describe("mission-interview module", () => {
|
|||||||
expect(lastCall[0].builtinToolsAllowlist).toEqual(["WebSearch", "WebFetch"]);
|
expect(lastCall[0].builtinToolsAllowlist).toEqual(["WebSearch", "WebFetch"]);
|
||||||
const customToolNames = (lastCall[0].customTools as Array<{ name: string }> | undefined)?.map((tool) => tool.name) ?? [];
|
const customToolNames = (lastCall[0].customTools as Array<{ name: string }> | undefined)?.map((tool) => tool.name) ?? [];
|
||||||
expect(customToolNames).toContain("fn_task_list");
|
expect(customToolNames).toContain("fn_task_list");
|
||||||
expect(customToolNames).toContain("fn_task_get");
|
expect(customToolNames).toContain("fn_task_show");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses override prompt when promptOverrides provided", async () => {
|
it("uses override prompt when promptOverrides provided", async () => {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ describe("createPlanningBoardTools", () => {
|
|||||||
expect(emptyResult.content[0]?.text).toBe("No active tasks.");
|
expect(emptyResult.content[0]?.text).toBe("No active tasks.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fn_task_get returns full details and not-found fallback", async () => {
|
it("fn_task_show returns full details and not-found fallback", async () => {
|
||||||
const store = createStoreMock({
|
const store = createStoreMock({
|
||||||
getTask: vi.fn(async (id: string) => ({
|
getTask: vi.fn(async (id: string) => ({
|
||||||
id,
|
id,
|
||||||
@@ -84,7 +84,7 @@ describe("createPlanningBoardTools", () => {
|
|||||||
})) as TaskStore["getTask"],
|
})) as TaskStore["getTask"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const taskGet = createPlanningBoardTools(store).find((tool) => tool.name === "fn_task_get");
|
const taskGet = createPlanningBoardTools(store).find((tool) => tool.name === "fn_task_show");
|
||||||
expect(taskGet).toBeDefined();
|
expect(taskGet).toBeDefined();
|
||||||
const result = await taskGet!.execute("c3", { id: "FN-10" });
|
const result = await taskGet!.execute("c3", { id: "FN-10" });
|
||||||
expect(result.content[0]?.text).toContain("ID: FN-10");
|
expect(result.content[0]?.text).toContain("ID: FN-10");
|
||||||
@@ -96,7 +96,7 @@ describe("createPlanningBoardTools", () => {
|
|||||||
|
|
||||||
const notFoundStore = createStoreMock();
|
const notFoundStore = createStoreMock();
|
||||||
const missingResult = await createPlanningBoardTools(notFoundStore)
|
const missingResult = await createPlanningBoardTools(notFoundStore)
|
||||||
.find((tool) => tool.name === "fn_task_get")!
|
.find((tool) => tool.name === "fn_task_show")!
|
||||||
.execute("c4", { id: "FN-404" });
|
.execute("c4", { id: "FN-404" });
|
||||||
expect(missingResult.content[0]?.text).toBe("Task FN-404 not found.");
|
expect(missingResult.content[0]?.text).toBe("Task FN-404 not found.");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ A milestone represents a major phase or deliverable within a larger mission. Eac
|
|||||||
|
|
||||||
## Board tools
|
## Board tools
|
||||||
- fn_task_list — list active tasks
|
- fn_task_list — list active tasks
|
||||||
- fn_task_get — read a task's full details and PROMPT.md
|
- fn_task_show — read a task's full details and PROMPT.md
|
||||||
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
@@ -220,7 +220,7 @@ A slice represents a focused work unit within a milestone that can be activated
|
|||||||
|
|
||||||
## Board tools
|
## Board tools
|
||||||
- fn_task_list — list active tasks
|
- fn_task_list — list active tasks
|
||||||
- fn_task_get — read a task's full details and PROMPT.md
|
- fn_task_show — read a task's full details and PROMPT.md
|
||||||
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ PREFER structured question types over free-text. This makes the interview faster
|
|||||||
|
|
||||||
## Board tools
|
## Board tools
|
||||||
- fn_task_list — list active tasks
|
- fn_task_list — list active tasks
|
||||||
- fn_task_get — read a task's full details and PROMPT.md
|
- fn_task_show — read a task's full details and PROMPT.md
|
||||||
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
|
|||||||
@@ -83,8 +83,12 @@ export function createPlanningBoardTools(store: TaskStore): ToolDefinition[] {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskGet: ToolDefinition = {
|
/**
|
||||||
name: "fn_task_get",
|
* FNXC:AgentTooling 2026-06-27-00:00:
|
||||||
|
* Planning-board interviews must expose the task detail read tool as canonical `fn_task_show`, matching prompt text and the FN-7118 shared read-tool factory so every agent surface learns one model-visible show-tool name.
|
||||||
|
*/
|
||||||
|
const taskShow: ToolDefinition = {
|
||||||
|
name: "fn_task_show",
|
||||||
label: "Get Task",
|
label: "Get Task",
|
||||||
description:
|
description:
|
||||||
"Get full details of a specific task including its PROMPT.md content. " +
|
"Get full details of a specific task including its PROMPT.md content. " +
|
||||||
@@ -115,5 +119,5 @@ export function createPlanningBoardTools(store: TaskStore): ToolDefinition[] {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return [taskList, taskGet];
|
return [taskList, taskShow];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ When ready to complete, generate:
|
|||||||
|
|
||||||
## Board tools
|
## Board tools
|
||||||
- fn_task_list — list active tasks
|
- fn_task_list — list active tasks
|
||||||
- fn_task_get — read a task's full details and PROMPT.md
|
- fn_task_show — read a task's full details and PROMPT.md
|
||||||
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
Use these to avoid duplicating an existing in-flight plan and to anchor your questions against current backlog context.
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
|
|||||||
@@ -140,21 +140,31 @@ describe("shared task read tools", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("pins per-surface task-read tool name parity", () => {
|
it("pins per-surface task-read tool name parity on canonical fn_task_show", () => {
|
||||||
const triageProcessor = new TriageProcessor(createStore() as never, "/tmp/fn-test");
|
const triageProcessor = new TriageProcessor(createStore() as never, "/tmp/fn-test");
|
||||||
const triageNames = toolNames((triageProcessor as unknown as { createTriageTools: (opts: unknown) => Array<{ name: string }> }).createTriageTools({
|
const triageNames = toolNames((triageProcessor as unknown as { createTriageTools: (opts: unknown) => Array<{ name: string }> }).createTriageTools({
|
||||||
parentTaskId: "FN-TRIAGE",
|
parentTaskId: "FN-TRIAGE",
|
||||||
allowTaskCreate: true,
|
allowTaskCreate: true,
|
||||||
createdSubtasksRef: { current: [] },
|
createdSubtasksRef: { current: [] },
|
||||||
})).filter((name) => name.startsWith("fn_task_") && name !== "fn_task_create");
|
})).filter((name) => name.startsWith("fn_task_") && name !== "fn_task_create");
|
||||||
expect(triageNames).toEqual(["fn_task_list", "fn_task_search", "fn_task_get"]);
|
|
||||||
|
|
||||||
expect(toolNames(createPlanningBoardTools(createStore())).filter((name) => name.startsWith("fn_task_"))).toEqual([
|
const surfaces = {
|
||||||
"fn_task_list",
|
triage: triageNames,
|
||||||
"fn_task_get",
|
planningBoard: toolNames(createPlanningBoardTools(createStore())).filter((name) => name.startsWith("fn_task_")),
|
||||||
]);
|
cliExtension: extractRegisteredCliTaskReadNames(),
|
||||||
|
sharedFactory: toolNames(createTaskReadTools(createStore())),
|
||||||
|
};
|
||||||
|
|
||||||
expect(extractRegisteredCliTaskReadNames()).toEqual(["fn_task_list", "fn_task_show"]);
|
expect(surfaces).toEqual({
|
||||||
expect(toolNames(createTaskReadTools(createStore()))).toEqual(["fn_task_list", "fn_task_show", "fn_task_search"]);
|
triage: ["fn_task_list", "fn_task_search", "fn_task_show"],
|
||||||
|
planningBoard: ["fn_task_list", "fn_task_show"],
|
||||||
|
cliExtension: ["fn_task_list", "fn_task_show"],
|
||||||
|
sharedFactory: ["fn_task_list", "fn_task_show", "fn_task_search"],
|
||||||
|
});
|
||||||
|
const deprecatedGetName = ["fn_task", "get"].join("_");
|
||||||
|
for (const names of Object.values(surfaces)) {
|
||||||
|
expect(names).toContain("fn_task_show");
|
||||||
|
expect(names).not.toContain(deprecatedGetName);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2286,7 +2286,7 @@ describe("taskCreate tool model inheritance", () => {
|
|||||||
expect(toolNames).toContain("fn_task_create");
|
expect(toolNames).toContain("fn_task_create");
|
||||||
expect(toolNames).toContain("fn_task_list");
|
expect(toolNames).toContain("fn_task_list");
|
||||||
expect(toolNames).toContain("fn_task_search");
|
expect(toolNames).toContain("fn_task_search");
|
||||||
expect(toolNames).toContain("fn_task_get");
|
expect(toolNames).toContain("fn_task_show");
|
||||||
expect(tools).toHaveLength(4);
|
expect(tools).toHaveLength(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4494,7 +4494,7 @@ describe("TriageProcessor delegation tools", () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
it("createTriageTools returns fn_task_list, fn_task_search, fn_task_get, fn_task_create (no delegation tools — those are in customTools)", () => {
|
it("createTriageTools returns fn_task_list, fn_task_search, fn_task_show, fn_task_create (no delegation tools — those are in customTools)", () => {
|
||||||
const store = createMockStore();
|
const store = createMockStore();
|
||||||
const processor = new TriageProcessor(store as any, "/tmp/root");
|
const processor = new TriageProcessor(store as any, "/tmp/root");
|
||||||
|
|
||||||
@@ -4507,7 +4507,7 @@ describe("TriageProcessor delegation tools", () => {
|
|||||||
const toolNames = tools.map((t: any) => t.name);
|
const toolNames = tools.map((t: any) => t.name);
|
||||||
expect(toolNames).toContain("fn_task_list");
|
expect(toolNames).toContain("fn_task_list");
|
||||||
expect(toolNames).toContain("fn_task_search");
|
expect(toolNames).toContain("fn_task_search");
|
||||||
expect(toolNames).toContain("fn_task_get");
|
expect(toolNames).toContain("fn_task_show");
|
||||||
expect(toolNames).toContain("fn_task_create");
|
expect(toolNames).toContain("fn_task_create");
|
||||||
// fn_list_agents and fn_delegate_task are added in customTools, not createTriageTools
|
// fn_list_agents and fn_delegate_task are added in customTools, not createTriageTools
|
||||||
expect(toolNames).not.toContain("fn_list_agents");
|
expect(toolNames).not.toContain("fn_list_agents");
|
||||||
|
|||||||
@@ -1044,7 +1044,10 @@ function formatTaskSummaryLine(task: { id: string; column: string; title?: strin
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FNXC:AgentTooling 2026-06-27-14:05:
|
* FNXC:AgentTooling 2026-06-27-14:05:
|
||||||
* Shared read-only task discovery factories must return host-safe text and be reusable by triage, chat/planning, and heartbeat surfaces. Heartbeat agents now receive task read tools through this single store-backed implementation instead of bespoke copies, while model-visible legacy `fn_task_get` surfaces remain separately pinned by drift tests.
|
* Shared read-only task discovery factories must return host-safe text and be reusable by triage, chat/planning, and heartbeat surfaces. Heartbeat agents now receive task read tools through this single store-backed implementation instead of bespoke copies.
|
||||||
|
*
|
||||||
|
* FNXC:AgentTooling 2026-06-27-00:00:
|
||||||
|
* Triage and planning-board surfaces now use canonical `fn_task_show`; deprecated `fn_task_get` survives only as a recognition alias in action-gate and analytics compatibility paths, not as a model-visible registered tool.
|
||||||
*/
|
*/
|
||||||
export function createTaskListTool(store: TaskStore): ToolDefinition {
|
export function createTaskListTool(store: TaskStore): ToolDefinition {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -118,8 +118,9 @@ export const READONLY_FN_TOOLS: ReadonlySet<string> = new Set([
|
|||||||
"fn_artifact_view",
|
"fn_artifact_view",
|
||||||
"fn_task_list",
|
"fn_task_list",
|
||||||
"fn_task_show",
|
"fn_task_show",
|
||||||
// FNXC:ToolGovernance 2026-06-27-14:16: Task search and legacy task-get surfaces are read-only duplicate-discovery tools; classify them positively so heartbeat/triage calls never rely on the unknown-tool exempt fallback.
|
// FNXC:ToolGovernance 2026-06-27-14:16: Task search is a read-only duplicate-discovery tool; classify it positively so heartbeat/triage calls never rely on the unknown-tool exempt fallback.
|
||||||
"fn_task_search",
|
"fn_task_search",
|
||||||
|
// FNXC:ToolGovernance 2026-06-27-00:00: `fn_task_get` is a deprecated recognition-only alias. It is no longer registered as a live tool, but historical/in-flight calls must still classify as read-only instead of falling through to unknown-tool handling.
|
||||||
"fn_task_get",
|
"fn_task_get",
|
||||||
"fn_task_create",
|
"fn_task_create",
|
||||||
"fn_task_document_write",
|
"fn_task_document_write",
|
||||||
@@ -172,7 +173,10 @@ export const COORDINATION_EXEMPT_TOOLS = [
|
|||||||
"fn_task_document_read",
|
"fn_task_document_read",
|
||||||
/**
|
/**
|
||||||
* FNXC:ToolGovernance 2026-06-27-15:22:
|
* FNXC:ToolGovernance 2026-06-27-15:22:
|
||||||
* Task list/show/search/get are read-only discovery tools. Put them on the action-gate exempt registry, not only READONLY_FN_TOOLS, because evaluateAgentActionGate recognizes coordination exemptions directly and otherwise unknown fn_task_* reads silently fall through to exempt allow.
|
* Task list/show/search tools are read-only discovery tools. Put them on the action-gate exempt registry, not only READONLY_FN_TOOLS, because evaluateAgentActionGate recognizes coordination exemptions directly and otherwise unknown fn_task_* reads silently fall through to exempt allow.
|
||||||
|
*
|
||||||
|
* FNXC:ToolGovernance 2026-06-27-00:00:
|
||||||
|
* `fn_task_get` is no longer registered as a live tool, but it remains here as a deprecated recognition-only alias so stray in-flight legacy calls classify as known read-only coordination instead of relying on the unknown-tool fallback.
|
||||||
*/
|
*/
|
||||||
"fn_task_list",
|
"fn_task_list",
|
||||||
"fn_task_show",
|
"fn_task_show",
|
||||||
|
|||||||
@@ -1575,8 +1575,12 @@ export class TriageProcessor {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskGet: ToolDefinition = {
|
/**
|
||||||
name: "fn_task_get",
|
* FNXC:AgentTooling 2026-06-27-00:00:
|
||||||
|
* Triage must expose the task detail read tool as canonical `fn_task_show`, matching prompt text and the FN-7118 shared read-tool factory so every agent surface learns one model-visible show-tool name.
|
||||||
|
*/
|
||||||
|
const taskShow: ToolDefinition = {
|
||||||
|
name: "fn_task_show",
|
||||||
label: "Get Task",
|
label: "Get Task",
|
||||||
description:
|
description:
|
||||||
"Get full details of a specific task including its PROMPT.md content. " +
|
"Get full details of a specific task including its PROMPT.md content. " +
|
||||||
@@ -1605,7 +1609,7 @@ export class TriageProcessor {
|
|||||||
};
|
};
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
planLog.warn(`${options.parentTaskId}: fn_task_get lookup failed for ${params.id}: ${msg}`);
|
planLog.warn(`${options.parentTaskId}: fn_task_show lookup failed for ${params.id}: ${msg}`);
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{ type: "text" as const, text: `Task ${params.id} not found.` },
|
{ type: "text" as const, text: `Task ${params.id} not found.` },
|
||||||
@@ -1743,7 +1747,7 @@ export class TriageProcessor {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return [taskList, taskSearch, taskGet, taskCreate];
|
return [taskList, taskSearch, taskShow, taskCreate];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user