diff --git a/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server.test.ts b/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server.test.ts index b089243991..7f55c5d805 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server.test.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server.test.ts @@ -3257,8 +3257,11 @@ describe('OpenCodeServerHarness', () => { } }); - it('terminates an OpenCode retry loop after its structured attempt budget without a message', async () => { - const { client, harness } = createHarness(); + it('hands an exhausted OpenCode connection reset retry to bounded Roomote recovery', async () => { + vi.useFakeTimers(); + const { client, harness } = createHarness(undefined, { + providerErrorBaseDelayMs: 1_000, + }); const taskEvents: TaskEvent[] = []; const persistedEnvelopes: AcpPersistedEnvelope[] = []; @@ -3294,7 +3297,6 @@ describe('OpenCodeServerHarness', () => { }), ).toBe(true); - // The decision uses the structured attempt count, not the provider prose. await client.emit({ type: 'session.status', properties: { @@ -3302,6 +3304,7 @@ describe('OpenCodeServerHarness', () => { status: { type: 'retry', attempt: 3, + message: 'Connection reset by server', next: Date.now() + 2_000, }, }, @@ -3315,20 +3318,40 @@ describe('OpenCodeServerHarness', () => { taskEvents.some( (event) => event.eventName === TaskEventName.TaskAborted, ), - ).toBe(true); - expect(harness.getQueuedMessages()).toEqual([]); + ).toBe(false); + expect( + harness.getQueuedMessages().map((message) => message.text), + ).toEqual(['Queued follow-up.']); expect(client.promptAsync).toHaveBeenCalledTimes(1); expect( persistedEnvelopes.some( (envelope) => envelope.eventType === ACP_ENVELOPE_EVENT_TYPES.AssistantMessage && String(envelope.payload.text ?? '').includes( - 'Provider retry limit exceeded.', - ), + 'Connection reset by server', + ) && + String(envelope.payload.text ?? '').includes('Retrying in 1s') && + asRecord(envelope.payload.providerRetryNotice)?.kind === + 'provider_error', ), ).toBe(true); + + await client.emit({ + type: 'session.error', + properties: { + sessionID: 'ses_1', + error: { name: 'MessageAbortedError', data: { message: 'Aborted' } }, + }, + }); + await client.emit({ + type: 'session.idle', + properties: { sessionID: 'ses_1' }, + }); + await vi.advanceTimersByTimeAsync(1_000); + expect(client.promptAsync).toHaveBeenCalledTimes(2); } finally { harness.dispose(); + vi.useRealTimers(); } }); diff --git a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts index 79e491b9da..43280b2606 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/harness.ts @@ -93,6 +93,7 @@ import { formatOpenCodeProviderErrorRetryNoticeText, getOpenCodeProviderErrorRecovery, isOpenCodeContextOverflowError, + isOpenCodeRetryableTransportError, isOpenCodeTerminalProviderError, resolveOpenCodeProviderErrorRetryDelayMs, summarizeOpenCodeProviderError, @@ -3995,6 +3996,7 @@ export class OpenCodeServerHarness (isTerminalProviderError ? 'Provider request failed with a non-retryable error.' : 'Provider retry limit exceeded.'), + !isTerminalProviderError && isOpenCodeRetryableTransportError(status), ); return; } @@ -4073,6 +4075,7 @@ export class OpenCodeServerHarness private async terminateOpenCodeProviderRetry( sessionId: string, message: string, + retryable: boolean, ): Promise { this.logger.error( `OpenCode reported a terminal provider error as retryable sessionId=${sessionId}: ${message}`, @@ -4100,7 +4103,7 @@ export class OpenCodeServerHarness sessionID: sessionId, error: { name: 'APIError', - data: { message, isRetryable: false }, + data: { message, isRetryable: retryable }, }, }, }); diff --git a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.test.ts b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.test.ts index 1784025b59..6d0a134afd 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.test.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.test.ts @@ -4,6 +4,7 @@ import { formatOpenCodeProviderErrorRetryNoticeText, getOpenCodeProviderErrorRecovery, isOpenCodeTerminalProviderError, + isOpenCodeRetryableTransportError, resolveOpenCodeProviderErrorRetryDelayMs, summarizeOpenCodeProviderError, } from './provider-error-recovery'; @@ -39,6 +40,29 @@ describe('getOpenCodeProviderErrorRecovery', () => { ).toMatchObject({ kind: 'provider_error', maxRetries: 6 }); }); + it.each([ + { message: 'Connection reset by server' }, + { error: { message: 'Connection reset by server' } }, + { + data: JSON.stringify({ + error: { message: 'Connection reset by server' }, + }), + }, + ])('retries exact and wrapped connection reset errors', (error) => { + expect(isOpenCodeRetryableTransportError(error)).toBe(true); + expect(isOpenCodeTerminalProviderError(error)).toBe(false); + expect(getOpenCodeProviderErrorRecovery(error)).toMatchObject({ + kind: 'provider_error', + maxRetries: 6, + }); + }); + + it('does not treat unrelated provider errors as connection resets', () => { + expect( + isOpenCodeRetryableTransportError({ message: 'Connection refused' }), + ).toBe(false); + }); + it('classifies native ContentFilterError payloads as policy refusals', () => { const recovery = getOpenCodeProviderErrorRecovery({ name: 'ContentFilterError', diff --git a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.ts b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.ts index a0fccb4b34..8a9ce1eeeb 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/provider-error-recovery.ts @@ -31,6 +31,7 @@ export type OpenCodeProviderErrorRecovery = { // client-side before or instead of an HTTP response. const TERMINAL_ERROR_NAMES = new Set(['contextoverflowerror']); const POLICY_ERROR_NAMES = new Set(['contentfiltererror']); +const CONNECTION_RESET_MESSAGE = 'connection reset by server'; // Client errors are terminal because replaying the same request cannot // succeed, except timeouts (408) and rate limits (429) which are transient. @@ -134,6 +135,12 @@ export function isOpenCodeContextOverflowError(error: unknown): boolean { return hasErrorName(collectProviderErrorValues(error), TERMINAL_ERROR_NAMES); } +export function isOpenCodeRetryableTransportError(error: unknown): boolean { + return collectProviderErrorValues(error).some( + (value) => normalizeIdentifier(value) === CONNECTION_RESET_MESSAGE, + ); +} + function isExplicitlyTerminal(values: unknown[]): boolean { if (values.some((value) => asRecord(value)?.isRetryable === false)) { return true; diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-schemas.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-schemas.test.ts index c214fa51dd..aa194d2b00 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-schemas.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-schemas.test.ts @@ -7,6 +7,7 @@ import { pathToFileURL } from 'node:url'; import { CALL_INTEGRATION_TOOL_TOOL, FAST_AGENT_NATIVE_TOOL_NAMES, + MANAGE_WAKEUPS_TOOL, } from '@roomote/types'; import { z } from 'zod'; import { Ajv2020 } from 'ajv/dist/2020.js'; @@ -429,6 +430,41 @@ describe('Fast native tool schemas as OpenAI receives them', () => { ).toEqual(request); }); + it('keeps internal wakeup visibility out of model-controlled arguments', async () => { + const wakeupsTool = tools.find( + (tool) => tool.name === FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups, + )!; + const request = { + action: 'create', + name: 'Follow through on session tasks', + prompt: 'Check the tasks in this conversation.', + schedule: 'in 10m', + reportPolicy: 'only_when_notable', + internal: true, + }; + const parsed = zod.z + .object(wakeupsTool.args as Record) + .parse(request); + const execute = wakeupsTool.execute as ( + args: unknown, + context: unknown, + ) => Promise<{ name: string; args: unknown }>; + const forwarded = await execute(parsed, {}); + + expect(parsed).not.toHaveProperty('internal'); + expect(wakeupsTool.args).not.toHaveProperty('internal'); + expect(forwarded.name).toBe(FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups); + expect( + z.object(MANAGE_WAKEUPS_TOOL.inputSchema).parse(forwarded.args), + ).toEqual({ + action: 'create', + name: request.name, + prompt: request.prompt, + schedule: request.schedule, + reportPolicy: request.reportPolicy, + }); + }); + // Synthetic arguments verify the generic bridge, not live upstream schemas. it.each([ { toolName: 'sources', args: {} }, diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts index fefd302f4b..dcbc9f61df 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts @@ -705,22 +705,14 @@ describe('buildFastAgentSystemPrompt', () => { 'After "launch_task" successfully creates a coding task for a human-authored request', ); expect(prompt).toContain( - 'list active wakeups and silently ensure this conversation has exactly one session-wide one-shot check', + 'the runtime silently ensures this conversation has exactly one internal session-wide one-shot check', ); - expect(prompt).toContain('name "Follow through on session tasks"'); - expect(prompt).toContain( - 'Run the Own Coding Task Follow-Through session check for all tasks in this conversation', - ); - expect(prompt).toContain('schedule "in 10m"'); - expect(prompt).toContain('reportPolicy "only_when_notable"'); - expect(prompt).toContain('and internal true'); + expect(prompt).toContain('Do not create another wakeup for this purpose'); + expect(prompt).not.toContain('and internal true'); expect(prompt).toContain( 'not external-process monitoring, so do not ask for monitoring consent', ); - expect(prompt).toContain('Do not schedule after a failed launch'); - expect(prompt).toContain( - 'task-independent so concurrent or successive launches deduplicate to one monitor for the Session', - ); + expect(prompt).toContain('Failed launches do not schedule follow-through'); expect(prompt).toContain( 'Do not mention this automatic monitor, its setup, cadence, or next run in the acknowledgement or closeout', ); diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts index 70e1f1a77b..e1edc4eb07 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts @@ -51,6 +51,7 @@ const mocks = vi.hoisted(() => ({ touchSessionActivity: vi.fn(), getSessionForTask: vi.fn(), getPendingHumanFollowUp: vi.fn(), + ensureOwnTaskFollowThroughWakeup: vi.fn(), inArray: vi.fn((...values: unknown[]) => values), updateParentEventWhere: vi.fn(), nativeSteer: vi.fn(), @@ -132,6 +133,11 @@ vi.mock('../../available-environments', () => ({ getAvailableEnvironments: mocks.getEnvironments, })); +vi.mock('../../session-wakeups', async (importOriginal) => ({ + ...(await importOriginal()), + ensureOwnTaskFollowThroughWakeup: mocks.ensureOwnTaskFollowThroughWakeup, +})); + vi.mock('@roomote/db/server', () => ({ and: vi.fn((...values) => values), asc: vi.fn((value) => value), @@ -413,6 +419,7 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { mocks.touchSessionActivity.mockResolvedValue(undefined); mocks.getSessionForTask.mockResolvedValue(null); mocks.getPendingHumanFollowUp.mockResolvedValue([]); + mocks.ensureOwnTaskFollowThroughWakeup.mockResolvedValue(undefined); mocks.updateParentEventWhere.mockResolvedValue(undefined); mocks.nativeSteer.mockResolvedValue(undefined); mocks.getNativeRuntime.mockImplementation(async () => { @@ -7850,6 +7857,11 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { 'Fix checkout.\n\nAttachment: checkout-plan.md\nAdd a retry test.', }), ); + expect(mocks.ensureOwnTaskFollowThroughWakeup).toHaveBeenCalledOnce(); + expect(mocks.ensureOwnTaskFollowThroughWakeup).toHaveBeenCalledWith({ + conversationId: 'conversation-1', + userId: 'user-1', + }); const canonicalWrites = mocks.upsertMessage.mock.calls.map( ([input]) => input.message, ); @@ -8315,6 +8327,7 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { 'I could not start the checkout work because no task capacity is available.', }); expect(adapter.postReply).toHaveBeenCalledTimes(2); + expect(mocks.ensureOwnTaskFollowThroughWakeup).not.toHaveBeenCalled(); }); it('allows a corrected launch after rejecting an unavailable model', async () => { diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts index 8a652ec715..d53093c9e9 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts @@ -363,7 +363,7 @@ ${emailCadenceGuidance}- Prefer one direct closeout over an acknowledgement foll - Do not offer or schedule checks that duplicate existing task, PR lifecycle/review, or other notifications and monitors. Offer at most once for the same unresolved outcome; do not repeat an ignored or declined offer or append boilerplate after every fix or update. Do not make proactive offers on automation or scheduled-wakeup turns. Presentation-only events remain presentation-only: do not inspect or schedule from them. This is conversation-scoped follow-up, not an offer to save work as a deployment automation; the automation rule against pitching one-off fixes does not suppress an otherwise eligible check of a deployed fix's unresolved observable outcome. ## Own Coding Task Follow-Through -- After "launch_task" successfully creates a coding task for a human-authored request, use "manage_wakeups" before the closeout to list active wakeups and silently ensure this conversation has exactly one session-wide one-shot check with name "Follow through on session tasks", prompt "Run the Own Coding Task Follow-Through session check for all tasks in this conversation. Follow that system policy exactly, including inspection, reporting, correction, stopping, and rearming.", schedule "in 10m", reportPolicy "only_when_notable", and internal true. Reuse an equivalent active check instead of creating another. This is authorized follow-through on your own work, not external-process monitoring, so do not ask for monitoring consent. Do not schedule after a failed launch. The name and prompt are intentionally task-independent so concurrent or successive launches deduplicate to one monitor for the Session. +- After "launch_task" successfully creates a coding task for a human-authored request, the runtime silently ensures this conversation has exactly one internal session-wide one-shot check for Own Coding Task Follow-Through. Do not create another wakeup for this purpose. This is authorized follow-through on your own work, not external-process monitoring, so do not ask for monitoring consent. Failed launches do not schedule follow-through. - Do not mention this automatic monitor, its setup, cadence, or next run in the acknowledgement or closeout. This exception overrides generic wakeup-creation confirmation instructions only for automatic own-task follow-through; continue to confirm reminders and monitoring that the user requested. - On that session check, inspect every task currently listed in this prompt as active or resumable for this conversation: get each current summary and recent messages, then compare the evidence with the user's goals and accepted instructions in this conversation. Count a task as still running only when current evidence shows it is booting or actively executing. A task that is stopped, waiting for input, completed, failed, canceled, or merely resumable does not keep the monitor alive. Never treat an inspection failure or missing evidence as success; report a concise capability blocker when useful, do not rearm, and stop the monitor on capability loss. - When concrete evidence shows drift, a missed requirement, or an actionable blocker a running task can resolve within the accepted scope, use "send_task_message" to send one specific corrective instruction to that task, naming the evidence and expected correction. Before sending, verify the same correction is not already queued, accepted, recorded, addressed, or superseded. Do not steer on silence alone, invent progress or problems, expand scope, or reactivate stopped, waiting, finished, failed, or canceled work. diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts index 8752d16445..93e93df1b7 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts @@ -63,6 +63,7 @@ import packageJson from '../../../../../package.json'; import { appendAttachmentTextsToPromptText } from '../../file-attachments'; import { + ensureOwnTaskFollowThroughWakeup, handleManageWakeupsToolCall, normalizeManageWakeupsArgs, } from '../session-wakeups'; @@ -4118,6 +4119,18 @@ export async function answerFastAgentQuestion({ } if (result.success) { currentTasks.set(result.taskId, { taskId: result.taskId }); + if (substantiveHumanInput) { + try { + await ensureOwnTaskFollowThroughWakeup({ + conversationId: session.id, + userId, + }); + } catch (error) { + console.warn( + `[Fast Agent] Failed to schedule own-task follow-through after launch: ${formatErrorForLog(error)}`, + ); + } + } if (result.kickoffDelivered) { visibleUpdatePosted = true; } diff --git a/packages/cloud-agents/src/server/session-wakeups/index.ts b/packages/cloud-agents/src/server/session-wakeups/index.ts index ddee3a6c83..c7976f72cc 100644 --- a/packages/cloud-agents/src/server/session-wakeups/index.ts +++ b/packages/cloud-agents/src/server/session-wakeups/index.ts @@ -25,6 +25,7 @@ export { export { cancelSessionWakeupForConversation, createSessionWakeup, + ensureOwnTaskFollowThroughWakeup, getSessionWakeupForConversation, handleManageWakeupsToolCall, listSessionWakeupsForConversation, diff --git a/packages/cloud-agents/src/server/session-wakeups/service.test.ts b/packages/cloud-agents/src/server/session-wakeups/service.test.ts index a5a348445a..63db891da1 100644 --- a/packages/cloud-agents/src/server/session-wakeups/service.test.ts +++ b/packages/cloud-agents/src/server/session-wakeups/service.test.ts @@ -10,6 +10,7 @@ import { import { enqueueSessionWakeupFireBestEffort } from './queue'; import { + ensureOwnTaskFollowThroughWakeup, handleManageWakeupsToolCall, type SessionWakeupActor, } from './service'; @@ -159,27 +160,35 @@ describe('handleManageWakeupsToolCall relative reminders', () => { expect(enqueueSessionWakeupFireBestEffort).not.toHaveBeenCalled(); }); - it('persists and manages internal wakeups without changing scheduling', async () => { - const created = await handleManageWakeupsToolCall(actor, { + it('reserves internal wakeups for server-owned task follow-through', async () => { + const visible = await handleManageWakeupsToolCall(actor, { ...createInput, internal: true, - }); - expect(created).toMatchObject({ + } as typeof createInput); + expect(visible).toMatchObject({ success: true, - wakeup: { internal: true, status: 'active' }, - }); - const wakeupId = (created.wakeup as { id: string }).id; - expect(enqueueSessionWakeupFireBestEffort).toHaveBeenCalledExactlyOnceWith({ - wakeupId, - runAt: now.getTime() + 30_000, + wakeup: { internal: false, status: 'active' }, }); - await expect( - handleManageWakeupsToolCall(actor, { action: 'list' }), - ).resolves.toMatchObject({ - count: 1, - wakeups: [{ id: wakeupId, internal: true }], + const created = await ensureOwnTaskFollowThroughWakeup(actor); + expect(created.wakeup).toMatchObject({ + name: 'Follow through on session tasks', + internal: true, + status: 'active', }); + const wakeupId = created.wakeup.id; + expect(enqueueSessionWakeupFireBestEffort).toHaveBeenNthCalledWith(2, { + wakeupId: expect.any(String), + runAt: now.getTime() + 10 * 60_000, + }); + + const listed = await handleManageWakeupsToolCall(actor, { action: 'list' }); + expect(listed.count).toBe(2); + expect(listed.wakeups).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: wakeupId, internal: true }), + ]), + ); await expect( handleManageWakeupsToolCall(actor, { action: 'get', wakeupId }), ).resolves.toMatchObject({ wakeup: { id: wakeupId, internal: true } }); diff --git a/packages/cloud-agents/src/server/session-wakeups/service.ts b/packages/cloud-agents/src/server/session-wakeups/service.ts index 5c853fea9b..c58a18c02a 100644 --- a/packages/cloud-agents/src/server/session-wakeups/service.ts +++ b/packages/cloud-agents/src/server/session-wakeups/service.ts @@ -25,6 +25,13 @@ import { } from './schedule'; const DEFAULT_DEPLOYMENT_SETTINGS_ID = 'default'; +const OWN_TASK_FOLLOW_THROUGH_WAKEUP = { + name: 'Follow through on session tasks', + prompt: + 'Run the Own Coding Task Follow-Through session check for all tasks in this conversation. Follow that system policy exactly, including inspection, reporting, correction, stopping, and rearming.', + schedule: 'in 10m', + reportPolicy: 'only_when_notable' as const, +}; /** The conversation a wakeup tool call acts on, and who is acting. */ export type SessionWakeupActor = { @@ -48,6 +55,15 @@ export type CreateSessionWakeupResult = { timeZone: string; }; +export function ensureOwnTaskFollowThroughWakeup( + actor: SessionWakeupActor, +): Promise { + return createSessionWakeup(actor, { + ...OWN_TASK_FOLLOW_THROUGH_WAKEUP, + internal: true, + }); +} + /** * Cron defaults and next-run confirmations use the deployment timezone when * one is configured, otherwise UTC. The Slack-workspace fallback that @@ -212,7 +228,6 @@ export async function handleManageWakeupsToolCall( prompt: input.prompt, schedule: input.schedule, reportPolicy: input.reportPolicy ?? null, - internal: input.internal, }); return { success: true, diff --git a/packages/types/src/session-wakeups.test.ts b/packages/types/src/session-wakeups.test.ts index a4cfae9693..90df7c34c6 100644 --- a/packages/types/src/session-wakeups.test.ts +++ b/packages/types/src/session-wakeups.test.ts @@ -45,17 +45,14 @@ describe('manage wakeups tool contract', () => { } }); - it('accepts an explicit internal create flag without adding it by default', () => { - expect( - manageWakeupsInputSchema.parse({ action: 'create', internal: true }), - ).toEqual({ action: 'create', internal: true }); + it('does not expose internal visibility as model input', () => { expect(manageWakeupsInputSchema.parse({ action: 'create' })).toEqual({ action: 'create', }); expect( - manageWakeupsInputSchema.safeParse({ action: 'create', internal: 'true' }) - .success, - ).toBe(false); + manageWakeupsInputSchema.parse({ action: 'create', internal: true }), + ).toEqual({ action: 'create' }); + expect(MANAGE_WAKEUPS_TOOL.inputSchema).not.toHaveProperty('internal'); }); it('publishes the canonical descriptor and is a Fast native tool', () => { @@ -114,7 +111,6 @@ describe('manage wakeups tool contract', () => { it('takes the schedule as one string and nothing else schedule-shaped', () => { expect(Object.keys(MANAGE_WAKEUPS_TOOL.inputSchema).sort()).toEqual([ 'action', - 'internal', 'name', 'prompt', 'reportPolicy', diff --git a/packages/types/src/session-wakeups.ts b/packages/types/src/session-wakeups.ts index 0817d99699..380f012fe3 100644 --- a/packages/types/src/session-wakeups.ts +++ b/packages/types/src/session-wakeups.ts @@ -156,12 +156,6 @@ export const manageWakeupsFieldSchemas = { .describe( '[create] "always" replies to the user on every run (default for one-shots). "only_when_notable" stays silent unless there is news or the condition resolved (default for repeating schedules). Omit to use the default.', ), - internal: z - .boolean() - .optional() - .describe( - '[create] Set true only when system instructions explicitly require an internal wakeup. Omit otherwise.', - ), } satisfies z.ZodRawShape; export const manageWakeupsInputSchema = z.object(manageWakeupsFieldSchemas);