Skip to content
Draft
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
40 changes: 40 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ export interface ProgressPayload {
content: string;
}

/** Progress namespace used for Dreaming artifacts. Downstream consumers filter on this. */
export const DREAMING_PROGRESS_NAMESPACE = "dreaming";
/** Progress kind used for Dreaming artifacts. */
export const DREAMING_ARTIFACT_KIND = "artifact";
/** Progress payload version for Dreaming artifacts. */
export const DREAMING_ARTIFACT_VERSION = 0;

/**
* A Dreaming artifact produced by a dream job and reported back to the platform.
*
* NOTE: This shape is provisional (see github/agents issue 1476). The fields
* below are a reasonable M0 default and may change once the artifact schema is
* finalized.
*/
export interface DreamingArtifact {
/** Short human-readable title for the artifact. */
title: string;
/** Markdown summary describing what the dream produced. */
summary: string;
/** Optional structured payload for machine consumers. */
data?: Record<string, unknown>;
}

/**
* Response from the progress API (may contain user messages for mid-session steering).
*/
Expand Down Expand Up @@ -370,6 +393,23 @@ export class PlatformClient {
}
}

/**
* Sends a Dreaming artifact to the platform via the job-progress callback.
*
* Uses the first-party "dreaming" progress namespace (not this.namespace) so
* downstream consumers can filter for it. The platform token and nonce stay
* inside this trusted process; the artifact travels as the progress payload
* content.
*/
async sendDreamingArtifact(artifact: DreamingArtifact): Promise<SendResult> {
return this.sendRawProgress({
namespace: DREAMING_PROGRESS_NAMESPACE,
kind: DREAMING_ARTIFACT_KIND,
version: DREAMING_ARTIFACT_VERSION,
content: JSON.stringify(artifact),
});
}

// =========================================================================
// Generic Progress API
// =========================================================================
Expand Down
9 changes: 7 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ export type {
// Platform Client
// =============================================================================

export { PlatformClient, resolveSelectedModel, isModelSelectionEnabled } from "./client.js";
export { PlatformClient, resolveSelectedModel, isModelSelectionEnabled, DREAMING_PROGRESS_NAMESPACE, DREAMING_ARTIFACT_KIND, DREAMING_ARTIFACT_VERSION } from "./client.js";

export type { PlatformClientConfig, ProgressPayload, ProgressRecord, ProgressResponse, SendResult, JobDetails, ProblemStatement, ResolveSelectedModelOptions } from "./client.js";
export type { PlatformClientConfig, ProgressPayload, ProgressRecord, ProgressResponse, SendResult, JobDetails, ProblemStatement, ResolveSelectedModelOptions, DreamingArtifact } from "./client.js";

// =============================================================================
// MCP Server
Expand All @@ -105,11 +105,16 @@ export {
REPORT_PROGRESS_TOOL_NAME,
reportProgressToolDescription,
reportProgressInputSchema,
REPORT_DREAMING_ARTIFACT_TOOL_NAME,
reportDreamingArtifactToolDescription,
reportDreamingArtifactInputSchema,
MAX_DREAMING_ARTIFACT_CONTENT_BYTES,
} from "./mcp-server.js";

export type {
EngineMcpServerConfig,
ReportProgressInput,
ReportDreamingArtifactInput,
} from "./mcp-server.js";

// =============================================================================
Expand Down
91 changes: 90 additions & 1 deletion src/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*
* Tools provided:
* - report_progress: Commits changes and reports progress to the platform
* - reply_to_comment: Posts a reply to a PR comment
* - report_dreaming_artifact: Reports a Dreaming artifact back to the platform
*
* Usage:
* // Start as standalone server
Expand All @@ -26,7 +28,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { commitAndPush } from "./git.js";
import type { PlatformClient } from "./client.js";
import type { PlatformClient, DreamingArtifact } from "./client.js";

// =============================================================================
// Debug Logging (writes to file since stdout is used for MCP protocol)
Expand Down Expand Up @@ -100,6 +102,30 @@ export const replyToCommentToolDescription = `Reply to a pull request comment or
* Use this to respond to reviewer feedback or questions
* The reply will be visible on the pull request as a comment from the agent`;

export const REPORT_DREAMING_ARTIFACT_TOOL_NAME = "report_dreaming_artifact";

/**
* Maximum size (in bytes) of the serialized artifact content. Progress payloads
* are forwarded onto a hydro topic with a bounded message size, so the tool
* rejects oversized artifacts rather than failing downstream. Provisional value
* (see github/agents issue 1476).
*/
export const MAX_DREAMING_ARTIFACT_CONTENT_BYTES = 256 * 1024;

export const reportDreamingArtifactInputSchema = z.object({
title: z.string().describe("Short human-readable title for the artifact"),
summary: z.string().describe("Markdown summary describing what the dream produced"),
data: z.record(z.string(), z.unknown()).optional().describe("Optional structured payload for machine consumers"),
});

export type ReportDreamingArtifactInput = z.infer<typeof reportDreamingArtifactInputSchema>;

export const reportDreamingArtifactToolDescription = `Report a Dreaming artifact produced by this job back to the platform.
* Call this when the dream has produced a result worth persisting (e.g. a summary of findings or decisions)
* Provide a short title and a markdown summary; include structured details in data when useful
* The artifact is sent to the platform over the job-progress channel; do not include secrets
* Keep the payload small; oversized artifacts are rejected`;

// =============================================================================
// Tool Implementation
// =============================================================================
Expand Down Expand Up @@ -267,6 +293,69 @@ export function createEngineMcpServer(config: EngineMcpServerConfig): McpServer
}
);

// Register the report_dreaming_artifact tool
server.tool(
REPORT_DREAMING_ARTIFACT_TOOL_NAME,
reportDreamingArtifactToolDescription,
reportDreamingArtifactInputSchema.shape,
async (params: ReportDreamingArtifactInput) => {
log("report_dreaming_artifact called", { title: params.title });

if (!config.platformClient) {
log("report_dreaming_artifact: no platform client available");
return {
content: [{ type: "text" as const, text: "Cannot report artifact: platform client not configured" }],
isError: true,
};
}

const artifact: DreamingArtifact = {
title: params.title,
summary: params.summary,
...(params.data !== undefined ? { data: params.data } : {}),
};

const contentBytes = Buffer.byteLength(JSON.stringify(artifact), "utf8");
if (contentBytes > MAX_DREAMING_ARTIFACT_CONTENT_BYTES) {
log("report_dreaming_artifact: payload too large", { contentBytes });
return {
content: [
{
type: "text" as const,
text: `Artifact too large: ${contentBytes} bytes exceeds the ${MAX_DREAMING_ARTIFACT_CONTENT_BYTES} byte limit. Shorten the summary or move detail into a smaller data payload.`,
},
],
isError: true,
};
}

try {
const result = await config.platformClient.sendDreamingArtifact(artifact);

if (result.success) {
log("report_dreaming_artifact: sent successfully");
return {
content: [{ type: "text" as const, text: "Artifact reported successfully." }],
isError: false,
};
}

log("report_dreaming_artifact: failed", { error: result.error?.message });
return {
content: [{ type: "text" as const, text: `Failed to report artifact: ${result.error?.message}` }],
isError: true,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
log("report_dreaming_artifact: error", { error: errorMessage });
return {
content: [{ type: "text" as const, text: `Error reporting artifact: ${errorMessage}` }],
isError: true,
};
}
}
);

return server;
}

Expand Down