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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions apps/api/src/handlers/tasks/__tests__/launchTask.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/api/src/handlers/tasks/getTaskSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export async function getTaskSummary(
taskRunStatus: latestRun?.status ?? null,
taskPhase: latestRun?.taskPhase ?? null,
taskRunError: latestRun?.error ?? null,
environmentSetupState: latestRun?.environmentSetupState ?? null,
linkedEnvironmentId: linkedEnvironmentId ?? null,
linkedEnvironmentName: linkedEnvironment?.name ?? null,
});
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/handlers/tasks/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface LatestTaskRunSummary {
status: string;
taskPhase: string | null;
error: string | null;
environmentSetupState: string | null;
firstAssistantOutputAt: Date | null;
payload: unknown;
}
Expand All @@ -54,6 +55,7 @@ export async function getLatestTaskRunsByTaskIds(
status: taskRuns.status,
taskPhase: taskRuns.taskPhase,
error: taskRuns.error,
environmentSetupState: taskRuns.environmentSetupState,
firstAssistantOutputAt: taskRuns.firstAssistantOutputAt,
payload: taskRuns.payload,
})
Expand Down
13 changes: 12 additions & 1 deletion apps/api/src/handlers/tasks/launchTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,19 @@ export async function launchTask(
userId: auth.userId,
});

// A settle notification needs a durable pointer back to the launching
// run, so the opt-in only takes effect on run-token launches.
const notifySourceRunOnSettle =
requestedType === 'standard' &&
body.notifyOnSettle === true &&
'runId' in auth.authContext;

const taskBase = {
harness: harnessSelection.harness ?? body.harness,
computeProvider: body.computeProvider,
requestedWorkKindDecision,
...(requestedType === 'environment-definition' &&
...((requestedType === 'environment-definition' ||
notifySourceRunOnSettle) &&
'runId' in auth.authContext
? { sourceRunId: auth.authContext.runId }
: {}),
Expand All @@ -340,6 +348,9 @@ export async function launchTask(
...basePayload,
bootstrap:
requestedType === 'standard' ? body.bootstrap : undefined,
...(notifySourceRunOnSettle
? { notifySourceRunOnSettle: true }
: {}),
},
};

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions apps/worker/src/mcp/roomote-mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ const manageTasksInputSchema = {
'Call "list_environments" immediately before launching and copy one of the returned environmentId values.',
),
branch: z.string().optional().describe('Branch to use (for launch)'),
notifyOnSettle: z
.boolean()
.optional()
.describe(
'For launch: when true, the platform sends a message into THIS task session when the launched task settles (completes, fails, is canceled, or goes idle), so you can wait for that notification instead of polling get_summary.',
),
} satisfies Record<string, z.ZodTypeAny>;

roomoteMcpServer.registerTool(
Expand Down Expand Up @@ -556,6 +562,7 @@ roomoteMcpServer.registerTool(
prompt: params.prompt,
branch: params.branch,
environmentId,
notifyOnSettle: params.notifyOnSettle,
},
config,
);
Expand Down
2 changes: 2 additions & 0 deletions apps/worker/src/mcp/roomote-mcp-server/launch-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export async function handleLaunchTask(
prompt: string;
branch?: string;
environmentId: string;
notifyOnSettle?: boolean;
},
config: RoomoteConfig,
): Promise<ToolResult> {
Expand All @@ -22,6 +23,7 @@ export async function handleLaunchTask(
? undefined
: params.environmentId,
type: 'standard',
...(params.notifyOnSettle ? { notifyOnSettle: true } : {}),
});

if (!result.success) {
Expand Down
24 changes: 24 additions & 0 deletions apps/worker/src/mcp/roomote-mcp-server/task-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@ import { getHarnessLabel, getTaskStatusLabel } from './task-display.js';
import { textResult, catchError } from './tool-result.js';
import type { RoomoteConfig, ToolResult } from './types.js';

/**
* Lifecycle of the task's environment setup (repository setup commands and
* Docker projects), which can keep running in the background after the agent
* has started. Surfaced so a caller monitoring another task — e.g. the
* environment-setup workflow watching its verification task — can tell
* whether that task's environment actually finished setting up, instead of
* inferring it from the task's prose.
*/
function getEnvironmentSetupLine(state: string | null): string | null {
switch (state) {
case 'running':
return 'Environment Setup: still running in the background (workspace dependencies and services may not be ready yet)';
case 'completed':
return 'Environment Setup: completed';
case 'completed_with_warnings':
return 'Environment Setup: completed with warnings (one or more setup commands failed; details in the workspace `.roomote/setup-status.json` and `.roomote/setup-logs/`)';
case 'failed':
return 'Environment Setup: failed (workspace may be missing dependencies or services; details in the workspace `.roomote/setup-status.json` and `.roomote/setup-logs/`)';
default:
return null;
}
}

export async function handleGetTaskSummary(
params: { taskId: string },
config: RoomoteConfig,
Expand All @@ -25,6 +48,7 @@ export async function handleGetTaskSummary(
? `Linked Environment ID: ${result.linkedEnvironmentId}`
: null,
result.taskRunError ? `Error: ${result.taskRunError}` : null,
getEnvironmentSetupLine(result.environmentSetupState ?? null),
].filter(Boolean);

return textResult(lines.join('\n'));
Expand Down
1 change: 1 addition & 0 deletions apps/worker/src/mcp/roomote-mcp-server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface TaskSummaryResponse {
taskRunStatus: string | null;
taskPhase: string | null;
taskRunError: string | null;
environmentSetupState: string | null;
linkedEnvironmentId: string | null;
linkedEnvironmentName: string | null;
}
Expand Down
9 changes: 9 additions & 0 deletions apps/worker/src/sandbox-server/procedures/sendPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ const sendPromptInputSchema = z
* follow-ups (Slack, Teams, Telegram), which always steer.
*/
autoSteerWhenQueued: z.boolean().optional(),
/**
* Hide this prompt from the user-facing transcript. For platform-injected
* machinery (e.g. spawned-task settle notifications) that the agent must
* see but the user should not. Callers hold a run-scoped token, so this
* is not reachable from arbitrary clients.
*/
visibleInTranscript: z.boolean().optional(),
})
.superRefine((data, ctx) => {
const hasPrompt =
Expand Down Expand Up @@ -130,6 +137,7 @@ export const sendPrompt = publicProcedure
clientMessageId: input.clientMessageId,
userName: input.userName,
userImageUrl: input.userImageUrl,
visibleInTranscript: input.visibleInTranscript,
});

if (!success) {
Expand Down Expand Up @@ -189,6 +197,7 @@ export const sendPrompt = publicProcedure
userName: input.userName,
userImageUrl: input.userImageUrl,
clientMessageId: input.clientMessageId,
visibleInTranscript: input.visibleInTranscript,
});

if (!success) {
Expand Down
Loading
Loading