Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 8 additions & 33 deletions src/tools/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,39 +574,14 @@ export function createDATools(
});

// Planning bracket — mirrors AO's enter_plan_mode / exit_plan_mode built-in tools.
// enter_plan_mode: signals start of planning phase; no approval, no side effects.
tools.enter_plan_mode = tool({
description:
'Signal the start of a planning phase. Call this before reasoning about what steps to take ' +
'for any operation involving 2 or more distinct steps or tool calls. ' +
'No action is taken — this is a signal only. Follow it by calling exit_plan_mode with the full plan.',
inputSchema: z.object({}),
needsApproval: async () => false,
execute: async () => ({ planning: true }),
});

// exit_plan_mode: submits the plan for user review; requires approval before execution proceeds.
tools.exit_plan_mode = tool({
description:
'Submit the completed plan for the user to review before any actions are taken. ' +
'Call this after enter_plan_mode, once you have determined all the steps. ' +
'The user will see the plan card and click Run to approve execution. ' +
'Use the same task labels later in :::task-item directives to report progress.',
inputSchema: z.object({
title: z.string().describe('Short plan title (≤ 8 words)'),
description: z.string().optional().describe('One-line summary of what you are about to do'),
tasks: z
.array(
z.object({
id: z.string().describe('Unique step identifier, e.g. "1", "2"'),
label: z.string().describe('Human-readable step description'),
}),
)
.describe('Ordered list of steps to execute'),
}),
needsApproval: async () => true,
execute: async () => ({ approved: true }),
});
//
// HOTFIX(da-nx#658): plan mode is disabled until the da-nx client can render
// plan/tasks. PR #73 removed the prompt guidance, but the model still called
// these tools from their schema descriptions alone — entering plan mode and then
// showing a generic approval card for exit_plan_mode (which the client can't render
// as a plan). So do NOT register enter_plan_mode / exit_plan_mode at all while
// disabled. Both execute() were no-ops and nothing else references them, so this is
// self-contained. Restore this block (see git history / PR #73) once #658 lands.

// Memory tools write to internal agent metadata paths — no user approval needed.
tools.write_project_memory = tool({
Expand Down
48 changes: 34 additions & 14 deletions test/eds-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,17 @@
// Minimal mock for EDSAdminClient
function makeEdsClient(overrides: Partial<EDSAdminClient> = {}): EDSAdminClient {
return {
preview: vi
.fn()
.mockResolvedValue({
status: 200,
path: '/docs/index',
url: 'https://main--repo--org.hlx.page/docs/index',
}),
preview: vi.fn().mockResolvedValue({
status: 200,
path: '/docs/index',
url: 'https://main--repo--org.hlx.page/docs/index',
}),
unpreview: vi.fn().mockResolvedValue({ status: 200, path: '/docs/index' }),
publishLive: vi
.fn()
.mockResolvedValue({
status: 200,
path: '/docs/index',
url: 'https://main--repo--org.hlx.live/docs/index',
}),
publishLive: vi.fn().mockResolvedValue({
status: 200,
path: '/docs/index',
url: 'https://main--repo--org.hlx.live/docs/index',
}),
unpublishLive: vi.fn().mockResolvedValue({ status: 200, path: '/docs/index' }),
...overrides,
} as unknown as EDSAdminClient;
Expand All @@ -37,7 +33,7 @@

const result = await tools.content_preview.execute(
{ org: 'myorg', repo: 'myrepo', path: '/docs/index' },
{} as any,

Check warning on line 36 in test/eds-tools.test.ts

View workflow job for this annotation

GitHub Actions / Test

Unexpected any. Specify a different type
);

expect(edsClient.preview).toHaveBeenCalledWith('myorg', 'myrepo', '/docs/index');
Expand Down Expand Up @@ -204,6 +200,30 @@
const tools = createDATools(null, {});
expect(tools).not.toHaveProperty('content_list');
});

// HOTFIX(da-nx#658): plan mode is disabled until the client renders plan/tasks.
// PR #73 removed the prompt guidance; this ensures the tools themselves are not
// registered, so the model can't enter plan mode from the schema alone. Restore
// both assertions to toHaveProperty once #658 lands.
it('does not register enter_plan_mode / exit_plan_mode while plan mode is disabled', () => {
const daClient = {
listSources: vi.fn(),
getSource: vi.fn(),
createSource: vi.fn(),
updateSource: vi.fn(),
deleteSource: vi.fn(),
copyContent: vi.fn(),
moveContent: vi.fn(),
createVersion: vi.fn(),
getVersions: vi.fn(),
lookupMedia: vi.fn(),
lookupFragment: vi.fn(),
uploadMedia: vi.fn(),
} as any;
const tools = createDATools(daClient, {});
expect(tools).not.toHaveProperty('enter_plan_mode');
expect(tools).not.toHaveProperty('exit_plan_mode');
});
});

describe('content_read missing-file handling', () => {
Expand Down
Loading