FN-7138: show task column context in agent assignments
Agent Current Task output now includes linked task column context to distinguish active execution from parked or stale links. - Add shared Current Task formatting for active, terminal, and unresolved task links. - Show linked task columns in engine and CLI agent list/show surfaces. - Cover parked, active, terminal, and missing task-link display cases with tests. - Document the durable agent task-link invariant and release the CLI fix. Files changed: .changeset/fn-7138-agent-current-task-context.md | 7 ++++ docs/agents.md | 1 + docs/architecture.md | 2 +- packages/cli/src/__tests__/extension.test.ts | 44 ++++++++++++++++++++++ packages/cli/src/extension.ts | 35 +++++++++++++++-- packages/core/src/agent-store.ts | 30 +++++++++++++++ packages/core/src/index.ts | 2 +- .../src/__tests__/agent-tools-delegation.test.ts | 44 +++++++++++++++++++++- packages/engine/src/agent-tools.ts | 15 ++++++-- 9 files changed, 169 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-7138 Fusion-Task-Lineage: 0ea97b4c-d4f8-4289-a5b6-e5c51b47b768 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7138-agent-current-task-context.md
Normal file
7
.changeset/fn-7138-agent-current-task-context.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show linked task columns in agent Current Task output.
|
||||
category: fix
|
||||
dev: Adds shared Current Task formatting for agent list/show tools across engine and CLI surfaces.
|
||||
@@ -35,6 +35,7 @@ fn chat <agent-id> [message…] [--once] [--non-interactive] [--poll-ms <n>]
|
||||
- Agent workflow-routing tools follow an intent boundary: agents may select or change a task workflow only when the user explicitly requested that workflow or when the agent created the task. Executors must not call `fn_workflow_select` to reroute the task they are executing unless the task instructions or a user steering comment explicitly asks for the workflow change.
|
||||
- Executor, heartbeat, and dashboard chat sessions expose artifact registry tools so agents can publish and inspect multi-type deliverables without relying on the dashboard gallery. Planning sessions intentionally exclude artifact tools until they can thread the existing `MessageStore` dependency.
|
||||
- Permanent/custom heartbeat agents receive the broad coordination and work-discovery tool surface instead of a narrowly curated subset: read-only task discovery (`fn_task_list`, `fn_task_show`, `fn_task_search`) for work discovery and duplicate avoidance, workflow discovery and authoring (`fn_workflow_list`, `fn_workflow_get`, `fn_workflow_create`, `fn_workflow_update`, `fn_workflow_delete`, `fn_workflow_settings`, `fn_trait_list`), governed research (`fn_research_run`, `fn_research_list`, `fn_research_get`, `fn_research_cancel`), structured clarification (`fn_ask_question`), artifact, memory, messaging, goal, evaluation, identity, and delegation tools. Task-scoped heartbeat sessions also expose current-task workflow selection and promotion (`fn_workflow_select`, `fn_task_promote`); no-task heartbeats omit those because they have no ambient task. Executor-only worktree/workspace tools such as `fn_run_verification` and `fn_acquire_repo_worktree` remain out of the ambient heartbeat lane until that lane owns the required worktree/workspace context. The task read tools are store-backed, text-only, and action-gate-recognized as read-only; dangerous actions are controlled at invocation time by each agent's `AgentPermissionPolicy` through the action gate (allow / require approval / block), not by withholding governed tools from the session.
|
||||
- `agent.taskId` is an active-execution linkage, not durable ownership. It may legitimately point at a `todo`/`triage` task only while the agent has live run or executor-active proof; task-move sync and self-healing clear stale parked, terminal, or unresolved links otherwise. `fn_list_agents` and `fn_agent_show` therefore include column context in the human-readable `Current Task` line, such as `(triage)`, `(in-progress)`, `(not active — done)`, or `(unresolved)`, so coordinators can distinguish transient planning ownership from drift.
|
||||
|
||||
### Artifact registry tools
|
||||
|
||||
|
||||
@@ -683,7 +683,7 @@ Runtime action-gate flow (v1):
|
||||
- `recoverGhostReviewTasks()` is a fallback only for idle, non-terminal `in-review` states. Terminal/actionable states (notably `status: "failed"`) are preserved and **not** auto-kicked back to `todo`.
|
||||
- `recoverPausedAbortFailures()` clears executor pause/resume abort parks only when the durable row is safe to recover. `todo`/`in-progress` rows are requeued for normal scheduling, while clean `in-review` rows (completed steps, not paused/user-paused/executing, auto-merge eligible, no confirmed or terminal merge evidence) have `status`/`error` cleared in place so review progression can continue. User hard-cancel, global/user pause, `autoMerge:false`, terminal merge, and live-execution guards remain operator-actionable. Successful recovery emits `task:auto-recover-paused-abort-park` with `preservedInReview` metadata.
|
||||
- `reattach-orphaned-assigned-executions` is a forward-resume safety net for durable-agent assignments. During startup recovery and periodic maintenance, after orphaned-agent and stale-heartbeat-run repairs, self-healing finds `in-progress` tasks with an `assignedAgentId` whose agent has no active heartbeat run and no active executor session after the orphan grace window. It re-dispatches in place via `executor.resumeTaskForAgent(agentId)` (the same seam used by clean `HeartbeatMonitor.onRunCompleted` and guarded by executor double-execution checks), emits `task:reattach-orphaned-execution`, and never moves the task backward. This complements engine-start `executor.resumeOrphaned()` and leaves unassigned/role-based execution recovery to the existing startup/limbo/stuck-task paths.
|
||||
- Durable `Agent.taskId` is a running assignment for parked `todo`/`triage` task rows only when the agent has live proof: a fresh active heartbeat run or an executor-active/tracked heartbeat signal. Scheduler overlap requeues, task move sync, self-healing, and Reports Health Check share this invariant: stale durable links are cleared or rendered as stale while `status: "queued"` and `overlapBlockedBy` remain on the task row so file-scope lease blocking is not weakened.
|
||||
- Durable `Agent.taskId` is a running assignment for parked `todo`/`triage` task rows only when the agent has live proof: a fresh active heartbeat run or an executor-active/tracked heartbeat signal. Scheduler overlap requeues, task move sync, self-healing, and Reports Health Check share this invariant: stale durable links are cleared or rendered as stale while `status: "queued"` and `overlapBlockedBy` remain on the task row so file-scope lease blocking is not weakened. `fn_list_agents` and `fn_agent_show` render the linked task column next to `Current Task` (for example `Current Task: FN-1234 (triage)` or `Current Task: FN-1234 (not active — done)`) so parked-column planning ownership is not misread as in-progress execution drift.
|
||||
- Mission validation has a dedicated stale-run reaper: startup recovery and Batch 2 maintenance call `reapStaleMissionValidatorRuns()` when wired by the runtime, using `VALIDATOR_RUN_STALE_MAX_AGE_MS` (currently 6 hours). The sweep terminates ownerless `mission_validator_runs.status='running'` rows as `error`, writes the reap reason into `summary`, leaves `lastValidatorRunId` pointing at the now-terminal run, and emits run-audit telemetry with `mutationType: "mission:validator-run-reaped"` plus `runId`/`featureId`/`missionId`/`triggerType`/`elapsedMs` metadata. Active mission features move to `loopState="needs_fix"` + `lastValidatorStatus="error"` unless their parent mission is already `complete`/`archived`.
|
||||
|
||||
#### Stuck-loop exhaustion terminal contract
|
||||
|
||||
@@ -3904,6 +3904,34 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
|
||||
expect(result.details.agents.every((a: any) => a.id !== ephemeralId)).toBe(true);
|
||||
});
|
||||
|
||||
it("shows current task column context for parked, active, terminal, and missing links", async () => {
|
||||
const store = createStore();
|
||||
await store.init();
|
||||
const triageTask = await store.createTask({ description: "Planning link", column: "triage" });
|
||||
const activeTask = await store.createTask({ description: "Active link", column: "in-progress" });
|
||||
const doneTask = await store.createTask({ description: "Done link", column: "done" });
|
||||
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
|
||||
await agentStore.init();
|
||||
const triageAgent = await agentStore.createAgent({ name: "triage-linked", role: "executor", metadata: {} });
|
||||
const activeAgent = await agentStore.createAgent({ name: "active-linked", role: "executor", metadata: {} });
|
||||
const doneAgent = await agentStore.createAgent({ name: "done-linked", role: "executor", metadata: {} });
|
||||
const missingAgent = await agentStore.createAgent({ name: "missing-linked", role: "executor", metadata: {} });
|
||||
await agentStore.syncExecutionTaskLink(triageAgent.id, triageTask.id);
|
||||
await agentStore.syncExecutionTaskLink(activeAgent.id, activeTask.id);
|
||||
await agentStore.syncExecutionTaskLink(doneAgent.id, doneTask.id);
|
||||
await agentStore.syncExecutionTaskLink(missingAgent.id, "FN-404404");
|
||||
|
||||
const tool = api.tools.get("fn_list_agents")!;
|
||||
const result = await tool.execute("la-current-task-context", {}, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
const text = result.content[0].text;
|
||||
expect(text).toContain(`Current Task: ${triageTask.id} (triage)`);
|
||||
expect(text).toContain(`Current Task: ${activeTask.id} (in-progress)`);
|
||||
expect(text).toContain(`Current Task: ${doneTask.id} (not active — done)`);
|
||||
expect(text).toContain("Current Task: FN-404404 (unresolved)");
|
||||
expect(text).not.toMatch(new RegExp(`Current Task: ${triageTask.id}(?! \\()`));
|
||||
});
|
||||
|
||||
it("returns empty list message when no agents", async () => {
|
||||
const tool = api.tools.get("fn_list_agents")!;
|
||||
const result = await tool.execute("la-5", {}, undefined, undefined, makeCtx(tmpDir));
|
||||
@@ -4219,6 +4247,22 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
|
||||
expect(result.details.agent.name).toBe("resolve-by-name");
|
||||
});
|
||||
|
||||
it("shows current task column context for linked agents", async () => {
|
||||
const store = createStore();
|
||||
await store.init();
|
||||
const triageTask = await store.createTask({ description: "Show linked task", column: "triage" });
|
||||
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
|
||||
await agentStore.init();
|
||||
const agent = await agentStore.createAgent({ name: "show-linked", role: "executor", metadata: {} });
|
||||
await agentStore.syncExecutionTaskLink(agent.id, triageTask.id);
|
||||
|
||||
const tool = api.tools.get("fn_agent_show")!;
|
||||
const result = await tool.execute("as-current-task-context", { id: agent.id }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
expect(result.content[0].text).toContain(`Current Task: ${triageTask.id} (triage)`);
|
||||
expect(result.content[0].text).not.toMatch(new RegExp(`Current Task: ${triageTask.id}(?! \\()`));
|
||||
});
|
||||
|
||||
it("returns error for unknown agent", async () => {
|
||||
const tool = api.tools.get("fn_agent_show")!;
|
||||
const result = await tool.execute("as-3", { id: "no-such-agent" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
resolveSecretAccessPolicy,
|
||||
getProjectRootFromWorktree,
|
||||
resolveTaskGithubTracking,
|
||||
formatCurrentTaskLine,
|
||||
type SecretScope,
|
||||
} from "@fusion/core";
|
||||
import {
|
||||
@@ -4123,6 +4124,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
if (params.includeEphemeral !== undefined) filter.includeEphemeral = params.includeEphemeral;
|
||||
|
||||
const agents = await agentStore.listAgents(filter as Parameters<typeof agentStore.listAgents>[0]);
|
||||
const store = await getStore(ctx.cwd);
|
||||
|
||||
if (agents.length === 0) {
|
||||
return {
|
||||
@@ -4131,7 +4133,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
};
|
||||
}
|
||||
|
||||
const lines = agents.map((agent) => {
|
||||
const lines = await Promise.all(agents.map(async (agent) => {
|
||||
const parts: string[] = [
|
||||
`ID: ${agent.id}`,
|
||||
`Name: ${agent.name}`,
|
||||
@@ -4145,10 +4147,22 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
const snippet = agent.instructionsText.slice(0, 100);
|
||||
parts.push(`Custom Instructions: ${snippet}${agent.instructionsText.length > 100 ? "…" : ""}`);
|
||||
}
|
||||
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
||||
if (agent.taskId) {
|
||||
/*
|
||||
FNXC:AgentTaskStateDrift 2026-06-27-16:05:
|
||||
Show the linked task column in fn_list_agents so parked triage/todo ownership is not mistaken for an in-progress execution mismatch.
|
||||
*/
|
||||
let linkedTask: Pick<Task, "id" | "column"> | null = null;
|
||||
try {
|
||||
linkedTask = await store.getTask(agent.taskId);
|
||||
} catch {
|
||||
linkedTask = null;
|
||||
}
|
||||
parts.push(formatCurrentTaskLine(agent.taskId, linkedTask));
|
||||
}
|
||||
|
||||
return parts.join("\n");
|
||||
});
|
||||
}));
|
||||
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Available agents (${agents.length}):\n\n${lines.join("\n\n")}` }],
|
||||
@@ -4285,6 +4299,7 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
|
||||
// Get direct reports
|
||||
const directReports = await agentStore.getAgentsByReportsTo(agent.id);
|
||||
const store = await getStore(ctx.cwd);
|
||||
|
||||
const parts: string[] = [
|
||||
`ID: ${agent.id}`,
|
||||
@@ -4309,7 +4324,19 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
parts.push(`Direct Reports: ${directReports.map((r) => `${r.name} (${r.id})`).join(", ")}`);
|
||||
}
|
||||
|
||||
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
||||
if (agent.taskId) {
|
||||
/*
|
||||
FNXC:AgentTaskStateDrift 2026-06-27-16:05:
|
||||
Show the linked task column in fn_agent_show so parked triage/todo ownership is not mistaken for an in-progress execution mismatch.
|
||||
*/
|
||||
let linkedTask: Pick<Task, "id" | "column"> | null = null;
|
||||
try {
|
||||
linkedTask = await store.getTask(agent.taskId);
|
||||
} catch {
|
||||
linkedTask = null;
|
||||
}
|
||||
parts.push(formatCurrentTaskLine(agent.taskId, linkedTask));
|
||||
}
|
||||
|
||||
if (agent.instructionsText) {
|
||||
const snippet = agent.instructionsText.slice(0, 100);
|
||||
|
||||
@@ -169,6 +169,20 @@ interface AgentLock {
|
||||
*/
|
||||
export const DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS = 3_600_000;
|
||||
|
||||
export function formatCurrentTaskLine(taskId: string, linkedTask: Pick<Task, "column"> | null | undefined): string {
|
||||
/*
|
||||
FNXC:AgentTaskStateDrift 2026-06-27-16:05:
|
||||
FN-7138 requires human-readable agent surfaces to include the linked task column so coordinators can distinguish legitimate transient triage/planning ownership from stale execution drift.
|
||||
*/
|
||||
if (!linkedTask) {
|
||||
return `Current Task: ${taskId} (unresolved)`;
|
||||
}
|
||||
if (linkedTask.column === "done" || linkedTask.column === "archived") {
|
||||
return `Current Task: ${taskId} (not active — ${linkedTask.column})`;
|
||||
}
|
||||
return `Current Task: ${taskId} (${linkedTask.column})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the runtimeConfig to persist for a newly created agent.
|
||||
*
|
||||
@@ -1288,6 +1302,22 @@ export class AgentStore extends EventEmitter {
|
||||
* Used by runtime execution bookkeeping so durable assigned agents can
|
||||
* reflect active task ownership without triggering heartbeat assignment wakeups.
|
||||
*/
|
||||
async resolveCurrentTaskLink(taskId: string): Promise<Pick<Task, "id" | "column"> | null> {
|
||||
/*
|
||||
FNXC:AgentTaskStateDrift 2026-06-27-16:05:
|
||||
AgentStore owns agent.taskId but may not have a TaskStore in every host. Expose a null-safe lookup for display-only column context without making task lookup a hard dependency.
|
||||
*/
|
||||
if (!this.taskStore) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const task = await this.taskStore.getTask(taskId);
|
||||
return task ? { id: task.id, column: task.column } : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async syncExecutionTaskLink(agentId: string, taskId: string | undefined): Promise<Agent> {
|
||||
return this.withLock(agentId, async () => {
|
||||
const agent = await this.getAgent(agentId);
|
||||
|
||||
@@ -469,7 +469,7 @@ export {
|
||||
validateColumnAgentBindings,
|
||||
ColumnAgentBindingError,
|
||||
} from "./column-agent-binding-validation.js";
|
||||
export { AgentStore, DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS } from "./agent-store.js";
|
||||
export { AgentStore, DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS, formatCurrentTaskLine } from "./agent-store.js";
|
||||
export type { AgentStoreEvents } from "./agent-store.js";
|
||||
export {
|
||||
isImplementationTask,
|
||||
|
||||
@@ -6,6 +6,7 @@ function createMockAgentStore(overrides: Partial<AgentStore> = {}): AgentStore {
|
||||
return {
|
||||
listAgents: vi.fn().mockResolvedValue([]),
|
||||
getAgent: vi.fn().mockResolvedValue(null),
|
||||
resolveCurrentTaskLink: vi.fn().mockResolvedValue(null),
|
||||
...overrides,
|
||||
} as unknown as AgentStore;
|
||||
}
|
||||
@@ -70,7 +71,48 @@ describe("createListAgentsTool", () => {
|
||||
expect(text).toContain("Name: Bob");
|
||||
expect(text).toContain("Role: reviewer");
|
||||
expect(text).toContain("State: running");
|
||||
expect(text).toContain("Current Task: FN-100");
|
||||
expect(text).toContain("Current Task: FN-100 (unresolved)");
|
||||
});
|
||||
|
||||
it("shows linked task columns for triage and in-progress task links", async () => {
|
||||
const agents = [
|
||||
createAgent({ id: "agent-triage", name: "Planner", taskId: "FN-200" }),
|
||||
createAgent({ id: "agent-active", name: "Runner", taskId: "FN-201" }),
|
||||
];
|
||||
vi.mocked(agentStore.listAgents).mockResolvedValue(agents);
|
||||
vi.mocked(agentStore.resolveCurrentTaskLink).mockImplementation(async (taskId: string) => {
|
||||
if (taskId === "FN-200") return { id: taskId, column: "triage" as const };
|
||||
if (taskId === "FN-201") return { id: taskId, column: "in-progress" as const };
|
||||
return null;
|
||||
});
|
||||
|
||||
const tool = createListAgentsTool(agentStore);
|
||||
const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any);
|
||||
|
||||
const text = (result.content[0] as { text: string }).text;
|
||||
expect(text).toContain("Current Task: FN-200 (triage)");
|
||||
expect(text).toContain("Current Task: FN-201 (in-progress)");
|
||||
expect(text).not.toMatch(/Current Task: FN-200(?! \()/);
|
||||
expect(text).not.toMatch(/Current Task: FN-201(?! \()/);
|
||||
});
|
||||
|
||||
it("marks missing and terminal linked tasks without throwing", async () => {
|
||||
const agents = [
|
||||
createAgent({ id: "agent-missing", name: "Missing", taskId: "FN-300" }),
|
||||
createAgent({ id: "agent-done", name: "Done", taskId: "FN-301" }),
|
||||
];
|
||||
vi.mocked(agentStore.listAgents).mockResolvedValue(agents);
|
||||
vi.mocked(agentStore.resolveCurrentTaskLink).mockImplementation(async (taskId: string) => {
|
||||
if (taskId === "FN-301") return { id: taskId, column: "done" as const };
|
||||
return null;
|
||||
});
|
||||
|
||||
const tool = createListAgentsTool(agentStore);
|
||||
const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any);
|
||||
|
||||
const text = (result.content[0] as { text: string }).text;
|
||||
expect(text).toContain("Current Task: FN-300 (unresolved)");
|
||||
expect(text).toContain("Current Task: FN-301 (not active — done)");
|
||||
});
|
||||
|
||||
it("includes soul truncated to 200 chars when present", async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { createHash } from "node:crypto";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import * as fusionCore from "@fusion/core";
|
||||
import type { AgentState, AgentCapability, AgentUpdateInput, Artifact, ArtifactCreateInput, ArtifactWithTask, TaskDocument, TaskDocumentCreateInput, TaskStore, RunMutationContext, MessageStore, Message, SourceType, Settings, ResearchRun, ResearchRunStatus, TaskCreateInput, ReflectionStore, ApprovalRequestStore, ProjectSettings, ChatStore, WorkflowSettingDefinition, GoalStatus } from "@fusion/core";
|
||||
import { listTraits, isBuiltinWorkflowId, AgentStore, validateColumnAgentBindings, ColumnAgentBindingError, stripApprovalBypassFlags, WorkflowSettingRejectionError, resolveEffectiveSettingsById, resolveWorkflowIrById, findOrphanedSettingValues, BUILTIN_WORKFLOW_SETTINGS, MAX_TASK_LIST_TEXT_CHARS } from "@fusion/core";
|
||||
import { listTraits, isBuiltinWorkflowId, AgentStore, validateColumnAgentBindings, ColumnAgentBindingError, stripApprovalBypassFlags, WorkflowSettingRejectionError, resolveEffectiveSettingsById, resolveWorkflowIrById, findOrphanedSettingValues, BUILTIN_WORKFLOW_SETTINGS, MAX_TASK_LIST_TEXT_CHARS, formatCurrentTaskLine } from "@fusion/core";
|
||||
import { promoteHeldTask } from "./hold-release.js";
|
||||
import { DASHBOARD_USER_ID, canAgentTakeImplementationTaskForExplicitRouting, dailyMemoryPath, ensureOpenClawMemoryFiles, extractAgentProvisioningRequest, formatRoleMismatchReason, getMemoryBackendCapabilities, getProjectMemory, isEphemeralAgent, memoryLongTermPath, normalizeMessageParticipant, reconcileDeterministicDuplicate, resolveAgentProvisioningPolicy, resolveMemoryBackend, resolveResearchSettings, resolveTaskGithubTracking, runDeterministicDuplicateGuard, scheduleQmdProjectMemoryRefresh, searchProjectMemory, shouldSkipBackgroundQmdRefresh } from "@fusion/core";
|
||||
import { ResearchOrchestrator } from "./research-orchestrator.js";
|
||||
@@ -2897,7 +2897,7 @@ export function createListAgentsTool(agentStore: AgentStore): ToolDefinition {
|
||||
};
|
||||
}
|
||||
|
||||
const lines = agents.map((agent) => {
|
||||
const lines = await Promise.all(agents.map(async (agent) => {
|
||||
const parts: string[] = [
|
||||
`ID: ${agent.id}`,
|
||||
`Name: ${agent.name}`,
|
||||
@@ -2911,10 +2911,17 @@ export function createListAgentsTool(agentStore: AgentStore): ToolDefinition {
|
||||
const snippet = agent.instructionsText.slice(0, 100);
|
||||
parts.push(`Custom Instructions: ${snippet}${agent.instructionsText.length > 100 ? "…" : ""}`);
|
||||
}
|
||||
if (agent.taskId) parts.push(`Current Task: ${agent.taskId}`);
|
||||
if (agent.taskId) {
|
||||
/*
|
||||
FNXC:AgentTaskStateDrift 2026-06-27-16:05:
|
||||
Show the linked task column in fn_list_agents so parked triage/todo ownership is not mistaken for an in-progress execution mismatch.
|
||||
*/
|
||||
const linkedTask = await agentStore.resolveCurrentTaskLink(agent.taskId);
|
||||
parts.push(formatCurrentTaskLine(agent.taskId, linkedTask));
|
||||
}
|
||||
|
||||
return parts.join("\n");
|
||||
});
|
||||
}));
|
||||
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Available agents:\n\n${lines.join("\n\n")}` }],
|
||||
|
||||
Reference in New Issue
Block a user