@schoolai/shipyard 3.5.3 → 3.6.0-rc.20260507.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5B5MBXHW.js → chunk-52T3USYD.js} +2 -2
- package/dist/{chunk-EE3Z2DJM.js → chunk-DBAPXL44.js} +9 -3
- package/dist/chunk-DBAPXL44.js.map +1 -0
- package/dist/{chunk-GIFN3IPT.js → chunk-L2WQMPWS.js} +74 -20
- package/dist/chunk-L2WQMPWS.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/{login-G6OYPHJL.js → login-WYC24WPA.js} +3 -3
- package/dist/{mcp-servers-FZV2P2ZO.js → mcp-servers-MXS5VAWI.js} +6 -4
- package/dist/{serve-Q2AWZ4S2.js → serve-QRYOK5U4.js} +2412 -1069
- package/dist/{serve-Q2AWZ4S2.js.map → serve-QRYOK5U4.js.map} +1 -1
- package/dist/{start-D4NXCWOI.js → start-6SCOMMSU.js} +4 -4
- package/package.json +3 -2
- package/dist/chunk-EE3Z2DJM.js.map +0 -1
- package/dist/chunk-GIFN3IPT.js.map +0 -1
- /package/dist/{chunk-5B5MBXHW.js.map → chunk-52T3USYD.js.map} +0 -0
- /package/dist/{login-G6OYPHJL.js.map → login-WYC24WPA.js.map} +0 -0
- /package/dist/{mcp-servers-FZV2P2ZO.js.map → mcp-servers-MXS5VAWI.js.map} +0 -0
- /package/dist/{start-D4NXCWOI.js.map → start-6SCOMMSU.js.map} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
DevicePollResponseSchema,
|
|
6
6
|
DeviceStartResponseSchema,
|
|
7
7
|
ROUTES
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DBAPXL44.js";
|
|
9
9
|
import {
|
|
10
10
|
print,
|
|
11
11
|
printError
|
|
@@ -219,4 +219,4 @@ export {
|
|
|
219
219
|
ensureAuthenticated,
|
|
220
220
|
loginCommand
|
|
221
221
|
};
|
|
222
|
-
//# sourceMappingURL=chunk-
|
|
222
|
+
//# sourceMappingURL=chunk-52T3USYD.js.map
|
|
@@ -269,7 +269,13 @@ var MachineCapabilitiesSchema = external_exports.object({
|
|
|
269
269
|
* the stub is sent first with all arrays empty. Defaults to false via Zod
|
|
270
270
|
* for backwards compatibility.
|
|
271
271
|
*/
|
|
272
|
-
detectionComplete: external_exports.boolean().default(false)
|
|
272
|
+
detectionComplete: external_exports.boolean().default(false),
|
|
273
|
+
/**
|
|
274
|
+
* True when the daemon detects Graphite on the machine (gt CLI present OR
|
|
275
|
+
* ~/.config/graphite/user_config contains an authToken). Additive field —
|
|
276
|
+
* defaults to false on older daemons. No PROTOCOL_VERSION bump required.
|
|
277
|
+
*/
|
|
278
|
+
usesGraphite: external_exports.boolean().default(false)
|
|
273
279
|
});
|
|
274
280
|
var LocalDirectAdvertisementSchema = external_exports.object({
|
|
275
281
|
port: external_exports.number().int().min(1).max(65535),
|
|
@@ -1173,7 +1179,7 @@ var PersonalRoomConnection = class {
|
|
|
1173
1179
|
};
|
|
1174
1180
|
|
|
1175
1181
|
// ../../packages/session/src/protocol-version.ts
|
|
1176
|
-
var PROTOCOL_VERSION =
|
|
1182
|
+
var PROTOCOL_VERSION = 69;
|
|
1177
1183
|
|
|
1178
1184
|
// ../../packages/session/src/publish-constants.ts
|
|
1179
1185
|
var PUBLISHED_PREVIEW_KINDS = [
|
|
@@ -1273,4 +1279,4 @@ export {
|
|
|
1273
1279
|
PublishCreateResponseSchema,
|
|
1274
1280
|
ROUTES
|
|
1275
1281
|
};
|
|
1276
|
-
//# sourceMappingURL=chunk-
|
|
1282
|
+
//# sourceMappingURL=chunk-DBAPXL44.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/session/src/schemas.ts","../../../packages/session/src/routes.ts","../../../packages/session/src/claude-code-compat.ts","../../../packages/session/src/collab-room-connection.ts","../../../packages/session/src/personal-room-connection.ts","../../../packages/session/src/protocol-version.ts","../../../packages/session/src/publish-constants.ts"],"sourcesContent":["/**\n * Zod schemas for all signaling server request/response bodies and WebSocket messages.\n *\n * This module provides:\n * - Validation schemas for all HTTP endpoints\n * - WebSocket message schemas for personal and collab rooms\n * - Inferred TypeScript types for type-safe API usage\n * - Error response schemas for consistent error handling\n *\n * This is the single source of truth for all API schemas.\n * The client directory is designed to be self-contained and hoistable\n * to packages/shared without depending on server code.\n *\n * @module client/schemas\n */\n\nimport { z } from 'zod';\n\n/**\n * Pattern for valid git branch names.\n * Shared between daemon validation and web form validation.\n */\nexport const BRANCH_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;\n\n/**\n * Pattern for browser/agent machineId query params on personal-room and\n * collab-room WebSocket upgrades. Used by the server to validate before\n * composing participantIds. Clients below MIN_PROTOCOL_VERSION that omit\n * it will be rejected elsewhere, but the server falls back gracefully.\n */\nexport const MACHINE_ID_PATTERN = /^[\\w-]+$/;\nexport const MACHINE_ID_MAX_LENGTH = 128;\n\n/**\n * Standard error response schema used across all endpoints.\n */\nexport const ErrorResponseSchema = z.object({\n error: z.string(),\n message: z.string(),\n});\n\nexport type ErrorResponse = z.infer<typeof ErrorResponseSchema>;\n\n/**\n * Validation error response with field-level details.\n */\nexport const ValidationErrorResponseSchema = ErrorResponseSchema.extend({\n details: z\n .array(\n z.object({\n path: z.array(z.union([z.string(), z.number()])),\n message: z.string(),\n code: z.string().optional(),\n })\n )\n .optional(),\n});\n\nexport type ValidationErrorResponse = z.infer<typeof ValidationErrorResponseSchema>;\n\n/**\n * Not found error response with available endpoints.\n */\nexport const NotFoundResponseSchema = ErrorResponseSchema.extend({\n endpoints: z.array(z.string()),\n});\n\nexport type NotFoundResponse = z.infer<typeof NotFoundResponseSchema>;\n\n/**\n * GET /health response schema.\n *\n * Returns service health status and environment info.\n */\nexport const HealthResponseSchema = z.object({\n status: z.literal('ok'),\n service: z.literal('shipyard-signaling'),\n environment: z.enum(['development', 'production']),\n});\n\nexport type HealthResponse = z.infer<typeof HealthResponseSchema>;\n\n/**\n * POST /auth/github/callback request body schema.\n *\n * Used to exchange a GitHub OAuth code for a Shipyard JWT.\n */\nexport const AuthGitHubCallbackRequestSchema = z.object({\n /** GitHub OAuth authorization code */\n code: z.string().min(1, 'code is required'),\n /** OAuth redirect URI that was used in the authorize request */\n redirect_uri: z.string().url('redirect_uri must be a valid URL'),\n});\n\nexport type AuthGitHubCallbackRequest = z.infer<typeof AuthGitHubCallbackRequestSchema>;\n\n/**\n * User info returned from successful OAuth.\n */\nexport const OAuthUserSchema = z.object({\n /** Shipyard user ID (\"usr_abc123\") */\n id: z.string(),\n /** Display name */\n displayName: z.string(),\n /** GitHub avatar URL */\n avatarUrl: z.string().nullable(),\n /** Linked providers */\n providers: z.array(z.string()),\n});\n\nexport type OAuthUser = z.infer<typeof OAuthUserSchema>;\n\n/**\n * POST /auth/github/callback response schema.\n *\n * Returns a Shipyard JWT and user info on successful OAuth.\n */\nexport const AuthGitHubCallbackResponseSchema = z.object({\n /** Shipyard JWT for authentication */\n token: z.string(),\n /** User info from GitHub */\n user: OAuthUserSchema,\n /** GitHub access token for API calls (~8h expiry for GitHub App tokens) */\n githubAccessToken: z.string().optional(),\n /** GitHub refresh token for obtaining new access tokens (~6 month expiry) */\n githubRefreshToken: z.string().optional(),\n /** Present and true if request came from a mobile device */\n is_mobile: z.boolean().optional(),\n});\n\nexport type AuthGitHubCallbackResponse = z.infer<typeof AuthGitHubCallbackResponseSchema>;\n\n/**\n * POST /auth/github/refresh request body schema.\n *\n * Exchange a GitHub refresh token for a new access token.\n */\nexport const AuthGitHubRefreshRequestSchema = z.object({\n /** GitHub refresh token from the original OAuth flow */\n refreshToken: z.string().min(1, 'refreshToken is required'),\n});\n\nexport type AuthGitHubRefreshRequest = z.infer<typeof AuthGitHubRefreshRequestSchema>;\n\n/**\n * POST /auth/github/refresh response schema.\n */\nexport const AuthGitHubRefreshResponseSchema = z.object({\n /** New GitHub access token */\n githubAccessToken: z.string(),\n /** New GitHub refresh token (GitHub rotates these on each use) */\n githubRefreshToken: z.string(),\n});\n\nexport type AuthGitHubRefreshResponse = z.infer<typeof AuthGitHubRefreshResponseSchema>;\n\n/**\n * GET /auth/verify response schema.\n *\n * Validates a JWT against the database and returns user info or failure reason.\n */\nexport const AuthVerifyResponseSchema = z.discriminatedUnion('valid', [\n z.object({ valid: z.literal(true), user: OAuthUserSchema }),\n z.object({\n valid: z.literal(false),\n reason: z.enum(['invalid_token', 'user_not_found']),\n }),\n]);\n\nexport type AuthVerifyResponse = z.infer<typeof AuthVerifyResponseSchema>;\n\n/**\n * All valid participant roles in a collab room.\n * Owner is the task creator; collaborator roles come from the invite link.\n */\nexport const ParticipantRoleSchema = z.enum([\n 'owner',\n 'collaborator-full',\n 'collaborator-review',\n 'viewer',\n]);\nexport type ParticipantRole = z.infer<typeof ParticipantRoleSchema>;\n\n/**\n * Collaborator-only roles (subset of ParticipantRole, excludes 'owner').\n * Used for invite link creation where the owner role is never assignable.\n */\nexport const CollaboratorRoleSchema = ParticipantRoleSchema.extract([\n 'collaborator-full',\n 'collaborator-review',\n 'viewer',\n]);\nexport type CollaboratorRole = z.infer<typeof CollaboratorRoleSchema>;\n\n/**\n * POST /collab/create request body schema.\n *\n * Used to create a new collaboration room with a pre-signed URL.\n */\nexport const CollabCreateRequestSchema = z.object({\n /** ID of the task to collaborate on */\n taskId: z.string().min(1, 'taskId is required'),\n /** How long the collaboration link should be valid (1-10080 minutes / 7 days max, default 60) */\n expiresInMinutes: z.number().min(1).max(10080).default(60),\n /** Permission level for the invitee (defaults to 'collaborator-full') */\n role: CollaboratorRoleSchema.optional(),\n});\n\nexport type CollabCreateRequest = z.infer<typeof CollabCreateRequestSchema>;\n\n/**\n * POST /collab/create response schema.\n *\n * Returns the pre-signed WebSocket URL for joining the collaboration room.\n */\nexport const CollabCreateResponseSchema = z.object({\n /** Pre-signed WebSocket URL for joining the room */\n url: z.string().url(),\n /** Unique room identifier */\n roomId: z.string(),\n /** Unix timestamp when the collaboration link expires */\n expiresAt: z.number(),\n});\n\nexport type CollabCreateResponse = z.infer<typeof CollabCreateResponseSchema>;\n\n/**\n * GET /personal/:userId error responses.\n *\n * WebSocket messages are defined below.\n *\n * The actual WebSocket is handled by the Durable Object.\n * These are HTTP error responses before upgrade.\n */\nexport const WsPersonalErrorSchema = z.discriminatedUnion('error', [\n ErrorResponseSchema.extend({\n error: z.literal('upgrade_required'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('missing_token'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('invalid_token'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('forbidden'),\n }),\n]);\n\nexport type WsPersonalError = z.infer<typeof WsPersonalErrorSchema>;\n\n/**\n * GET /collab/:roomId error responses.\n *\n * WebSocket messages are defined below.\n *\n * The actual WebSocket is handled by the Durable Object.\n * These are HTTP error responses before upgrade.\n */\nexport const WsCollabErrorSchema = z.discriminatedUnion('error', [\n ErrorResponseSchema.extend({\n error: z.literal('upgrade_required'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('missing_token'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('invalid_token'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('forbidden'),\n }),\n ErrorResponseSchema.extend({\n error: z.literal('expired'),\n }),\n]);\n\nexport type WsCollabError = z.infer<typeof WsCollabErrorSchema>;\n\n/**\n * Shipyard JWT claims schema.\n *\n * This is the payload embedded in the JWT returned by /auth/github/callback.\n */\nexport const ShipyardJWTClaimsSchema = z.object({\n /** Shipyard user ID: \"usr_abc123\" */\n sub: z.string(),\n /** Display name from primary provider */\n displayName: z.string(),\n /** Linked OAuth providers */\n providers: z.array(z.string()),\n iat: z.number(),\n exp: z.number(),\n /** Optional: Scope for agent tokens (e.g., 'task:abc123') */\n scope: z.string().optional(),\n /** Optional: Machine ID for daemon tokens */\n machineId: z.string().optional(),\n});\n\nexport type ShipyardJWTClaims = z.infer<typeof ShipyardJWTClaimsSchema>;\n\n/**\n * Pre-signed URL payload schema.\n *\n * Embedded in the token query parameter for /collab/:roomId.\n */\nexport const PresignedUrlPayloadSchema = z.object({\n /** Room ID this token is valid for */\n roomId: z.string(),\n /** Task ID being collaborated on */\n taskId: z.string(),\n /** User ID of the person who created the invite */\n inviterId: z.string(),\n /** Expiration timestamp (Unix ms) */\n exp: z.number(),\n /** Permission level for the invitee (defaults to 'collaborator-full') */\n role: CollaboratorRoleSchema.optional(),\n /** Generation counter for link revocation — tokens with a generation older than the room's current generation are rejected */\n generation: z.number().optional(),\n});\n\nexport type PresignedUrlPayload = z.infer<typeof PresignedUrlPayloadSchema>;\n\n/**\n * Reasoning capability schema for models that support configurable reasoning effort.\n */\n/*\n * Keep in sync with ReasoningEffortSchema in packages/loro-schema/src/shared-enums.ts.\n * Inlined here because packages/session has no dependency on @shipyard/loro-schema.\n */\nexport const ReasoningCapabilitySchema = z\n .object({\n efforts: z.array(z.enum(['none', 'low', 'medium', 'high', 'xhigh', 'max'])).min(1),\n defaultEffort: z.enum(['none', 'low', 'medium', 'high', 'xhigh', 'max']),\n })\n .refine((data) => data.efforts.includes(data.defaultEffort), {\n message: 'defaultEffort must be one of the supported efforts',\n });\n\nexport type ReasoningCapability = z.infer<typeof ReasoningCapabilitySchema>;\n\n/**\n * Model info schema for machine capabilities.\n */\nexport const ModelInfoSchema = z.object({\n id: z.string(),\n name: z.string(),\n provider: z.string(),\n description: z.string().optional(),\n reasoning: ReasoningCapabilitySchema.optional(),\n supportsFastMode: z.boolean().optional(),\n});\n\nexport type ModelInfo = z.infer<typeof ModelInfoSchema>;\n\n/**\n * Git repo info schema for machine capabilities.\n */\nexport const GitRepoInfoSchema = z.object({\n path: z.string(),\n name: z.string(),\n branch: z.string(),\n remote: z.string().optional(),\n});\n\nexport type GitRepoInfo = z.infer<typeof GitRepoInfoSchema>;\n\n/**\n * Permission mode schema for machine capabilities.\n */\nexport const PermissionModeSchema = z.enum(['default', 'accept-edits', 'plan', 'bypass', 'auto']);\n\nexport type PermissionMode = z.infer<typeof PermissionModeSchema>;\n\n/**\n * Anthropic auth status — detected from env vars, Claude Code OAuth, or cloud providers.\n */\nconst AnthropicAuthMethodEnum = z.enum([\n 'claude-ai',\n 'console',\n 'api-key',\n 'sso',\n 'gateway',\n 'bedrock',\n 'vertex',\n 'foundry',\n 'none',\n]);\n\nexport const AnthropicAuthStatusSchema = z.object({\n status: z.enum(['authenticated', 'unauthenticated', 'unknown']),\n method: AnthropicAuthMethodEnum,\n email: z.string().optional(),\n orgName: z.string().optional(),\n subscriptionType: z.string().nullable().optional(),\n apiProvider: z.string().optional(),\n authMismatch: z\n .object({\n selected: AnthropicAuthMethodEnum,\n envMethod: AnthropicAuthMethodEnum,\n envVar: z.string(),\n })\n .optional(),\n});\n\nexport type AnthropicAuthStatus = z.infer<typeof AnthropicAuthStatusSchema>;\n\nexport const MCPServerAuthStatusSchema = z.enum(['authenticated', 'unauthenticated', 'unknown']);\nexport type MCPServerAuthStatus = z.infer<typeof MCPServerAuthStatusSchema>;\n\nexport const MCPServerSourceSchema = z.enum([\n 'project',\n 'user',\n 'local',\n 'mcp-json',\n 'plugin',\n 'claudeai',\n]);\nexport type MCPServerSource = z.infer<typeof MCPServerSourceSchema>;\n\nconst MCP_SERVER_TYPES = ['stdio', 'http', 'sse'] as const;\nexport const MCPServerTypeSchema = z.enum(MCP_SERVER_TYPES);\nexport type MCPServerType = z.infer<typeof MCPServerTypeSchema>;\n\nexport const MCPOAuthConfigSchema = z.object({\n clientId: z.string().optional(),\n callbackPort: z.number().optional(),\n});\n\nexport const MCPServerInfoSchema = z.object({\n name: z.string(),\n type: MCPServerTypeSchema.default('stdio'),\n command: z.string().optional(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n url: z.string().optional(),\n headers: z.record(z.string(), z.string()).optional(),\n oauth: MCPOAuthConfigSchema.optional(),\n enabled: z.boolean(),\n source: MCPServerSourceSchema,\n authStatus: MCPServerAuthStatusSchema,\n});\nexport type MCPServerInfo = z.infer<typeof MCPServerInfoSchema>;\n\nexport const SkillInfoSchema = z.object({\n name: z.string(),\n description: z.string(),\n});\nexport type SkillInfo = z.infer<typeof SkillInfoSchema>;\n\n/**\n * Machine capabilities schema — advertised by daemons at registration time.\n */\nexport const MarketplacePluginInfoSchema = z.object({\n name: z.string(),\n description: z.string(),\n author: z.string(),\n marketplace: z.string(),\n installCount: z.number().optional(),\n installed: z.boolean(),\n enabled: z.boolean(),\n version: z.string().optional(),\n isExternal: z.boolean(),\n});\nexport type MarketplacePluginInfo = z.infer<typeof MarketplacePluginInfoSchema>;\n\nexport const ClaudeCodeAgentSchema = z.object({\n id: z.literal('claude-code'),\n name: z.literal('Claude Code'),\n version: z.string().optional(),\n});\n\nexport const InstalledAgentSchema = z.discriminatedUnion('id', [ClaudeCodeAgentSchema]);\nexport type InstalledAgent = z.infer<typeof InstalledAgentSchema>;\n\nexport const MachineCapabilitiesSchema = z.object({\n models: z.array(ModelInfoSchema),\n environments: z.array(GitRepoInfoSchema),\n permissionModes: z.array(PermissionModeSchema),\n homeDir: z.string(),\n anthropicAuth: AnthropicAuthStatusSchema.optional(),\n mcpServers: z.array(MCPServerInfoSchema),\n skills: z.array(SkillInfoSchema),\n marketplacePlugins: z.array(MarketplacePluginInfoSchema),\n autoModeEnabled: z.boolean().optional(),\n installedAgents: z.array(InstalledAgentSchema),\n /**\n * False until `runCapabilityDetection` resolves (success OR failure).\n * Browser banner predicates that treat empty arrays as authoritative-empty\n * MUST gate on this — otherwise they flicker on every reconnect because\n * the stub is sent first with all arrays empty. Defaults to false via Zod\n * for backwards compatibility.\n */\n detectionComplete: z.boolean().default(false),\n /**\n * True when the daemon detects Graphite on the machine (gt CLI present OR\n * ~/.config/graphite/user_config contains an authToken). Additive field —\n * defaults to false on older daemons. No PROTOCOL_VERSION bump required.\n */\n usesGraphite: z.boolean().default(false),\n});\n\nexport type MachineCapabilities = z.infer<typeof MachineCapabilitiesSchema>;\n\n/**\n * Register agent message schema for personal room WebSocket.\n * Capabilities are no longer sent via signaling -- they flow through\n * Loro ephemeral on the room document instead.\n */\n/**\n * Local-direct advertisement — when the daemon is running a LocalDirectServer\n * on a loopback port, it advertises that port + connection token so a browser\n * on the same machine can bypass WebRTC/signaling entirely. Additive, optional.\n *\n * Token is carried here because the browser cannot read the daemon's on-disk\n * advertisement file. Signaling is JWT-authenticated, so a token over this\n * channel is safe for a single-user dev box. For multi-user hosts, audit the\n * threat model before shipping phase 2 (default-on).\n */\nexport const LocalDirectAdvertisementSchema = z.object({\n port: z.number().int().min(1).max(65535),\n token: z.string().min(16),\n});\n\nexport type LocalDirectAdvertisement = z.infer<typeof LocalDirectAdvertisementSchema>;\n\nexport const RegisterAgentSchema = z.object({\n type: z.literal('register-agent'),\n agentId: z.string(),\n machineId: z.string(),\n machineName: z.string(),\n agentType: z.string(),\n protocolVersion: z.number().optional(),\n npmVersion: z.string().optional(),\n localDirect: LocalDirectAdvertisementSchema.optional(),\n});\n\n/**\n * Unregister agent message schema for personal room WebSocket.\n */\nexport const UnregisterAgentSchema = z.object({\n type: z.literal('unregister-agent'),\n agentId: z.string(),\n});\n\n/**\n * Agent status update message schema for personal room WebSocket.\n */\nexport const AgentStatusSchema = z.object({\n type: z.literal('agent-status'),\n agentId: z.string(),\n status: z.enum(['idle', 'running', 'error']),\n activeTaskId: z.string().optional(),\n});\n\n/**\n * WebRTC offer message schema for personal room WebSocket.\n *\n * When sent by a client: `targetMachineId` is the intended recipient.\n * When relayed by the server: `fromMachineId` is added to identify the sender.\n *\n * `generationId` is a UUID minted by the offerer when it constructs the\n * RTCPeerConnection. It flows through the answer and every subsequent ICE\n * candidate for that PC so both sides can discard messages belonging to a\n * prior generation after a teardown-and-replace cycle. Without it, stale\n * candidates in flight across the signaling relay silently contaminate the\n * successor PC's ICE state.\n */\nexport const WebRTCOfferSchema = z.object({\n type: z.literal('webrtc-offer'),\n targetMachineId: z.string(),\n fromMachineId: z.string().optional(),\n generationId: z.string(),\n offer: z.unknown(),\n requestId: z.string().optional(),\n});\n\n/**\n * WebRTC answer message schema for personal room WebSocket.\n *\n * When sent by a client: `targetMachineId` is the intended recipient.\n * When relayed by the server: `fromMachineId` is added to identify the sender.\n *\n * `generationId` echoes the offerer's generationId so the offerer can\n * discard answers that don't correspond to its current PC.\n */\nexport const WebRTCAnswerSchema = z.object({\n type: z.literal('webrtc-answer'),\n targetMachineId: z.string(),\n fromMachineId: z.string().optional(),\n generationId: z.string(),\n answer: z.unknown(),\n requestId: z.string().optional(),\n});\n\n/**\n * WebRTC ICE candidate message schema for personal room WebSocket.\n *\n * When sent by a client: `targetMachineId` is the intended recipient.\n * When relayed by the server: `fromMachineId` is added to identify the sender.\n *\n * `generationId` identifies the PC that emitted this candidate. Receivers\n * drop candidates whose generationId doesn't match the PC currently tracked\n * for the sender.\n */\nexport const WebRTCIceSchema = z.object({\n type: z.literal('webrtc-ice'),\n targetMachineId: z.string(),\n fromMachineId: z.string().optional(),\n generationId: z.string(),\n candidate: z.unknown(),\n});\n\n/**\n * ICE server entry schema (matches RTCIceServer).\n */\nexport const IceServerSchema = z.object({\n urls: z.union([z.string(), z.array(z.string())]),\n username: z.string().optional(),\n credential: z.string().optional(),\n});\n\nexport type IceServer = z.infer<typeof IceServerSchema>;\n\n/**\n * Default ICE servers used when the server has not provided TURN credentials.\n * Centralized here so all consumers (browser, daemon, session-server) share\n * one source of truth for the fallback STUN URL.\n */\nexport const DEFAULT_ICE_SERVERS: IceServer[] = [{ urls: 'stun:stun.cloudflare.com:3478' }];\n\n/**\n * Server→client message delivering ICE server configuration (STUN + TURN).\n * Sent after authentication so clients have TURN credentials before any\n * peer connections are created.\n */\nexport const IceServersSchema = z.object({\n type: z.literal('ice-servers'),\n iceServers: z.array(IceServerSchema),\n});\n\n/**\n * Error message schema for WebSocket connections.\n */\nexport const ErrorMessageSchema = z.object({\n type: z.literal('error'),\n code: z.string(),\n message: z.string(),\n requestId: z.string().optional(),\n});\n\n/**\n * User settings schema — stored in CRDT, synced across daemons.\n */\nexport const UserSettingsSchema = z.object({\n worktreeSetupScript: z.string().nullable(),\n});\n\nexport type UserSettings = z.infer<typeof UserSettingsSchema>;\n\n/**\n * Browser requests daemon self-update via signaling relay.\n * Follows the WebRTC relay pattern: browser sends with targetMachineId,\n * server relays to daemon with fromMachineId added.\n */\nexport const RequestDaemonUpdateSchema = z.object({\n type: z.literal('request-daemon-update'),\n targetMachineId: z.string(),\n fromMachineId: z.string().optional(),\n installCommand: z.string(),\n});\n\nexport type RequestDaemonUpdate = z.infer<typeof RequestDaemonUpdateSchema>;\n\n/**\n * Daemon reports self-update progress via signaling relay.\n * Sent by daemon, server broadcasts to all browser connections.\n *\n * Status lifecycle — daemon-as-stager model:\n * downloading → downloaded → handoff → (daemon exits) → installing →\n * verifying → restarting → (new daemon boots) → succeeded|rolled-back|failed\n * The succeeded transition is reported by the NEW daemon on first authenticated\n * browser connection (see readAndClearUpdateStatus).\n */\nexport const DaemonUpdateStatusSchema = z.object({\n type: z.literal('daemon-update-status'),\n fromMachineId: z.string().optional(),\n status: z.enum([\n 'downloading',\n 'downloaded',\n 'handoff',\n 'installing',\n 'verifying',\n 'restarting',\n 'succeeded',\n 'rolled-back',\n 'failed',\n ]),\n error: z.string().optional(),\n targetVersion: z.string().optional(),\n previousVersion: z.string().optional(),\n});\n\nexport type DaemonUpdateStatus = z.infer<typeof DaemonUpdateStatusSchema>;\n\n/**\n * Union of all client-to-server messages for personal room WebSocket.\n */\nexport const PersonalRoomClientMessageSchema = z.discriminatedUnion('type', [\n RegisterAgentSchema,\n UnregisterAgentSchema,\n AgentStatusSchema,\n WebRTCOfferSchema,\n WebRTCAnswerSchema,\n WebRTCIceSchema,\n ErrorMessageSchema,\n RequestDaemonUpdateSchema,\n DaemonUpdateStatusSchema,\n]);\n\nexport type PersonalRoomClientMessage = z.infer<typeof PersonalRoomClientMessageSchema>;\n\n/**\n * Authentication success message schema for personal room WebSocket.\n */\nexport const AuthenticatedSchema = z.object({\n type: z.literal('authenticated'),\n userId: z.string(),\n username: z.string(),\n});\n\n/**\n * Agent info schema for personal room WebSocket.\n * Capabilities are no longer included -- they flow through Loro ephemeral.\n */\nexport const AgentInfoSchema = z.object({\n agentId: z.string(),\n machineId: z.string(),\n machineName: z.string(),\n agentType: z.string(),\n status: z.enum(['idle', 'running', 'error']),\n activeTaskId: z.string().optional(),\n protocolVersion: z.number().optional(),\n npmVersion: z.string().optional(),\n localDirect: LocalDirectAdvertisementSchema.optional(),\n});\n\nexport type AgentInfo = z.infer<typeof AgentInfoSchema>;\n\n/**\n * Agents list message schema for personal room WebSocket.\n */\nexport const AgentsListSchema = z.object({\n type: z.literal('agents-list'),\n agents: z.array(AgentInfoSchema),\n hasDaemonRegistered: z.boolean().optional(),\n});\n\n/**\n * Agent joined notification message schema for personal room WebSocket.\n */\nexport const AgentJoinedSchema = z.object({\n type: z.literal('agent-joined'),\n agent: AgentInfoSchema,\n});\n\n/**\n * Agent left notification message schema for personal room WebSocket.\n *\n * `connectionId` is minted by the PersonalRoom DO at WebSocket accept and\n * mirrored back on `agent-left` so daemons that route this message can\n * discriminate stale teardown events from a prior session (Invariant #2a).\n * Without it, a delayed `agent-left` arriving after the same agent has\n * re-registered would tear down the freshly-established peer.\n */\nexport const AgentLeftSchema = z.object({\n type: z.literal('agent-left'),\n agentId: z.string(),\n connectionId: z.string(),\n});\n\n/**\n * Browser left notification message schema for personal room WebSocket.\n *\n * Mirrors `agent-left` for browser sessions. The DO emits this on\n * `webSocketClose` for browser connections so the daemon can tear down\n * its WebRTC peer immediately rather than waiting for ICE timeout (~30s).\n *\n * `connectionId` is the DO-minted per-WebSocket discriminator used to\n * suppress stale leaves when the same machineId has reconnected with a\n * newer session (Invariant #2a).\n */\nexport const BrowserLeftSchema = z.object({\n type: z.literal('browser-left'),\n machineId: z.string(),\n connectionId: z.string(),\n});\n\n/**\n * Agent status changed notification message schema for personal room WebSocket.\n */\nexport const AgentStatusChangedSchema = z.object({\n type: z.literal('agent-status-changed'),\n agentId: z.string(),\n status: z.enum(['idle', 'running', 'error']),\n activeTaskId: z.string().optional(),\n /**\n * Mirror the daemon's local-direct advert on status updates so browsers\n * joining mid-session (or recovering from a signaling reconnect) always\n * see the authoritative port/token. The browser's status-merge path\n * preserves prior-frame fields via spread; making this explicit avoids\n * relying on that incidental behavior.\n */\n localDirect: LocalDirectAdvertisementSchema.optional(),\n});\n\n/**\n * Webhook event schema for events ingested from external services (e.g. Linear)\n * and forwarded to connected daemons via the PersonalRoom WebSocket.\n */\nexport const WebhookEventSchema = z.object({\n type: z.literal('webhook-event'),\n source: z.string(),\n event: z.string(),\n payload: z.record(z.string(), z.unknown()),\n timestamp: z.number(),\n});\n\nexport type WebhookEvent = z.infer<typeof WebhookEventSchema>;\n\n/**\n * Union of all server-to-client messages for personal room WebSocket.\n */\nexport const PersonalRoomServerMessageSchema = z.discriminatedUnion('type', [\n AuthenticatedSchema,\n AgentsListSchema,\n AgentJoinedSchema,\n AgentLeftSchema,\n BrowserLeftSchema,\n AgentStatusChangedSchema,\n ErrorMessageSchema,\n WebRTCOfferSchema,\n WebRTCAnswerSchema,\n WebRTCIceSchema,\n IceServersSchema,\n WebhookEventSchema,\n RequestDaemonUpdateSchema,\n DaemonUpdateStatusSchema,\n]);\n\nexport type PersonalRoomServerMessage = z.infer<typeof PersonalRoomServerMessageSchema>;\n\n/**\n * WebRTC offer message schema for collab room WebSocket.\n *\n * `generationId` is minted per-PeerConnection by the offerer so both sides\n * can discriminate messages from a prior PC generation after teardown and\n * replacement. Mirrors the personal-room pattern; without it, stale ICE or\n * answers from a pre-reconnect PC silently contaminate the successor PC's\n * state (see PR #2244 and the 2026-04-22 collab-refresh deadlock incident).\n */\nexport const CollabWebRTCOfferSchema = z.object({\n type: z.literal('webrtc-offer'),\n targetUserId: z.string(),\n fromUserId: z.string().optional(),\n generationId: z.string(),\n offer: z.unknown(),\n});\n\n/**\n * WebRTC answer message schema for collab room WebSocket.\n *\n * Echoes the offerer's `generationId` so the offerer can drop answers that\n * don't correspond to its current PC.\n */\nexport const CollabWebRTCAnswerSchema = z.object({\n type: z.literal('webrtc-answer'),\n targetUserId: z.string(),\n fromUserId: z.string().optional(),\n generationId: z.string(),\n answer: z.unknown(),\n});\n\n/**\n * WebRTC ICE candidate message schema for collab room WebSocket.\n *\n * `generationId` identifies the PC that emitted this candidate. Receivers\n * drop candidates whose generationId doesn't match the PC currently tracked\n * for the sender.\n */\nexport const CollabWebRTCIceSchema = z.object({\n type: z.literal('webrtc-ice'),\n targetUserId: z.string(),\n fromUserId: z.string().optional(),\n generationId: z.string(),\n candidate: z.unknown(),\n});\n\n/**\n * Union of all client-to-server messages for collab room WebSocket.\n */\nexport const CollabRoomClientMessageSchema = z.discriminatedUnion('type', [\n CollabWebRTCOfferSchema,\n CollabWebRTCAnswerSchema,\n CollabWebRTCIceSchema,\n]);\n\nexport type CollabRoomClientMessage = z.infer<typeof CollabRoomClientMessageSchema>;\n\n/**\n * Participant info schema for collab room WebSocket.\n *\n * `connectionId` is minted by the CollabRoom DO at each WebSocket accept\n * (per-connection, opaque to clients). Clients track it per-userId so they\n * can discard stale `participant-left` events from a prior session that\n * arrive after the same user has rejoined with a newer connection — the\n * root cause of the 2026-04-22 collab-refresh deadlock. Consumers MUST\n * record the latest connectionId they've seen for each userId and match\n * against incoming leave events before tearing down peer state.\n */\nexport const ParticipantSchema = z.object({\n userId: z.string(),\n username: z.string(),\n avatarUrl: z.string().nullable().optional(),\n role: ParticipantRoleSchema,\n connectionId: z.string(),\n /**\n * True when this participant is the collab room's WebRTC hub — the sole\n * peer that other browsers open a PeerConnection to. Always derived by\n * the CollabRoom DO from `role === 'owner'` at message emission time,\n * never stored independently. Browsers MUST find the hub via this flag\n * (not by inspecting role) so the rule lives in one place server-side.\n * See AGENTS.md Invariant #15.\n */\n isHub: z.boolean(),\n});\n\nexport type Participant = z.infer<typeof ParticipantSchema>;\n\n/**\n * Authentication success message schema for collab room WebSocket.\n */\nexport const CollabAuthenticatedSchema = z.object({\n type: z.literal('authenticated'),\n userId: z.string(),\n username: z.string(),\n taskId: z.string(),\n});\n\n/**\n * Participants list message schema for collab room WebSocket.\n */\nexport const ParticipantsListSchema = z.object({\n type: z.literal('participants-list'),\n participants: z.array(ParticipantSchema),\n});\n\n/**\n * Participant joined notification message schema for collab room WebSocket.\n */\nexport const ParticipantJoinedSchema = z.object({\n type: z.literal('participant-joined'),\n participant: ParticipantSchema,\n});\n\n/**\n * Participant left notification message schema for collab room WebSocket.\n *\n * `connectionId` identifies WHICH connection left. When a user refreshes,\n * the DO's webSocketClose may fire after the refreshed tab has already\n * joined with a new connection — consumers drop a leave whose connectionId\n * doesn't match the latest one tracked for that userId. This is the\n * stale-disconnect discriminator that prevents ghost-teardown of the\n * freshly-established peer.\n */\nexport const ParticipantLeftSchema = z.object({\n type: z.literal('participant-left'),\n userId: z.string(),\n connectionId: z.string(),\n});\n\n/**\n * Union of all server-to-client messages for collab room WebSocket.\n */\nexport const CollabRoomServerMessageSchema = z.discriminatedUnion('type', [\n CollabAuthenticatedSchema,\n ParticipantsListSchema,\n ParticipantJoinedSchema,\n ParticipantLeftSchema,\n ErrorMessageSchema,\n CollabWebRTCOfferSchema,\n CollabWebRTCAnswerSchema,\n CollabWebRTCIceSchema,\n IceServersSchema,\n]);\n\nexport type CollabRoomServerMessage = z.infer<typeof CollabRoomServerMessageSchema>;\n\n/**\n * GitHub issue type for feedback categorization.\n */\nexport const GitHubIssueTypeSchema = z.enum(['bug', 'feature', 'question', 'praise']);\nexport type GitHubIssueType = z.infer<typeof GitHubIssueTypeSchema>;\n\n/**\n * POST /feedback/issue request body schema.\n */\nexport const GitHubIssueCreateRequestSchema = z.object({\n type: GitHubIssueTypeSchema,\n title: z.string().min(1, 'title is required').max(256, 'title must be 256 chars or fewer'),\n description: z.string().max(10000).optional(),\n logs: z.string().max(50000).optional(),\n context: z\n .object({\n url: z.string().optional(),\n taskId: z.string().optional(),\n userAgent: z.string().optional(),\n timestamp: z.string().optional(),\n })\n .optional(),\n});\n\nexport type GitHubIssueCreateRequest = z.infer<typeof GitHubIssueCreateRequestSchema>;\n\n/**\n * POST /feedback/issue response schema.\n */\nexport const GitHubIssueCreateResponseSchema = z.object({\n issueUrl: z.string(),\n issueNumber: z.number(),\n});\n\nexport type GitHubIssueCreateResponse = z.infer<typeof GitHubIssueCreateResponseSchema>;\n\n/**\n * POST /auth/linear/callback request body schema.\n *\n * Used to exchange a Linear OAuth code for Linear access tokens.\n * Requires an authenticated Shipyard JWT.\n */\nexport const AuthLinearCallbackRequestSchema = z.object({\n /** Linear OAuth authorization code */\n code: z.string().min(1, 'code is required'),\n /** OAuth redirect URI that was used in the authorize request */\n redirect_uri: z.string().url('redirect_uri must be a valid URL'),\n});\n\nexport type AuthLinearCallbackRequest = z.infer<typeof AuthLinearCallbackRequestSchema>;\n\n/**\n * POST /auth/linear/callback response schema.\n *\n * Returns Linear OAuth tokens and user info on successful OAuth.\n */\nexport const AuthLinearCallbackResponseSchema = z.object({\n /** Linear access token */\n linearAccessToken: z.string(),\n /** Seconds until the access token expires (from Linear's token response) */\n expiresIn: z.number().optional(),\n /** Linear user info */\n linearUser: z.object({\n id: z.string(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n avatarUrl: z.string().nullable(),\n }),\n});\n\nexport type AuthLinearCallbackResponse = z.infer<typeof AuthLinearCallbackResponseSchema>;\n\n/** POST /auth/device/start — no request body needed */\nexport const DeviceStartResponseSchema = z.object({\n deviceCode: z.string(),\n userCode: z.string(),\n verificationUri: z.string().url(),\n expiresIn: z.number(),\n interval: z.number(),\n});\nexport type DeviceStartResponse = z.infer<typeof DeviceStartResponseSchema>;\n\n/** POST /auth/device/poll request */\nexport const DevicePollRequestSchema = z.object({\n deviceCode: z.string().min(1, 'deviceCode is required'),\n});\nexport type DevicePollRequest = z.infer<typeof DevicePollRequestSchema>;\n\n/** POST /auth/device/poll response (success) */\nexport const DevicePollResponseSchema = z.object({\n token: z.string(),\n user: OAuthUserSchema,\n});\nexport type DevicePollResponse = z.infer<typeof DevicePollResponseSchema>;\n\n/** POST /auth/device/poll response (pending/errors) */\nexport const DevicePollPendingSchema = z.object({\n error: z.enum(['authorization_pending', 'slow_down', 'expired_token']),\n});\nexport type DevicePollPending = z.infer<typeof DevicePollPendingSchema>;\n\n/** POST /auth/device/authorize — browser authorizes a device using its own JWT */\nexport const DeviceAuthorizeRequestSchema = z.object({\n userCode: z.string().min(1, 'userCode is required'),\n});\nexport type DeviceAuthorizeRequest = z.infer<typeof DeviceAuthorizeRequestSchema>;\n\nexport const DeviceAuthorizeResponseSchema = z.object({\n authorized: z.boolean(),\n});\nexport type DeviceAuthorizeResponse = z.infer<typeof DeviceAuthorizeResponseSchema>;\n\n/** POST /auth/device/exchange-code — daemon exchanges userCode for deviceCode */\nexport const DeviceExchangeCodeRequestSchema = z.object({\n userCode: z.string().min(1, 'userCode is required'),\n});\nexport type DeviceExchangeCodeRequest = z.infer<typeof DeviceExchangeCodeRequestSchema>;\n\nexport const DeviceExchangeCodeResponseSchema = z.object({\n deviceCode: z.string(),\n});\nexport type DeviceExchangeCodeResponse = z.infer<typeof DeviceExchangeCodeResponseSchema>;\n\n/** POST /auth/browser-code — daemon requests a one-time code for browser auth */\nexport const BrowserCodeResponseSchema = z.object({\n code: z.string(),\n expiresIn: z.number(),\n});\nexport type BrowserCodeResponse = z.infer<typeof BrowserCodeResponseSchema>;\n\n/** POST /auth/browser-code/exchange — browser exchanges code for JWT */\nexport const BrowserCodeExchangeRequestSchema = z.object({\n code: z.string().min(1, 'code is required'),\n});\nexport type BrowserCodeExchangeRequest = z.infer<typeof BrowserCodeExchangeRequestSchema>;\n\nexport const BrowserCodeExchangeResponseSchema = z.object({\n token: z.string(),\n user: OAuthUserSchema,\n});\nexport type BrowserCodeExchangeResponse = z.infer<typeof BrowserCodeExchangeResponseSchema>;\n\n/** GET /vault/key response */\nexport const VaultKeyGetResponseSchema = z.object({\n encryptedKey: z.string(),\n createdAt: z.number(),\n});\nexport type VaultKeyGetResponse = z.infer<typeof VaultKeyGetResponseSchema>;\n\n/** PUT /vault/key request */\nexport const VaultKeyPutRequestSchema = z.object({\n encryptedKey: z.string().min(1, 'encryptedKey is required').max(4096),\n});\nexport type VaultKeyPutRequest = z.infer<typeof VaultKeyPutRequestSchema>;\n\n/** PUT /vault/key response (created) */\nexport const VaultKeyPutResponseSchema = z.object({\n created: z.boolean(),\n});\nexport type VaultKeyPutResponse = z.infer<typeof VaultKeyPutResponseSchema>;\n\n/** Metrics ingestion */\n\nexport const MetricsEventSchema = z.object({\n eventType: z.string().min(1).max(100),\n taskId: z.string().max(200).optional(),\n payload: z.record(z.unknown()).default({}),\n clientTimestamp: z.number().int().positive(),\n});\nexport type MetricsEvent = z.infer<typeof MetricsEventSchema>;\n\nexport const MetricsIngestRequestSchema = z.object({\n events: z.array(MetricsEventSchema).min(1).max(100),\n});\nexport type MetricsIngestRequest = z.infer<typeof MetricsIngestRequestSchema>;\n\nexport const MetricsIngestResponseSchema = z.object({\n accepted: z.number(),\n});\nexport type MetricsIngestResponse = z.infer<typeof MetricsIngestResponseSchema>;\n","/**\n * Shared route constants for the signaling server.\n *\n * Used by both server (route registration) and client (request URLs).\n * Single source of truth prevents drift between implementations.\n *\n * @module client/routes\n */\nexport const ROUTES = {\n HEALTH: '/health',\n AUTH_GITHUB_CALLBACK: '/auth/github/callback',\n AUTH_GITHUB_REFRESH: '/auth/github/refresh',\n AUTH_DEVICE_START: '/auth/device/start',\n AUTH_DEVICE_VERIFY: '/auth/device/verify',\n AUTH_DEVICE_POLL: '/auth/device/poll',\n AUTH_DEVICE_AUTHORIZE: '/auth/device/authorize',\n AUTH_DEVICE_EXCHANGE_CODE: '/auth/device/exchange-code',\n AUTH_BROWSER_CODE: '/auth/browser-code',\n AUTH_BROWSER_CODE_EXCHANGE: '/auth/browser-code/exchange',\n AUTH_VERIFY: '/auth/verify',\n COLLAB_CREATE: '/collab/create',\n COLLAB_REVOKE: '/collab/:roomId',\n GITHUB_ISSUE_CREATE: '/feedback/issue',\n VAULT_KEY: '/vault/key',\n AUTH_LINEAR: '/auth/linear',\n AUTH_LINEAR_CALLBACK: '/auth/linear/callback',\n WEBHOOK_INGEST: '/webhooks/:userId/:source',\n WS_PERSONAL: '/personal/:userId',\n WS_COLLAB: '/collab/:roomId',\n METRICS_INGEST: '/ingest',\n METRICS_QUERY_EVENTS: '/query/events',\n PUBLISH_UPLOAD: '/publish',\n PUBLISH_ITEM: '/publish/:id',\n PUBLISH_MINE: '/publish/mine',\n} as const;\n\n/**\n * Human-readable endpoint descriptions for documentation and error messages.\n */\nexport const ROUTE_DESCRIPTIONS = [\n `GET ${ROUTES.HEALTH}`,\n `POST ${ROUTES.AUTH_GITHUB_CALLBACK}`,\n `POST ${ROUTES.AUTH_GITHUB_REFRESH}`,\n `POST ${ROUTES.AUTH_DEVICE_START}`,\n `GET ${ROUTES.AUTH_DEVICE_VERIFY}`,\n `POST ${ROUTES.AUTH_DEVICE_POLL}`,\n `POST ${ROUTES.AUTH_DEVICE_AUTHORIZE}`,\n `POST ${ROUTES.AUTH_DEVICE_EXCHANGE_CODE}`,\n `POST ${ROUTES.AUTH_BROWSER_CODE}`,\n `POST ${ROUTES.AUTH_BROWSER_CODE_EXCHANGE}`,\n `GET ${ROUTES.AUTH_VERIFY}`,\n `POST ${ROUTES.COLLAB_CREATE}`,\n `DELETE ${ROUTES.COLLAB_REVOKE}`,\n `POST ${ROUTES.GITHUB_ISSUE_CREATE}`,\n `GET ${ROUTES.VAULT_KEY}`,\n `PUT ${ROUTES.VAULT_KEY}`,\n `GET ${ROUTES.AUTH_LINEAR}`,\n `GET ${ROUTES.AUTH_LINEAR_CALLBACK}`,\n `POST ${ROUTES.WEBHOOK_INGEST}`,\n `WS ${ROUTES.WS_PERSONAL}`,\n `WS ${ROUTES.WS_COLLAB}`,\n `POST ${ROUTES.METRICS_INGEST}`,\n `GET ${ROUTES.METRICS_QUERY_EVENTS}`,\n `POST ${ROUTES.PUBLISH_UPLOAD}`,\n `PUT ${ROUTES.PUBLISH_ITEM}`,\n `DELETE ${ROUTES.PUBLISH_ITEM}`,\n `GET ${ROUTES.PUBLISH_MINE}`,\n] as const;\n","/**\n * Claude Code version compatibility for the Shipyard daemon.\n *\n * The SDK ships a per-platform native `claude` binary via optional\n * dependencies (`@anthropic-ai/claude-agent-sdk-${platform}-${arch}`).\n * The user's global `claude` install is NOT what Shipyard spawns.\n * Pinning the SDK version effectively pins Claude Code. This module\n * provides observability: when the init message reports a runtime\n * version, we compare it against what we tested.\n */\n\n/** The Claude Code version bundled with the currently pinned SDK (0.2.120). */\nexport const TESTED_CLAUDE_CODE_VERSION = '2.1.120';\n\nexport type ClaudeCodeCompatResult =\n | { status: 'compatible' }\n | { status: 'untested'; detected: string; tested: string }\n | { status: 'unknown' };\n\n/**\n * Compare a detected Claude Code runtime version against the tested version.\n *\n * - `compatible`: major.minor matches the tested version\n * - `untested`: version detected but major.minor differs\n * - `unknown`: version could not be determined\n */\nexport function classifyClaudeCodeCompatibility(\n detected: string | undefined,\n tested: string = TESTED_CLAUDE_CODE_VERSION\n): ClaudeCodeCompatResult {\n if (!detected) return { status: 'unknown' };\n\n const detectedParts = detected.split('.');\n const testedParts = tested.split('.');\n\n if (detectedParts[0] === testedParts[0] && detectedParts[1] === testedParts[1]) {\n return { status: 'compatible' };\n }\n\n return { status: 'untested', detected, tested };\n}\n","import type { ConnectionState } from './personal-room-connection';\nimport type { CollabRoomClientMessage, CollabRoomServerMessage } from './schemas';\nimport { CollabRoomServerMessageSchema } from './schemas';\n\ninterface MinimalWebSocket {\n onopen: ((ev: unknown) => void) | null;\n onclose: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: string }) => void) | null;\n send(data: string): void;\n close(): void;\n}\n\nexport interface CollabRoomConnectionConfig {\n url: string;\n WebSocketImpl?: new (url: string) => MinimalWebSocket;\n maxRetries?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n backoffMultiplier?: number;\n /** Called when the server closes the connection with code 1000 (\"Room expired\"). Do not reconnect. */\n onRoomExpired?: () => void;\n}\n\nexport class CollabRoomConnection {\n private state: ConnectionState = 'disconnected';\n private ws: MinimalWebSocket | null = null;\n private readonly messageHandlers = new Set<(msg: CollabRoomServerMessage) => void>();\n private readonly stateChangeHandlers = new Set<(state: ConnectionState) => void>();\n private readonly config: CollabRoomConnectionConfig;\n private retryCount = 0;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private pingTimer: ReturnType<typeof setTimeout> | null = null;\n private intentionalDisconnect = false;\n\n constructor(config: CollabRoomConnectionConfig) {\n this.config = config;\n }\n\n getState(): ConnectionState {\n return this.state;\n }\n\n connect(): void {\n this.intentionalDisconnect = false;\n\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n\n if (this.ws) {\n this.stopPingTimer();\n this.ws.onopen = null;\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close();\n this.ws = null;\n }\n\n const Impl = this.config.WebSocketImpl ?? (WebSocket as never);\n try {\n this.ws = new Impl(this.config.url);\n } catch {\n this.setState('error');\n this.reconnectWithBackoff();\n return;\n }\n this.setState('connecting');\n\n this.ws.onopen = () => {\n this.setState('connected');\n this.startPingTimer();\n };\n\n this.ws.onclose = (ev: unknown) => {\n this.stopPingTimer();\n if (this.state !== 'error' && this.state !== 'reconnecting') {\n this.setState('disconnected');\n }\n const code = extractCloseCode(ev);\n /** NOTE: 1000 = \"Room expired\" — the Durable Object TTL has elapsed. Stop reconnecting and notify the caller. */\n if (code === 1000) {\n this.config.onRoomExpired?.();\n return;\n }\n /** NOTE: 4001 = \"Replaced by new connection\" — a newer WebSocket for the same userId was accepted by the server. Do not reconnect; the replacement is already active. */\n if (!this.intentionalDisconnect && code !== 4001) {\n this.reconnectWithBackoff();\n }\n };\n\n this.ws.onerror = () => {\n this.stopPingTimer();\n this.setState('error');\n if (!this.intentionalDisconnect) {\n this.reconnectWithBackoff();\n }\n };\n\n this.ws.onmessage = (event: { data: string }) => {\n this.startPingTimer();\n\n if (event.data === 'pong') return;\n\n let raw: unknown;\n try {\n raw = JSON.parse(event.data);\n } catch {\n return;\n }\n\n const result = CollabRoomServerMessageSchema.safeParse(raw);\n if (!result.success) {\n // biome-ignore lint/suspicious/noConsole: intentional debug visibility for silent Zod drops that hid a critical share-link bug\n console.warn(\n '[CollabRoom] Dropped unrecognized server message:',\n JSON.stringify(raw),\n result.error.issues\n );\n return;\n }\n\n for (const handler of [...this.messageHandlers]) {\n handler(result.data);\n }\n };\n }\n\n send(msg: CollabRoomClientMessage): void {\n try {\n this.ws?.send(JSON.stringify(msg));\n } catch {\n /** WebSocket may be in CLOSING/CLOSED state */\n }\n }\n\n onMessage(handler: (msg: CollabRoomServerMessage) => void): () => void {\n this.messageHandlers.add(handler);\n return () => {\n this.messageHandlers.delete(handler);\n };\n }\n\n onStateChange(handler: (state: ConnectionState) => void): () => void {\n this.stateChangeHandlers.add(handler);\n return () => {\n this.stateChangeHandlers.delete(handler);\n };\n }\n\n reconnect(): void {\n this.intentionalDisconnect = false;\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.retryCount = 0;\n this.connect();\n }\n\n disconnect(): void {\n this.intentionalDisconnect = true;\n this.stopPingTimer();\n\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n\n this.retryCount = 0;\n\n if (this.ws) {\n this.ws.onopen = null;\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close();\n this.ws = null;\n }\n this.setState('disconnected');\n }\n\n private startPingTimer(): void {\n this.stopPingTimer();\n this.schedulePing();\n }\n\n private stopPingTimer(): void {\n if (this.pingTimer !== null) {\n clearTimeout(this.pingTimer);\n this.pingTimer = null;\n }\n }\n\n private schedulePing(): void {\n const jitter = Math.floor(Math.random() * 25_000);\n const delay = 30_000 + jitter;\n this.pingTimer = setTimeout(() => {\n this.pingTimer = null;\n try {\n this.ws?.send('ping');\n } catch {\n /** WebSocket may be in CLOSING/CLOSED state */\n }\n this.schedulePing();\n }, delay);\n }\n\n private reconnectWithBackoff(): void {\n if (this.retryTimer !== null) return;\n\n const maxRetries = this.config.maxRetries;\n if (maxRetries === undefined || maxRetries === 0) {\n return;\n }\n\n if (maxRetries !== -1 && this.retryCount >= maxRetries) {\n return;\n }\n\n this.setState('reconnecting');\n\n if (this.intentionalDisconnect) return;\n\n const initialDelay = this.config.initialDelayMs ?? 1000;\n const maxDelay = this.config.maxDelayMs ?? 30000;\n const multiplier = this.config.backoffMultiplier ?? 2;\n const delay = Math.min(initialDelay * multiplier ** this.retryCount, maxDelay);\n\n this.retryCount++;\n\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.connect();\n }, delay);\n }\n\n private setState(newState: ConnectionState): void {\n this.state = newState;\n if (newState === 'connected') {\n this.retryCount = 0;\n }\n for (const handler of [...this.stateChangeHandlers]) {\n handler(newState);\n }\n }\n}\n\nfunction extractCloseCode(ev: unknown): number | undefined {\n if (typeof ev !== 'object' || ev === null || !('code' in ev)) return undefined;\n const code: unknown = ev.code;\n return typeof code === 'number' ? code : undefined;\n}\n","import type { PersonalRoomClientMessage, PersonalRoomServerMessage } from './schemas';\nimport { PersonalRoomServerMessageSchema } from './schemas';\n\nexport type ConnectionState =\n | 'connecting'\n | 'connected'\n | 'disconnected'\n | 'reconnecting'\n | 'error';\n\ninterface MinimalWebSocket {\n onopen: ((ev: unknown) => void) | null;\n onclose: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: string }) => void) | null;\n send(data: string): void;\n close(): void;\n}\n\nexport interface PersonalRoomConnectionConfig {\n url: string;\n WebSocketImpl?: new (url: string) => MinimalWebSocket;\n /**\n * -1 = retry forever (geometric ramp then slow-retry every `slowRetryDelayMs`);\n * 0 / undefined = no auto-reconnect (legacy backwards-compat default);\n * N>0 = stop after N attempts (legacy bounded retry, used by older callers).\n *\n * Cluster A8 introduces the slow-retry-forever path (`-1`) — no terminal\n * `failed` state. The bounded variant is preserved so existing tests and\n * callers don't break.\n */\n maxRetries?: number;\n /**\n * Geometric backoff schedule used for the first attempts. After the\n * schedule is exhausted, retries fire every `slowRetryDelayMs` indefinitely\n * (no terminal `failed` state — Cluster A8). Tests may inject a custom\n * schedule for determinism.\n *\n * Defaults to `[1000, 2000, 4000, 8000, 15000, 30000]` per the spec.\n */\n backoffSchedule?: readonly number[];\n /**\n * Slow-retry delay used after the geometric schedule is exhausted.\n * Defaults to 30_000 ms.\n */\n slowRetryDelayMs?: number;\n /**\n * Proactive heartbeat configuration. Defaults: ping every 30s, expect pong\n * within 60s. Half-open WS detection (Cluster A4): if no pong arrives in\n * `pongTimeoutMs` after ping send, force-close the WS to trigger reconnect.\n */\n pingIntervalMs?: number;\n pongTimeoutMs?: number;\n /** @deprecated Legacy fields preserved for backwards-compat with older tests. */\n initialDelayMs?: number;\n maxDelayMs?: number;\n backoffMultiplier?: number;\n}\n\nconst DEFAULT_BACKOFF_SCHEDULE: readonly number[] = [1000, 2000, 4000, 8000, 15000, 30000];\nconst DEFAULT_SLOW_RETRY_DELAY_MS = 30_000;\nconst DEFAULT_PING_INTERVAL_MS = 30_000;\nconst DEFAULT_PONG_TIMEOUT_MS = 60_000;\n\nexport class PersonalRoomConnection {\n private state: ConnectionState = 'disconnected';\n private ws: MinimalWebSocket | null = null;\n private readonly messageHandlers = new Set<(msg: PersonalRoomServerMessage) => void>();\n private readonly stateChangeHandlers = new Set<(state: ConnectionState) => void>();\n private readonly rttSampleHandlers = new Set<(rttMs: number) => void>();\n private readonly config: PersonalRoomConnectionConfig;\n private retryCount = 0;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n /**\n * Timestamp (ms) of the most recent outbound 'ping'. Cleared on the first\n * pong-shaped reply that arrives afterwards. RTT = now - lastPingSentAt.\n * Approximate when more than one ping is in flight at once (pong always\n * matches the latest send), but the proactive interval (30s) is two orders\n * of magnitude slower than typical RTTs so the approximation is fine.\n */\n private lastPingSentAt: number | null = null;\n /**\n * Cluster A3: proactive interval timer. Fires every `pingIntervalMs`\n * regardless of whether messages have been received. Today's reactive\n * timer (reset on every `onmessage`) hides half-open WS death until\n * kernel TCP RST (~30s on residential ISP).\n */\n private pingTimer: ReturnType<typeof setInterval> | null = null;\n /**\n * Cluster A4: pong watchdog. Set when a ping is sent; cleared on any\n * incoming message. If it fires before clear, we force-close the WS.\n */\n private pongWatchdog: ReturnType<typeof setTimeout> | null = null;\n private intentionalDisconnect = false;\n private readonly backoffSchedule: readonly number[];\n private readonly slowRetryDelayMs: number;\n private readonly pingIntervalMs: number;\n private readonly pongTimeoutMs: number;\n\n constructor(config: PersonalRoomConnectionConfig) {\n this.config = config;\n this.backoffSchedule = config.backoffSchedule ?? DEFAULT_BACKOFF_SCHEDULE;\n this.slowRetryDelayMs = config.slowRetryDelayMs ?? DEFAULT_SLOW_RETRY_DELAY_MS;\n this.pingIntervalMs = config.pingIntervalMs ?? DEFAULT_PING_INTERVAL_MS;\n this.pongTimeoutMs = config.pongTimeoutMs ?? DEFAULT_PONG_TIMEOUT_MS;\n }\n\n getState(): ConnectionState {\n return this.state;\n }\n\n connect(): void {\n this.intentionalDisconnect = false;\n\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n\n if (this.ws) {\n this.stopPingTimer();\n this.ws.onopen = null;\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close();\n this.ws = null;\n }\n\n const Impl = this.config.WebSocketImpl ?? (WebSocket as never);\n try {\n this.ws = new Impl(this.config.url);\n } catch {\n this.setState('error');\n this.reconnectWithBackoff();\n return;\n }\n this.setState('connecting');\n\n this.ws.onopen = () => {\n this.setState('connected');\n this.startPingTimer();\n };\n\n this.ws.onclose = () => {\n this.stopPingTimer();\n if (this.state !== 'error' && this.state !== 'reconnecting') {\n this.setState('disconnected');\n }\n if (!this.intentionalDisconnect) {\n this.reconnectWithBackoff();\n }\n };\n\n this.ws.onerror = () => {\n this.stopPingTimer();\n this.setState('error');\n if (!this.intentionalDisconnect) {\n this.reconnectWithBackoff();\n }\n };\n\n this.ws.onmessage = (event: { data: string }) => {\n /**\n * Cluster A4: any incoming traffic (including server pongs) clears the\n * pong watchdog. The proactive ping interval keeps firing — we do NOT\n * reset it here (Cluster A3 — decouple from message receipt).\n */\n this.clearPongWatchdog();\n\n if (event.data === 'pong') {\n this.handlePong();\n return;\n }\n\n let raw: unknown;\n try {\n raw = JSON.parse(event.data);\n } catch {\n return;\n }\n\n const result = PersonalRoomServerMessageSchema.safeParse(raw);\n if (!result.success) {\n if (this.state === 'connected') {\n // biome-ignore lint/suspicious/noConsole: intentional debug visibility for silent Zod drops that hid a critical share-link bug\n console.warn(\n '[PersonalRoom] Dropped unrecognized server message:',\n JSON.stringify(raw),\n result.error.issues\n );\n }\n return;\n }\n\n for (const handler of [...this.messageHandlers]) {\n handler(result.data);\n }\n };\n }\n\n send(msg: PersonalRoomClientMessage): void {\n try {\n this.ws?.send(JSON.stringify(msg));\n } catch {\n /** WebSocket may be in CLOSING/CLOSED state */\n }\n }\n\n onMessage(handler: (msg: PersonalRoomServerMessage) => void): () => void {\n this.messageHandlers.add(handler);\n return () => {\n this.messageHandlers.delete(handler);\n };\n }\n\n onStateChange(handler: (state: ConnectionState) => void): () => void {\n this.stateChangeHandlers.add(handler);\n return () => {\n this.stateChangeHandlers.delete(handler);\n };\n }\n\n /**\n * Register a listener for ping/pong RTT samples. Fires once per pong\n * received from the server with the round-trip duration in milliseconds.\n * Intended for observability: lets callers distinguish a high-latency\n * connection from a healthy responsive one.\n */\n onRttSample(handler: (rttMs: number) => void): () => void {\n this.rttSampleHandlers.add(handler);\n return () => {\n this.rttSampleHandlers.delete(handler);\n };\n }\n\n reconnect(): void {\n this.intentionalDisconnect = false;\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n this.retryCount = 0;\n this.connect();\n }\n\n disconnect(): void {\n this.intentionalDisconnect = true;\n this.stopPingTimer();\n\n if (this.retryTimer !== null) {\n clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n\n this.retryCount = 0;\n\n if (this.ws) {\n this.ws.onopen = null;\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close();\n this.ws = null;\n }\n this.setState('disconnected');\n }\n\n /**\n * Compute and emit the RTT for the most recent outbound 'ping' upon pong\n * receipt. Extracted from `onmessage` so the message-dispatch handler stays\n * within the cognitive-complexity cap.\n */\n private handlePong(): void {\n if (this.lastPingSentAt === null) return;\n const rttMs = Date.now() - this.lastPingSentAt;\n this.lastPingSentAt = null;\n for (const handler of [...this.rttSampleHandlers]) {\n handler(rttMs);\n }\n }\n\n /**\n * Cluster A3: start the proactive ping interval. Fires every `pingIntervalMs`\n * regardless of message receipt — half-open WS death is detected by the\n * absence of an inbound pong, not by the absence of inbound messages.\n */\n private startPingTimer(): void {\n this.stopPingTimer();\n this.pingTimer = setInterval(() => {\n try {\n this.ws?.send('ping');\n this.lastPingSentAt = Date.now();\n } catch {\n /** WebSocket may be in CLOSING/CLOSED state */\n }\n this.armPongWatchdog();\n }, this.pingIntervalMs);\n }\n\n private stopPingTimer(): void {\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this.clearPongWatchdog();\n /** Reset RTT bookkeeping so a stale send time doesn't carry across reconnects. */\n this.lastPingSentAt = null;\n }\n\n /**\n * Cluster A4: arm a one-shot watchdog that fires if no inbound traffic\n * arrives within `pongTimeoutMs`. On expiry we force-close the WS so the\n * onclose path runs the backoff and reconnect.\n *\n * If a watchdog is already pending (a prior ping hasn't been answered\n * yet), leave it alone — the deadline applies to the OLDEST unanswered\n * ping, not the most recent. Resetting on every ping would mask a\n * persistent half-open WS forever.\n */\n private armPongWatchdog(): void {\n if (this.pongWatchdog !== null) return;\n this.pongWatchdog = setTimeout(() => {\n this.pongWatchdog = null;\n try {\n this.ws?.close();\n } catch {\n /** WebSocket may already be in CLOSING/CLOSED state */\n }\n }, this.pongTimeoutMs);\n }\n\n private clearPongWatchdog(): void {\n if (this.pongWatchdog !== null) {\n clearTimeout(this.pongWatchdog);\n this.pongWatchdog = null;\n }\n }\n\n /**\n * Cluster A8: geometric backoff schedule replacing today's bimodal jump\n * (1s -> 30s). Schedule defaults to [1, 2, 4, 8, 15, 30]s. After the\n * schedule is exhausted we slow-retry every `slowRetryDelayMs` forever —\n * no terminal `failed` state. Bounded `maxRetries` is preserved for\n * legacy callers (still uses the geometric schedule for the bounded run,\n * but stops once `retryCount >= maxRetries`).\n *\n * Legacy `initialDelayMs` / `maxDelayMs` / `backoffMultiplier` configs\n * fall back to the original `min(initial * mult^count, max)` formula so\n * existing tests and call sites keep their semantics.\n */\n private reconnectWithBackoff(): void {\n if (this.retryTimer !== null) return;\n\n const maxRetries = this.config.maxRetries;\n if (maxRetries === undefined || maxRetries === 0) {\n return;\n }\n\n if (maxRetries !== -1 && this.retryCount >= maxRetries) {\n return;\n }\n\n this.setState('reconnecting');\n\n if (this.intentionalDisconnect) return;\n\n const delay = this.computeBackoffDelay(this.retryCount);\n\n this.retryCount++;\n\n this.retryTimer = setTimeout(() => {\n this.retryTimer = null;\n this.connect();\n }, delay);\n }\n\n private computeBackoffDelay(attempt: number): number {\n /**\n * Legacy path: callers that pass `initialDelayMs`/`backoffMultiplier`/`maxDelayMs`\n * keep the old `min(initial * mult^attempt, max)` semantics. New callers\n * use the geometric schedule + slow-retry-forever tail.\n */\n if (\n this.config.initialDelayMs !== undefined ||\n this.config.maxDelayMs !== undefined ||\n this.config.backoffMultiplier !== undefined\n ) {\n const initialDelay = this.config.initialDelayMs ?? 1000;\n const maxDelay = this.config.maxDelayMs ?? 30_000;\n const multiplier = this.config.backoffMultiplier ?? 2;\n return Math.min(initialDelay * multiplier ** attempt, maxDelay);\n }\n\n if (attempt < this.backoffSchedule.length) {\n return this.backoffSchedule[attempt] ?? this.slowRetryDelayMs;\n }\n return this.slowRetryDelayMs;\n }\n\n private setState(newState: ConnectionState): void {\n this.state = newState;\n if (newState === 'connected') {\n this.retryCount = 0;\n }\n for (const handler of [...this.stateChangeHandlers]) {\n handler(newState);\n }\n }\n}\n","/**\n * Monotonic protocol version for daemon↔browser compatibility.\n *\n * Bump ONLY on breaking changes to the communication protocol between\n * the daemon and browser (new/removed message types, changed ephemeral\n * schemas, etc.). Feature releases that don't break the protocol do\n * NOT bump this number.\n *\n * The browser hardcodes a minimum acceptable version. When a daemon\n * connects with a lower version, the browser shows a blocking update\n * modal.\n */\nexport const PROTOCOL_VERSION = 69;\n\n/**\n * Minimum protocol version the browser requires from daemons.\n * Daemons reporting a version below this (or no version at all)\n * trigger the \"update your daemon\" modal.\n *\n * Version history:\n * 32: Canvas publish stability — `projectRoot` / `packageName` on DetectedPort;\n * `canvasElementId` / `projectRoot` on request_publish previewPort target;\n * new `published_artifacts_state` broadcast.\n * 33: Task index pagination — `partial` / `nextCursor` / `nextCursorId` /\n * `totalCount` on `task_index_snapshot` and matching `activeOnly` / `limit` /\n * `sinceLastActivityAt` / `sinceLastId` on `request_task_index`. Bounds the\n * initial startup payload for users with large task stores and lets the\n * browser fetch older pages on demand.\n * 34: Sidebar redesign — task coloring / favorites / projects. New message\n * types: set_task_color, toggle_favorite_task, set_task_project,\n * create_project, rename_project, delete_project, reorder_projects\n * (browser→daemon); projects_snapshot, project_updated, project_deleted\n * (daemon→browser). Adds `projectId` to TaskRecord and `taskColors` /\n * `favoriteTasks` / `collapsedProjects` to UserSettingsRecord.\n * Additive: MIN_PROTOCOL_VERSION stays at 33 — pre-v34 daemons still\n * function, just without the new sidebar features.\n * 35: Browser-initiated cwd persistence — `set_task_cwd` control message\n * (browser → daemon) that writes `TaskRecord.cwd` and invokes the\n * existing `onCwdChanged` hook so worktree auto-switch selections\n * survive page refresh. Hard bumps MIN to 35: pre-v35 daemons don't\n * handle `set_task_cwd`, so the browser's optimistic auto-switch\n * would write UI state but not persist, silently re-introducing the\n * refresh-reset bug.\n * 36: Preview-pointer refactor — canvas `preview` elements become\n * pointer-only in the CRDT (`{ ownerUserId }`). Port / url /\n * projectRoot / proxyPort / initialPath move to a daemon-local\n * `PreviewStateStore` broadcast via a new `preview_elements_state`\n * control message. Hard bumps MIN to 36: pre-v36 daemons still write\n * port/projectRoot into the CRDT, so a v36 browser reading\n * `preview_elements_state` would show \"preview pending\" forever\n * against an older daemon. Graceful downgrade isn't possible.\n * 37: No wire-protocol change. Forces the update-modal for v3.3.0 daemons\n * so users pick up the v3.3.1 hotfix for the node-pty `spawn-helper`\n * permissions bug — node-pty 1.1.0 ships the PTY-spawn intermediate\n * with mode 0644 (no execute bit), so every terminal spawn on\n * macOS/Linux fails with an opaque \"posix_spawnp failed.\" v3.3.1 adds\n * a postinstall chmod to the daemon's package-npm.json. Bumping MIN\n * forces the prompt because graceful downgrade leaves a featured\n * surface (terminal panel) broken with no user-visible clue why.\n * 38: Linear plugin filter/sort/scope persistence — adds `linearViewPreferences`,\n * `linearScope`, `linearSelectedTeamId` to UserSettingsRecord. Additive:\n * MIN_PROTOCOL_VERSION stays at 37 — pre-v38 daemons just ignore the new\n * fields and the browser falls back to local defaults.\n * 39: Worktree state-machine refactor + start_from_linear protocol.\n * Replaces the worktree_progress/worktree_created/worktree_error\n * message triple with a single worktree_phase_changed snapshot\n * carrying full WorktreeState (per Invariant #2). Adds\n * start_from_linear (browser→daemon) and task_starting_from_linear\n * (daemon→browser) for the Linear plugin one-click ergonomics flow.\n * TaskRecord adds startupPhase, awaitingSetup, startedFromLinear\n * with Zod defaults — no imperative migration. MIN bumped because\n * pre-v39 daemons emit removed message types and post-v39 browsers\n * don't know how to handle them (and vice versa).\n * 40: Daemon-owns-templates refactor — new browser→daemon `apply_template_request`\n * message. Removes browser-side template mirroring into TodoEnrichmentStore.\n * Hard bumps MIN to 40: pre-v40 daemons don't handle `apply_template_request`,\n * so applying a template to an existing task would silently no-op.\n * 41: Notification system foundation — adds task-scoped notification\n * control-channel messages (`notification_snapshot`, `notification_added`,\n * `notification_updated`, `notification_resolved` daemon→browser plus\n * `notification_ack` browser→daemon) and a `notifications` field on\n * `TaskRecord`. Hard MIN bump: pre-v41 daemons can't classify the new\n * D→B messages through `collab-outbound-filter`'s exhaustive switch\n * and won't honour browser acks, so the bell badge / sidebar Needs\n * attention bucket would silently desync from daemon state.\n * 42: Unified comment experience — DiffHunkAnnotation extended with\n * provider/external/baseRef/baseLabel/syncStatus, new annotation_updated\n * control message for reanchor broadcasts, PRComment extended with\n * commit_id/threadId/isResolved/isOutdated for richer GitHub mapping,\n * new unresolve_thread PRAction. Pre-v42 daemons can't emit\n * annotation_updated, so a v42 browser waiting for reanchor broadcasts\n * on commit would never receive them. Hard bumps MIN: graceful\n * downgrade leaves the auto-promote-on-commit feature silently broken.\n * 43: On-demand per-task hydration — adds `request_task_state` (browser→daemon).\n * Daemon responds with the existing per-task messages\n * (`published_artifacts_state`, `preview_elements_state`, `annotation_snapshot`,\n * `thread_list`, `permission_request`/`permission_resolved`, `plan_detected`,\n * `turn_stats`) only for the requested taskId. Replaces the daemon's\n * at-attach iterate-all-tasks fan-out (the thundering herd at 552\n * tasks: ~1,656 control sends per attach, self-feeding via SCTP\n * backpressure → channel close → reconnect loop). Pre-v43 daemons\n * don't handle `request_task_state`, so a v43 browser would never\n * hydrate published artifacts / preview elements / pending permissions\n * and the canvas + permissions UI would render empty. Hard MIN bump\n * — graceful downgrade isn't possible.\n * 44: Favorites zombie fix — removes the deprecated browser→daemon\n * `toggle_pin_task` control message and the `pinned` field from\n * TaskRecord (TASK_STORE_VERSION 14→15). Favorites now live exclusively\n * in `UserSettingsRecord.favoriteTasks`. Hard MIN bump: pre-v44\n * browsers still call `togglePinTask` on pin/unpin, which a v44 daemon\n * would reject as an unknown control message — silently breaking the\n * star toggle. The unified favorites surface is on every browser at\n * v34+, so the upgrade modal is the right UX.\n * 45: MCP architecture rebuild — adds browser→daemon `request_mcp_toggle`\n * (coordinator-driven runtime toggle, replaces overloading\n * `update_settings.disabledMcpServers` for the same purpose), renames\n * `mcp_server_status` enum value `pending` → `connecting` (the new\n * per-server FSM in `apps/daemon/src/services/mcp/server-fsm.ts` uses\n * `connecting` and the protocol now mirrors that vocabulary), and\n * routes lifecycle through the new `McpCoordinator` (single source of\n * truth, owns transports, persistence-before-ack, single broadcast\n * per transition). Hard MIN bump for two reasons: (1) pre-v45 daemons\n * emit `pending` which v45 browsers no longer accept, and (2) pre-v45\n * browsers send toggle intent via `update_settings.disabledMcpServers`\n * which v45 daemons no longer act on at the toggle level (Phase 3\n * collapses the path through the coordinator). Graceful downgrade\n * isn't possible on either side without re-introducing the bugs the\n * rebuild closes.\n * 46: ReasoningEffort enum change — removed `'auto'` (meta-value that silently\n * omitted `--effort`), added `'none'` (explicit thinking-off, translated to\n * `applyFlagSettings({alwaysThinkingEnabled:false})`). The\n * `ReasoningCapabilitySchema` in `packages/session` uses `z.enum()`, so a\n * v45 daemon advertising `'auto'` fails Zod parse on a v46 browser and\n * vice versa. Hard bumps MIN to 46.\n * 47: Per-item model+effort carrier — adds `set_task_capabilities`\n * (browser→daemon) for per-structured-task-item capability overrides.\n * Schema additions: `model` / `reasoningEffort` on `StructuredTask` +\n * `TemplateItem`; `capabilitiesOverrides` on `TaskOverlay`. Additive on\n * the wire — pre-v47 daemons don't handle `set_task_capabilities` (the\n * browser badge edit affordance no-ops) but everything else still\n * functions. MIN_PROTOCOL_VERSION stays at 46: a graceful downgrade\n * leaves only the new badge-edit UI inert, no silent corruption.\n * 48: Generic batch-state signal — adds `resource_batch_state`\n * (daemon→browser) and `flush_resource_batch` (browser→daemon).\n * `resource_batch_state` lets the browser display an authoritative\n * countdown for any batched resource (comments today, canvas, branch,\n * etc.) without polling. `flush_resource_batch` lets the browser\n * trigger an immediate flush for a specific URI. Additive: pre-v48\n * daemons don't emit `resource_batch_state` and don't handle\n * `flush_resource_batch`; all other surfaces still function.\n * MIN_PROTOCOL_VERSION stays at 46: graceful downgrade leaves the\n * countdown inert without corrupting any data.\n * 49: Agent-driven preview build config — adds `installCmd`, `buildCmd`,\n * `outDir` to `PreviewElementState` so `present({source:'port',\n * projectRoot, installCmd?, buildCmd?, outDir?})` can teach the\n * publish daemon how to build monorepo subdirectory spikes whose\n * framework defaults aren't reachable from `projectRoot` alone (e.g.\n * pnpm workspace deps installed at the workspace root). Pre-v49\n * daemons simply ignore the new fields and fall through to\n * framework-detect — the existing publish flow stays correct.\n * MIN_PROTOCOL_VERSION stays at 46: pure additive, no silent\n * corruption on graceful downgrade.\n * 50: Plugin sandbox capability expansion — adds `plugin_push`\n * (daemon→browser) for per-plugin live update channel, plus new\n * file-based MCP server registration, `events`/`provideResources`\n * declaration, `spawnAgent` capability, and a new `plugin://`\n * resource sub-resolver. Additive on the wire: pre-v50 browsers\n * drop unknown `plugin_push` (plugins simply don't receive live\n * updates), and pre-v50 daemons never emit it. Other plugin\n * capabilities work via the existing harness MCP path which is\n * unchanged. MIN_PROTOCOL_VERSION stays at 46.\n * 51: Collab WebRTC hub-and-spoke topology cutover — `ParticipantSchema.isHub`\n * (and the mirror on `collab_room_state.room.participants`) becomes\n * required. CollabRoom DO refuses non-owner-to-non-owner WebRTC relays\n * (`webrtc_relay_refused` error). Browsers open exactly one PC — to the\n * hub — and never initiate; daemon always initiates. Hard MIN bump:\n * pre-v51 browsers attempt mesh PC opens (lexicographic tiebreak) which\n * a v51 DO refuses, leaving the room with the daemon-PC connected but\n * all viewer-to-viewer attempts erroring; pre-v51 daemons emit\n * Participant payloads without `isHub`, which a v51 browser's Zod parse\n * rejects. Graceful downgrade isn't possible. See AGENTS.md Invariant #15.\n * 52: Connection-stability lifecycle discriminators (Cluster A7) —\n * adds `BrowserLeftSchema { type: 'browser-left', machineId,\n * connectionId }` to the personal-room server→client union and\n * requires `connectionId: string` on the existing `AgentLeftSchema`.\n * The DO emits `browser-left` on `webSocketClose` for browser\n * sessions so the daemon can tear down its WebRTC peer immediately\n * (eliminating the silent ICE-disconnect window that drove the\n * painful p95 reconnect tail). Hard MIN bump: pre-v52 daemons\n * sent `agent-left` without `connectionId`, so a v52 browser's\n * Zod parse rejects them outright; pre-v52 browsers ignore the\n * newly-required field. Graceful downgrade isn't possible because\n * the schema change is breaking on both sides.\n * 53: task_index_snapshot / task_state_update slim payload — daemon strips\n * three heavy fields (lastTurnStats, lastTokenCount,\n * lastPlanDetection) at emit time and the browser fetches them on\n * demand via new `request_task_detail` (browser → daemon) and\n * `task_detail_snapshot` (daemon → browser). structuredTasks +\n * taskOverlay + taskOverlays stay in the slim entry — the sidebar\n * and the active task's tile-slot read them live and stripping them\n * would force an extra round-trip on every overlay change. Cuts a\n * 50-task page from ~1.6 MB to ~400 KB for a user with 385 tasks,\n * eliminating the SCTP-overflow + ALO resend storm tracked at #2552.\n * Also: outer daemon-control GuardedChannel switches\n * `policy: 'queue'` → `'await-drain'` and exposes bufferedAmount;\n * GuardedChannel adds an `oversize` drop reason for single messages\n * > maxQueuedBytes (the existing `MAX_DELIVER=8` ALO ladder bounds\n * retries — `oversize` drops fast at the channel layer instead of\n * chewing SCTP buffer on each attempt). `task_detail_snapshot`\n * carries no version tag and uses property-presence merge: a key\n * absent from `detail` is left untouched on the browser side. This\n * means the snapshot can ADD or UPDATE heavy fields but cannot\n * CLEAR them — daemon-side clears (clearSession resets to\n * DEFAULT_TASK_OVERLAY rather than `undefined`, so this only\n * practically affects `lastTurnStats` / `lastPlanDetection` which\n * are owned by live message channels). Hard MIN bump: pre-v53\n * daemons emit fat records (no behavioral change for browser\n * parse), but pre-v53 browsers don't request detail and would\n * render with missing heavy fields after a v53-daemon snapshot.\n * 54: daemon-control at-least-once streamId derivation made symmetric.\n * Pre-v54 the browser's CCM hand-rolled `streamId = control-${sourceId}`\n * with sourceId = literal 'personal', while the daemon's\n * wireControlChannel hand-rolled `streamId = control-${controlPeerId}`\n * with controlPeerId = the DAEMON's own machineId. The values never\n * matched, so every browser→daemon control envelope was rejected by\n * `aloShell.receiveParsed` as passthrough, fell through to the\n * legacy parser, and was silently dropped with\n * \"Unknown or invalid control message type: unknown\" (literal\n * `unknown` because ALO envelopes use `kind` not `type`). Production\n * symptom: green WebRTC chip, \"No models\", \"Connecting to daemon...\"\n * on Settings → Agent — every browser↔daemon control message lost.\n * Fix: shared `controlChannelStreamId(sourceId)` helper used by both\n * sides, with a required `peerSourceId: 'personal' | \\`collab:\\${roomId}:\\${browserMachineId}\\``\n * threaded through `wireControlChannel`. Collab `sourceId` becomes\n * per-peer (was per-room) so each viewer's daemon-side ALO outbox\n * gets a distinct JSONL file (a room-scoped value caused 2+ viewers\n * to share an outbox, with peer A's acks truncating peer B's\n * pending messages). Hard MIN bump: pre-v54 browsers send\n * `streamId='control-personal'` (or `'control-collab:${roomId}'`)\n * which a v54 daemon's shell does not recognize → reject at\n * handshake instead of silently spinning a broken connection.\n * 55: Archive task — adds `archive_task` / `unarchive_task` (browser →\n * daemon) control messages and an `archivedAt: number | null` field\n * on `TaskRecord`. Pure-additive on the wire; archived tasks are\n * hidden from active surfaces and surfaced under a single \"Archived\"\n * sidebar section. Hard MIN bump because pre-v55 daemons reject the\n * new control message types and the browser would silently no-op\n * the archive context-menu action / ⌘E hotkey against an older\n * daemon, leaving users to wonder why nothing happened.\n * 56: Task-index version ping — adds `request_task_index_version`\n * (browser → daemon) and `task_index_version` (daemon → browser)\n * carrying `{version, totalCount}` (~32 bytes). Replaces the prior\n * 30s heartbeat that fired a full `request_task_index limit:50`\n * (~400 KB at slim wire shape × 50 entries) every tick regardless\n * of need. Browser fires the ping every 10s during silence; on\n * version mismatch it falls through to the existing\n * `request_task_index` path. Pure-additive: pre-v56 daemons\n * receiving the new message type fall through to the legacy\n * unknown-message-type path (silent drop). The browser handles\n * this by keeping the legacy heartbeat in place when talking to a\n * pre-v56 daemon — version is checked at handshake and the\n * browser picks the right strategy. MIN_PROTOCOL_VERSION stays\n * at 55: graceful downgrade is intentional so a pre-v56 daemon\n * that ships before the browser update still works at the prior\n * traffic level.\n * 57: Parallel threads in a task — `ThreadMetadata` gains `kind: 'main' |\n * 'parallel' | 'fork'` (defaults to 'fork' for legacy persisted\n * threads). `Message` gains optional `crossThreadOrigin` stamped by\n * the daemon when an agent calls `post_to_thread`. New control\n * message extension: `create_thread` extended with `kind`/`spawnMode`.\n * New MCP tool `post_to_thread` exposed via the harness server.\n * Hard MIN bump because the new `kind` field is a Zod-required\n * discriminant on the wire and pre-v57 daemons would reject the\n * create_thread extension.\n * 58: Truly slim wire shape — `TASK_INDEX_HEAVY_FIELDS` extended to strip\n * `notifications`, `structuredTasks`, `taskOverlay`, `taskOverlays`,\n * `attributedCommitShas`, `composerSettings` from `task_index_snapshot`\n * / `task_state_update` (in addition to the v53 strip set:\n * `lastTurnStats` / `lastTokenCount` / `lastPlanDetection`). New\n * derived fields `inProgressCount` / `unreadNotificationCount` on the\n * slim entry surface the counts the sidebar needs without the full\n * maps. Heavy fields delivered on demand for the active task via the\n * existing `task_detail_snapshot` (extended to include the 6\n * newly-stripped fields). User-measured impact: 50-task page goes\n * from ~1.0 MB to ~50 KB — 20× reduction on the dominant hot path.\n * Hard MIN bump per the v53 precedent: a v58 daemon emits slim\n * records that pre-v58 browsers would render with empty\n * `inProgressCount` / `unreadNotificationCount` (sidebar shows no\n * in-progress arc, no unread badge); a pre-v58 daemon emits fat\n * records but a v58 browser doesn't request detail (no protocol\n * change in pre-v58 daemon's handler), so heavy-field consumers see\n * missing data. Both directions break the sidebar — bump MIN.\n * 59: Additive ConnectorCap kind — `crow-zero-or-one` added to the\n * `ConnectorCap` discriminated union (loro-schema's\n * `connector-util-schema.ts`) and rendered in apps/web's\n * `connector-caps.tsx`. Pure additive — older browsers never\n * receive this cap unless a v59 client wrote a connector using it\n * (Zod would then reject that one connector with a parse error\n * on the older browser). Forward-compat additive: MIN stays at\n * 58 since the daemon protocol itself is unchanged; only the\n * data schema gained a variant.\n * 60: `request_capabilities.force` — optional boolean on the existing\n * `request_capabilities` browser→daemon control message. When set,\n * the daemon re-runs `detectCapabilities` (claude/codex install\n * probes, anthropic auth, MCP servers) before pushing the snapshot\n * instead of replaying the cached one. Used by the new \"Refresh\n * capabilities\" UI affordance and by the wedge-killing watcher path\n * (file watcher debounces multiple rapid changes into a single\n * force-detect call). Pure additive — pre-v60 daemons ignore the\n * field, pre-v60 browsers omit it. MIN stays at 58.\n * 61: Observability bundle — additive, MIN stays at 58.\n * browser → daemon: optional `renderTotals` and `idbWritesByStore`\n * on `browser_metrics_sample.metrics`, plus optional `windowEpoch`\n * on the same envelope. daemon → browser: optional `clockSkewMs`\n * and `windowEpoch` on `health_snapshot`, plus optional\n * `currentLabel` / `prevLabel` / `prevDurationMs` on\n * `event_loop_stall` (log-only, no wire). All fields nullable;\n * pre-v61 daemons/browsers omit them and the dashboard renders\n * null without breaking. The `windowEpoch` field on both\n * envelopes is a 10s-bucketed daemon-clock value used as a\n * correlation key to join browser-side render attribution to\n * daemon-side stall attribution within the same window.\n * 62: WebRTC outbox-epoch handshake — adds `daemon_outbox_epoch`\n * (daemon→browser) sent as the FIRST frame on every daemon-control\n * data channel open, outside the ALO envelope, so the browser\n * at-least-once layer can detect cursor-space resets the same way\n * the local-direct helloAck already does. Hard MIN bump: pre-v62\n * browsers have no handler for `daemon_outbox_epoch` and would\n * fall through to the unknown-message-type drop path (silent loss\n * of epoch info); post-v62 browsers require the epoch for correct\n * ALO cursor validation and cannot safely infer it from absence.\n * Graceful downgrade would silently re-introduce the duplicate\n * message delivery bug the epoch is designed to prevent.\n * 63: Reviews tab foundation — adds 'review' to the taskModes enum\n * (task-schemas.ts) and reviewSections + reviewDefaultRepos to\n * UserSettingsRecord (user-settings-schemas.ts). 'review' is a\n * new variant in a Zod z.enum() — pre-v63 daemons fail to parse\n * any TaskRecord with mode='review' (unknown enum value), and\n * pre-v63 browsers fail to render any task_index_snapshot that\n * contains a review task. Hard lockstep upgrade required.\n * MIN_PROTOCOL_VERSION bumped to 63. Additive UserSettingsRecord\n * fields (reviewSections, reviewDefaultRepos) use Zod .default([]).\n * 64: Per-tab ALO transport identity — daemon-control data channel label\n * changes from `daemon-control` (constant) to\n * `daemon-control:${sourceId}` where `sourceId` is per-tab in personal\n * mode (`personal:${tabId}` with tabId = `crypto.randomUUID()` minted\n * at JS module-load time, NOT sessionStorage) and per-tab in collab\n * mode (`collab:${roomId}:${browserMachineId}:${tabId}`). The daemon\n * parses the suffix from the data channel label and uses it as the\n * at-least-once peerSourceId, so each browser tab writes to its own\n * JSONL outbox + receive-cursor file under\n * `~/.shipyard/data/at-least-once-outbox/`. Pre-fix: three browser\n * tabs all used the literal sourceId `personal` and stomped a single\n * shared cursor file, breaking the daemon→browser snapshot stream\n * for capabilities / task index / settings — onboarding wedged on\n * \"Install Claude Code\" because the snapshot never reached any tab.\n * Hard MIN bump: pre-v64 daemons receiving a `daemon-control:${sourceId}`\n * label fall through to the `unknown` route in `routeDataChannel`\n * and never wire up the control channel; pre-v64 browsers send the\n * bare label which a v64 daemon's wireControlChannel still accepts\n * (defense-in-depth fallback to `PERSONAL_PEER_SOURCE_ID`), but the\n * multi-tab race remains. Co-deploy is required to actually fix the\n * bug. Also bundles two adjacent fixes: `getDaemonVersion()` now\n * reads the daemon's package.json via a path-walk from the running\n * module (replaces `process.env.npm_package_version` which returned\n * the literal string `\"unknown\"` for global installs and silently\n * defeated the v62 version-rotate-on-upgrade guard); and a\n * boot-time orphan-pruner deletes `*.jsonl` and `*.receive-cursor.json`\n * files older than 7 days from the outbox directory so per-tab\n * files don't accumulate forever as users open and close tabs. See\n * AGENTS.md Invariant #16 for the architectural rule this fix\n * codifies.\n * 65: Canvas element-type expansion — adds `text`, `embed`, `flow-step`,\n * `live-app` to `CANVAS_ELEMENT_TYPES` (and consequently to the\n * `CanvasElementDataShape` enum + `elementUtilSchemas` registry).\n * Each gets a permissive Zod schema in\n * `packages/loro-schema/src/canvas/elements/{type}-util-schema.ts`\n * with `passthrough().nullish()` to preserve unknown legacy fields.\n * `live-app` is distinct from `preview` — both render an external\n * URL but `preview` is daemon-managed (mutable port/url out of CRDT)\n * while `live-app` carries a user-supplied URL flat in CRDT data\n * and uses a broader sandbox for trusted-origin embeds.\n * Pure additive — pre-v65 daemons never write these types and pre-v65\n * browsers never parse them. MIN stays at 64: graceful downgrade leaves\n * the new types only renderable on v65+ peers, but no silent corruption.\n * 66: `session_error` added to `TaskErrorSubKindSchema` closed enum\n * (packages/loro-schema/src/protocols/channel-protocol.ts).\n * Older browsers parsing a `task_error` message with an unknown\n * `subKind` value will reject the entire message via Zod's closed\n * enum — the error notification is silently dropped. Hard MIN bump\n * to 66: pre-v66 browsers must be upgraded before they will\n * correctly display session-error notifications from the daemon.\n * 67: Per-task MCP isolation — `request_mcp_toggle` gains an optional\n * `taskId: string`. When present the daemon writes the toggle to\n * that task's `composerSettings.disabledMcpServers` (and asks the\n * MCP coordinator to re-push only the affected task's resolved\n * set — the per-server FSM is NOT transitioned). When absent the\n * daemon falls back to the workspace-default path (existing\n * behavior). Originally landed in v3.5.2 with MIN at 66 (silent\n * degrade on pre-v67 daemons). Hotfixed in v3.5.3 to bump MIN to\n * 67 so per-task MCP scoping is guaranteed end-to-end — pre-v67\n * daemons would silently apply per-task toggles workspace-wide,\n * which is a worse user experience than the blocking upgrade\n * modal. Pairs with a TASK_STORE_VERSION bump 20→21 that adds\n * `disabledMcpServers` on `ComposerSettingsSchema` so per-task\n * toggles persist through daemon restart.\n * 68: User settings: `keepMachineAwake: boolean` renamed to\n * `stayAwake: 'off' | 'during-tasks' | 'always'`. The user-settings\n * JSON store gets a v7→v8 migration that maps the old boolean to\n * the semantically equivalent enum value (false→off, true→during-tasks).\n * The new `always` mode is for \"I'm leaving the room, never sleep\"\n * use cases — caffeinate runs continuously regardless of task state.\n * Wire shape changes (the `user_settings_updated` snapshot field is\n * renamed) so MIN bumps in lockstep — pre-v68 browsers would parse\n * the snapshot and silently lose the keep-awake preference.\n * 69: Claude Code session import — adds browser → daemon\n * `list_claude_code_sessions` (with query/origin/timeRange/cursor\n * pagination) and `import_claude_code_session`, plus the matching\n * daemon → browser `claude_code_sessions_found` and\n * `claude_code_session_imported`. Drives the new sidebar\n * \"Import existing Claude Code session\" modal. Server-side search\n * (ripgrep over session JSONLs) and per-tab pagination keep the\n * wire payload bounded for users with thousands of on-disk\n * sessions. Pure additive: pre-v69 daemons never receive the new\n * request types and the v69+ browser surfaces an error in the\n * modal if it sees a downgraded daemon. MIN_PROTOCOL_VERSION\n * stays at 68 — graceful downgrade leaves the import modal inert\n * without corrupting any data. Pairs with TASK_STORE_VERSION\n * 21→22 (adds importedSessionId / importedSessionSourcePath on\n * TaskRecord). Suppressed for collab peers via\n * collab-outbound-filter per Invariant #7 (filesystem access\n * stays at the owner's daemon).\n */\nexport const MIN_PROTOCOL_VERSION = 68;\n\nexport type CompatResult =\n | { compatible: true }\n | { compatible: false; reason: 'protocol' | 'prerelease-mismatch'; installCommand: string };\n\nfunction isPrerelease(version: string): boolean {\n return /-(nightly|rc)\\./.test(version);\n}\n\nexport function getInstallCommand(version?: string, channel?: 'latest' | 'next'): string {\n if (channel) {\n return `npm install -g @schoolai/shipyard@${channel}`;\n }\n const tag = version && isPrerelease(version) ? '@next' : '@latest';\n return `npm install -g @schoolai/shipyard${tag}`;\n}\n\nexport function classifyDaemonCompatibility(\n daemon: { protocolVersion?: number; npmVersion?: string },\n browser: {\n minProtocolVersion: number;\n expectedDaemonVersion?: string;\n channel?: 'latest' | 'next';\n }\n): CompatResult {\n const installCommand = getInstallCommand(\n browser.expectedDaemonVersion ?? daemon.npmVersion,\n browser.channel\n );\n\n if (\n daemon.npmVersion &&\n browser.expectedDaemonVersion &&\n isPrerelease(daemon.npmVersion) &&\n isPrerelease(browser.expectedDaemonVersion) &&\n daemon.npmVersion !== browser.expectedDaemonVersion\n ) {\n return {\n compatible: false,\n reason: 'prerelease-mismatch',\n installCommand,\n };\n }\n\n if ((daemon.protocolVersion ?? 0) < browser.minProtocolVersion) {\n return {\n compatible: false,\n reason: 'protocol',\n installCommand,\n };\n }\n\n return { compatible: true };\n}\n","/**\n * Shared constants and schemas for the Shipyard publish feature.\n *\n * Lives in `@shipyard/session` (not `@shipyard/loro-schema`) because it must\n * be consumable from Cloudflare Workers (session-server, publish-worker) where\n * loro-crdt's WASM barrel cannot load in vitest-pool-workers. The session\n * package is Worker-safe by construction.\n *\n * `@shipyard/loro-schema` re-exports these for backwards compatibility with\n * daemon/browser imports that expect the schema to own the shape.\n *\n * @module publish-constants\n */\n\nimport { z } from 'zod';\n\n/**\n * The set of supported publish kinds. Mirrors the D1 CHECK constraint on\n * published_previews.kind.\n */\nexport const PUBLISHED_PREVIEW_KINDS = [\n 'html',\n 'mermaid',\n 'svg',\n 'image',\n 'static-site',\n 'dom-snapshot',\n] as const;\n\nexport type PublishedPreviewKind = (typeof PUBLISHED_PREVIEW_KINDS)[number];\n\nexport const PublishedPreviewKindSchema = z.enum(PUBLISHED_PREVIEW_KINDS);\n\n/**\n * TTL choices exposed to users. Mapped to ms via {@link ttlToMilliseconds}.\n */\nexport const PUBLISH_TTL_CHOICES = ['24h', '7d', '30d'] as const;\nexport type PublishTtl = (typeof PUBLISH_TTL_CHOICES)[number];\n\nexport const PublishTtlSchema = z.enum(PUBLISH_TTL_CHOICES).default('24h');\n\n/**\n * Convert a TTL choice to milliseconds.\n */\nexport function ttlToMilliseconds(ttl: PublishTtl): number {\n switch (ttl) {\n case '24h':\n return 24 * 60 * 60 * 1000;\n case '7d':\n return 7 * 24 * 60 * 60 * 1000;\n case '30d':\n return 30 * 24 * 60 * 60 * 1000;\n }\n}\n\n/** Max bytes for a single-file publish (html, mermaid, svg, image, dom-snapshot). */\nexport const MAX_SINGLE_FILE_BYTES = 50 * 1024 * 1024;\n/** Max aggregate bytes across a multi-file build (static-site). */\nexport const MAX_BUILD_TOTAL_BYTES = 200 * 1024 * 1024;\n/** Max bytes for any single file within a build. */\nexport const MAX_BUILD_PER_FILE_BYTES = 10 * 1024 * 1024;\n/** Max number of files in a build. */\nexport const MAX_BUILD_FILE_COUNT = 2000;\n\n/** Per-user publish rate limit (publishes per hour). */\nexport const PUBLISH_RATE_LIMIT_PER_HOUR = 20;\n/** Per-user total active-publish storage quota (bytes). */\nexport const PUBLISH_QUOTA_TOTAL_BYTES = 500 * 1024 * 1024;\n\n/**\n * POST /publish request fields.\n *\n * Note: the endpoint accepts multipart/form-data, not JSON. This schema\n * describes the string fields only; files are transported as `files[]`\n * entries and validated separately against size/count limits.\n */\nexport const PublishCreateRequestSchema = z.object({\n kind: PublishedPreviewKindSchema,\n ttl: PublishTtlSchema,\n entryPath: z.string().min(1).max(512),\n title: z.string().max(200).optional(),\n /** \"1\" to enable SPA fallback (index.html served for unknown paths). */\n spaFallback: z.enum(['0', '1']).default('0'),\n});\nexport type PublishCreateRequest = z.infer<typeof PublishCreateRequestSchema>;\n\n/**\n * POST /publish response body.\n *\n * Discriminated on `ok` so consumers cannot silently read `undefined` from\n * `id`/`url`/`expiresAt` on failure (previously all three were required on a\n * single flat object, which TypeScript happily parsed but which didn't survive\n * the HTTP error paths — e.g. 4xx bodies with `{ error, message }`). The\n * discriminant forces callers to narrow before reading the success fields.\n *\n * Session-server routes emit `ok: true` alongside the data on 2xx and\n * `ok: false` alongside `error` on 4xx — see routes/publish.ts.\n */\nexport const PublishCreateResponseSchema = z.discriminatedUnion('ok', [\n z.object({\n ok: z.literal(true),\n id: z.string().min(1),\n url: z.string().url(),\n expiresAt: z.number().int(),\n }),\n z.object({\n ok: z.literal(false),\n error: z.string(),\n }),\n]);\nexport type PublishCreateResponse = z.infer<typeof PublishCreateResponseSchema>;\n\n/**\n * DELETE /publish/:id response body.\n */\nexport const PublishDeleteResponseSchema = z.object({\n ok: z.literal(true),\n});\nexport type PublishDeleteResponse = z.infer<typeof PublishDeleteResponseSchema>;\n\n/**\n * Row shape for GET /publish/mine.\n */\nexport const PublishListItemSchema = z.object({\n id: z.string().min(1),\n url: z.string().url(),\n kind: z.string(),\n title: z.string().nullable(),\n createdAt: z.number().int(),\n expiresAt: z.number().int(),\n sizeBytes: z.number().int().nonnegative(),\n});\nexport type PublishListItem = z.infer<typeof PublishListItemSchema>;\n\n/**\n * GET /publish/mine response body.\n */\nexport const PublishListResponseSchema = z.object({\n items: z.array(PublishListItemSchema),\n});\nexport type PublishListResponse = z.infer<typeof PublishListResponseSchema>;\n"],"mappings":"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","names":[]}
|
|
@@ -264,25 +264,66 @@ function resolveEnabledMcpServers(overrides, available, mcpTokenStore) {
|
|
|
264
264
|
return result;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
// src/shared/
|
|
267
|
+
// src/shared/auth/anthropic-credentials.ts
|
|
268
268
|
import { execFile as execFileCb2 } from "child_process";
|
|
269
269
|
import { promisify as promisify2 } from "util";
|
|
270
270
|
var execFile2 = promisify2(execFileCb2);
|
|
271
|
-
|
|
271
|
+
var defaultLog = (entry) => logger.debug(entry, entry.event);
|
|
272
|
+
function isRecord(value) {
|
|
273
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
274
|
+
}
|
|
275
|
+
async function readKeychainEntry(service) {
|
|
272
276
|
if (process.platform !== "darwin") return null;
|
|
273
277
|
try {
|
|
274
|
-
const { stdout } = await execFile2(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const token = stdout.trim();
|
|
280
|
-
if (!token || token.startsWith("sk-")) return null;
|
|
281
|
-
return token;
|
|
278
|
+
const { stdout } = await execFile2("security", ["find-generic-password", "-s", service, "-w"], {
|
|
279
|
+
timeout: 5e3
|
|
280
|
+
});
|
|
281
|
+
const value = stdout.trim();
|
|
282
|
+
return value || null;
|
|
282
283
|
} catch {
|
|
283
284
|
return null;
|
|
284
285
|
}
|
|
285
286
|
}
|
|
287
|
+
async function readKeychainOAuthToken(log = defaultLog) {
|
|
288
|
+
if (process.platform !== "darwin") {
|
|
289
|
+
log({ event: "anthropic_keychain_read_skipped", reason: "non_darwin" });
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
const raw = await readKeychainEntry("Claude Code-credentials");
|
|
293
|
+
if (!raw) {
|
|
294
|
+
log({ event: "anthropic_keychain_oauth_missing", reason: "no_credentials_entry" });
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
let parsed;
|
|
298
|
+
try {
|
|
299
|
+
parsed = JSON.parse(raw);
|
|
300
|
+
} catch {
|
|
301
|
+
log({ event: "anthropic_keychain_oauth_corrupt", reason: "json_parse_error" });
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
if (!isRecord(parsed)) {
|
|
305
|
+
log({ event: "anthropic_keychain_oauth_corrupt", reason: "json_parse_error" });
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const oauth = parsed.claudeAiOauth;
|
|
309
|
+
if (!isRecord(oauth)) {
|
|
310
|
+
log({ event: "anthropic_keychain_oauth_missing", reason: "no_oauth_field" });
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const token = oauth.accessToken;
|
|
314
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
315
|
+
log({ event: "anthropic_keychain_oauth_missing", reason: "no_oauth_field" });
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return token;
|
|
319
|
+
}
|
|
320
|
+
async function readKeychainManagedKey() {
|
|
321
|
+
const value = await readKeychainEntry("Claude Code");
|
|
322
|
+
if (!value || !value.startsWith("sk-")) return null;
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/shared/capabilities/account-integrations.ts
|
|
286
327
|
function parseClaudeAiServers(data) {
|
|
287
328
|
if (typeof data !== "object" || data === null) return [];
|
|
288
329
|
const obj = data;
|
|
@@ -298,9 +339,13 @@ function parseClaudeAiServers(data) {
|
|
|
298
339
|
return results;
|
|
299
340
|
}
|
|
300
341
|
async function fetchClaudeAiIntegrations(log) {
|
|
342
|
+
const debugLog = log ?? ((entry) => logger.debug(entry, entry.event));
|
|
301
343
|
try {
|
|
302
|
-
const token = await
|
|
303
|
-
if (!token)
|
|
344
|
+
const token = await readKeychainOAuthToken(debugLog);
|
|
345
|
+
if (!token) {
|
|
346
|
+
debugLog({ event: "claudeai_integrations_skipped", reason: "no_oauth_token" });
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
304
349
|
const controller = new AbortController();
|
|
305
350
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
306
351
|
try {
|
|
@@ -311,7 +356,6 @@ async function fetchClaudeAiIntegrations(log) {
|
|
|
311
356
|
signal: controller.signal
|
|
312
357
|
});
|
|
313
358
|
if (!response.ok) {
|
|
314
|
-
const debugLog = log ?? ((entry) => logger.debug(entry, entry.event));
|
|
315
359
|
debugLog({
|
|
316
360
|
event: "claudeai_integrations_fetch_failed",
|
|
317
361
|
status: response.status
|
|
@@ -332,7 +376,6 @@ async function fetchClaudeAiIntegrations(log) {
|
|
|
332
376
|
clearTimeout(timeout);
|
|
333
377
|
}
|
|
334
378
|
} catch (err) {
|
|
335
|
-
const debugLog = log ?? ((entry) => logger.debug(entry, entry.event));
|
|
336
379
|
debugLog({
|
|
337
380
|
event: "claudeai_integrations_fetch_error",
|
|
338
381
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -364,6 +407,10 @@ var MCPSSEEntrySchema = external_exports.object({
|
|
|
364
407
|
headers: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
365
408
|
}).passthrough();
|
|
366
409
|
var MCPServerEntrySchema = external_exports.union([MCPStdioEntrySchema, MCPHttpEntrySchema, MCPSSEEntrySchema]);
|
|
410
|
+
function shouldFetchClaudeAiIntegrations(preferredAuth) {
|
|
411
|
+
if (preferredAuth == null) return true;
|
|
412
|
+
return preferredAuth === "claude-ai";
|
|
413
|
+
}
|
|
367
414
|
var SECRET_PATTERNS = /^(sk-|ghp_|gho_|glpat-|xoxb-|xoxp-|Bearer\s|token\s)/i;
|
|
368
415
|
var SECRET_FLAGS = /* @__PURE__ */ new Set(["--api-key", "--token", "--secret", "--password", "--key", "-k"]);
|
|
369
416
|
function redactArgs(args) {
|
|
@@ -525,9 +572,9 @@ async function resolveAuthStatuses(servers, tokenStore) {
|
|
|
525
572
|
}
|
|
526
573
|
await resolveClaudeCodeAuthStatuses(servers);
|
|
527
574
|
}
|
|
528
|
-
async function detectMCPServers(environments, tokenStore, log, lastKnown) {
|
|
575
|
+
async function detectMCPServers(environments, tokenStore, log, lastKnown, preferredAuth) {
|
|
529
576
|
try {
|
|
530
|
-
return await detectMCPServersInner(environments, tokenStore, log);
|
|
577
|
+
return await detectMCPServersInner(environments, tokenStore, log, preferredAuth);
|
|
531
578
|
} catch (err) {
|
|
532
579
|
const { logger: logger2 } = await import("./logger-GQCSLSZH.js");
|
|
533
580
|
if (lastKnown && lastKnown.length > 0) {
|
|
@@ -541,12 +588,17 @@ async function detectMCPServers(environments, tokenStore, log, lastKnown) {
|
|
|
541
588
|
return [];
|
|
542
589
|
}
|
|
543
590
|
}
|
|
544
|
-
async function detectMCPServersInner(environments, tokenStore, log) {
|
|
591
|
+
async function detectMCPServersInner(environments, tokenStore, log, preferredAuth) {
|
|
545
592
|
const allServers = [];
|
|
546
593
|
const userSettingsPath = join2(homedir2(), ".claude", "settings.json");
|
|
547
594
|
const userLocalSettingsPath = join2(homedir2(), ".claude", "settings.local.json");
|
|
548
595
|
const userMcpJsonPath = join2(homedir2(), ".mcp.json");
|
|
549
596
|
const userClaudeJsonPath = join2(homedir2(), ".claude.json");
|
|
597
|
+
const fetchClaudeAi = shouldFetchClaudeAiIntegrations(preferredAuth);
|
|
598
|
+
const claudeAiPromise = fetchClaudeAi ? fetchClaudeAiIntegrations(log) : Promise.resolve([]);
|
|
599
|
+
if (!fetchClaudeAi && log) {
|
|
600
|
+
log({ event: "claudeai_integrations_skipped_by_method", method: preferredAuth ?? null });
|
|
601
|
+
}
|
|
550
602
|
const [
|
|
551
603
|
userServers,
|
|
552
604
|
userLocalServers,
|
|
@@ -560,7 +612,7 @@ async function detectMCPServersInner(environments, tokenStore, log) {
|
|
|
560
612
|
readMCPConfig(userMcpJsonPath, "user", log),
|
|
561
613
|
readMCPConfig(userClaudeJsonPath, "user", log),
|
|
562
614
|
readPluginMCPServers(),
|
|
563
|
-
|
|
615
|
+
claudeAiPromise
|
|
564
616
|
]);
|
|
565
617
|
allServers.push(
|
|
566
618
|
...claudeAiServers,
|
|
@@ -604,9 +656,11 @@ export {
|
|
|
604
656
|
readClaudeCodeCredentials,
|
|
605
657
|
resolveStdioEnv,
|
|
606
658
|
resolveEnabledMcpServers,
|
|
607
|
-
|
|
659
|
+
readKeychainOAuthToken,
|
|
660
|
+
readKeychainManagedKey,
|
|
661
|
+
shouldFetchClaudeAiIntegrations,
|
|
608
662
|
redactArgs,
|
|
609
663
|
redactEnv,
|
|
610
664
|
detectMCPServers
|
|
611
665
|
};
|
|
612
|
-
//# sourceMappingURL=chunk-
|
|
666
|
+
//# sourceMappingURL=chunk-L2WQMPWS.js.map
|