mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: share machine runner schemas
This commit is contained in:
@@ -194,6 +194,7 @@ export class ApiClient {
|
||||
|
||||
return {
|
||||
id: raw.id,
|
||||
namespace: raw.namespace,
|
||||
seq: raw.seq,
|
||||
createdAt: raw.createdAt,
|
||||
updatedAt: raw.updatedAt,
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { Machine } from './types'
|
||||
function makeMachine(id: string): Machine {
|
||||
return {
|
||||
id,
|
||||
namespace: 'default',
|
||||
seq: 1,
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
|
||||
@@ -451,9 +451,7 @@ export class ApiMachineClient {
|
||||
if (desiredWorkspaceRoots?.length) {
|
||||
return { ...base, workspaceRoots: desiredWorkspaceRoots }
|
||||
}
|
||||
const { workspaceRoot: _legacyWorkspaceRoot, workspaceRoots: _workspaceRoots, ...rest } = base as MachineMetadata & {
|
||||
workspaceRoot?: string
|
||||
}
|
||||
const { workspaceRoots: _workspaceRoots, ...rest } = base
|
||||
return rest as MachineMetadata
|
||||
}).then(() => {
|
||||
console.log(`[HAPI] Workspace roots synced: ${formatWorkspaceRoots(this.machine.metadata?.workspaceRoots)}`)
|
||||
|
||||
+9
-72
@@ -2,8 +2,11 @@ import {
|
||||
AgentStateSchema,
|
||||
AttachmentMetadataSchema,
|
||||
CodexCollaborationModeSchema,
|
||||
MachineMetadataSchema,
|
||||
MachineSchema,
|
||||
MetadataSchema,
|
||||
PermissionModeSchema,
|
||||
RunnerStateSchema,
|
||||
TodosSchema
|
||||
} from '@hapi/protocol/schemas'
|
||||
import {
|
||||
@@ -11,7 +14,7 @@ import {
|
||||
LocalResumeTargetResponseSchema,
|
||||
ResumableSessionsResponseSchema
|
||||
} from '@hapi/protocol'
|
||||
import type { CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types'
|
||||
import type { CodexCollaborationMode, Machine, MachineMetadata, PermissionMode, RunnerState } from '@hapi/protocol/types'
|
||||
import { z } from 'zod'
|
||||
import { UsageSchema } from '@/claude/types'
|
||||
|
||||
@@ -23,7 +26,10 @@ export type {
|
||||
ClaudePermissionMode,
|
||||
CodexCollaborationMode,
|
||||
CodexPermissionMode,
|
||||
Machine,
|
||||
MachineMetadata,
|
||||
Metadata,
|
||||
RunnerState,
|
||||
Session
|
||||
} from '@hapi/protocol/types'
|
||||
export type SessionPermissionMode = PermissionMode
|
||||
@@ -32,65 +38,7 @@ export type SessionModel = string | null
|
||||
export type SessionModelReasoningEffort = string | null
|
||||
export type SessionEffort = string | null
|
||||
|
||||
export { AgentStateSchema, AttachmentMetadataSchema, MetadataSchema }
|
||||
|
||||
export const MachineMetadataSchema = z.object({
|
||||
host: z.string(),
|
||||
platform: z.string(),
|
||||
happyCliVersion: z.string(),
|
||||
displayName: z.string().optional(),
|
||||
homeDir: z.string(),
|
||||
happyHomeDir: z.string(),
|
||||
happyLibDir: z.string(),
|
||||
workspaceRoot: z.string().optional(),
|
||||
workspaceRoots: z.array(z.string()).optional()
|
||||
}).transform(({ workspaceRoot, workspaceRoots, ...rest }) => {
|
||||
const normalizedWorkspaceRoots = Array.from(new Set(
|
||||
Array.isArray(workspaceRoots)
|
||||
? workspaceRoots.filter((path): path is string => typeof path === 'string' && path.trim().length > 0)
|
||||
: workspaceRoot
|
||||
? [workspaceRoot]
|
||||
: []
|
||||
))
|
||||
|
||||
return {
|
||||
...rest,
|
||||
workspaceRoots: normalizedWorkspaceRoots.length > 0 ? normalizedWorkspaceRoots : undefined
|
||||
}
|
||||
})
|
||||
|
||||
export type MachineMetadata = z.infer<typeof MachineMetadataSchema>
|
||||
|
||||
export const RunnerStateSchema = z.object({
|
||||
status: z.union([z.enum(['running', 'shutting-down']), z.string()]),
|
||||
pid: z.number().optional(),
|
||||
httpPort: z.number().optional(),
|
||||
startedAt: z.number().optional(),
|
||||
shutdownRequestedAt: z.number().optional(),
|
||||
shutdownSource: z.union([z.enum(['mobile-app', 'cli', 'os-signal', 'unknown']), z.string()]).optional(),
|
||||
lastSpawnError: z.object({
|
||||
message: z.string(),
|
||||
pid: z.number().optional(),
|
||||
exitCode: z.number().nullable().optional(),
|
||||
signal: z.string().nullable().optional(),
|
||||
at: z.number()
|
||||
}).nullable().optional()
|
||||
})
|
||||
|
||||
export type RunnerState = z.infer<typeof RunnerStateSchema>
|
||||
|
||||
export type Machine = {
|
||||
id: string
|
||||
seq: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
active: boolean
|
||||
activeAt: number
|
||||
metadata: MachineMetadata | null
|
||||
metadataVersion: number
|
||||
runnerState: RunnerState | null
|
||||
runnerStateVersion: number
|
||||
}
|
||||
export { AgentStateSchema, AttachmentMetadataSchema, MachineMetadataSchema, MetadataSchema, RunnerStateSchema }
|
||||
|
||||
export const CliMessagesResponseSchema = z.object({
|
||||
messages: z.array(z.object({
|
||||
@@ -131,18 +79,7 @@ export const CreateSessionResponseSchema = z.object({
|
||||
export type CreateSessionResponse = z.infer<typeof CreateSessionResponseSchema>
|
||||
|
||||
export const CreateMachineResponseSchema = z.object({
|
||||
machine: z.object({
|
||||
id: z.string(),
|
||||
seq: z.number(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
active: z.boolean(),
|
||||
activeAt: z.number(),
|
||||
metadata: z.unknown().nullable(),
|
||||
metadataVersion: z.number(),
|
||||
runnerState: z.unknown().nullable(),
|
||||
runnerStateVersion: z.number()
|
||||
})
|
||||
machine: MachineSchema
|
||||
})
|
||||
|
||||
export type CreateMachineResponse = z.infer<typeof CreateMachineResponseSchema>
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import type { Machine, MachinePatch } from '@hapi/protocol/types'
|
||||
import { MachineMetadataSchema, RunnerStateSchema } from '@hapi/protocol/schemas'
|
||||
import type { Store } from '../store'
|
||||
import { clampAliveTime } from './aliveTime'
|
||||
import { EventPublisher } from './eventPublisher'
|
||||
|
||||
const machineMetadataSchema = z.object({
|
||||
host: z.string().optional(),
|
||||
platform: z.string().optional(),
|
||||
happyCliVersion: z.string().optional(),
|
||||
displayName: z.string().optional(),
|
||||
homeDir: z.string().optional(),
|
||||
happyHomeDir: z.string().optional(),
|
||||
happyLibDir: z.string().optional(),
|
||||
workspaceRoot: z.string().optional(),
|
||||
workspaceRoots: z.array(z.string()).optional()
|
||||
})
|
||||
|
||||
export class MachineCache {
|
||||
private readonly machines: Map<string, Machine> = new Map()
|
||||
private readonly lastBroadcastAtByMachineId: Map<string, number> = new Map()
|
||||
@@ -72,35 +60,24 @@ export class MachineCache {
|
||||
const existing = this.machines.get(machineId)
|
||||
|
||||
const metadata = (() => {
|
||||
const parsed = machineMetadataSchema.safeParse(stored.metadata)
|
||||
const parsed = MachineMetadataSchema.safeParse(stored.metadata)
|
||||
if (!parsed.success) return null
|
||||
const data = parsed.data
|
||||
const host = typeof data.host === 'string' ? data.host : 'unknown'
|
||||
const platform = typeof data.platform === 'string' ? data.platform : 'unknown'
|
||||
const happyCliVersion = typeof data.happyCliVersion === 'string' ? data.happyCliVersion : 'unknown'
|
||||
const displayName = typeof data.displayName === 'string' ? data.displayName : undefined
|
||||
const homeDir = typeof data.homeDir === 'string' ? data.homeDir : undefined
|
||||
const happyHomeDir = typeof data.happyHomeDir === 'string' ? data.happyHomeDir : undefined
|
||||
const happyLibDir = typeof data.happyLibDir === 'string' ? data.happyLibDir : undefined
|
||||
const workspaceRoots = Array.from(new Set(
|
||||
Array.isArray(data.workspaceRoots)
|
||||
? data.workspaceRoots.filter((path): path is string => typeof path === 'string' && path.trim().length > 0)
|
||||
: typeof data.workspaceRoot === 'string'
|
||||
? [data.workspaceRoot]
|
||||
: []
|
||||
(data.workspaceRoots ?? []).filter((path) => path.trim().length > 0)
|
||||
))
|
||||
return {
|
||||
host,
|
||||
platform,
|
||||
happyCliVersion,
|
||||
displayName,
|
||||
homeDir,
|
||||
happyHomeDir,
|
||||
happyLibDir,
|
||||
...data,
|
||||
workspaceRoots: workspaceRoots.length > 0 ? workspaceRoots : undefined
|
||||
}
|
||||
})()
|
||||
|
||||
const runnerState = (() => {
|
||||
if (stored.runnerState == null) return null
|
||||
const parsed = RunnerStateSchema.safeParse(stored.runnerState)
|
||||
return parsed.success ? parsed.data : null
|
||||
})()
|
||||
|
||||
const storedActiveAt = stored.activeAt ?? stored.createdAt
|
||||
const existingActiveAt = existing?.activeAt ?? 0
|
||||
const useStoredActivity = storedActiveAt > existingActiveAt
|
||||
@@ -115,7 +92,7 @@ export class MachineCache {
|
||||
activeAt: useStoredActivity ? storedActiveAt : (existingActiveAt || storedActiveAt),
|
||||
metadata,
|
||||
metadataVersion: stored.metadataVersion,
|
||||
runnerState: stored.runnerState,
|
||||
runnerState,
|
||||
runnerStateVersion: stored.runnerStateVersion
|
||||
}
|
||||
|
||||
|
||||
+21
-1
@@ -236,6 +236,26 @@ export const MachineMetadataSchema = z.object({
|
||||
workspaceRoots: z.array(z.string()).optional()
|
||||
})
|
||||
|
||||
export type MachineMetadata = z.infer<typeof MachineMetadataSchema>
|
||||
|
||||
export const RunnerStateSchema = z.object({
|
||||
status: z.union([z.enum(['running', 'shutting-down']), z.string()]),
|
||||
pid: z.number().optional(),
|
||||
httpPort: z.number().optional(),
|
||||
startedAt: z.number().optional(),
|
||||
shutdownRequestedAt: z.number().optional(),
|
||||
shutdownSource: z.union([z.enum(['mobile-app', 'cli', 'os-signal', 'unknown']), z.string()]).optional(),
|
||||
lastSpawnError: z.object({
|
||||
message: z.string(),
|
||||
pid: z.number().optional(),
|
||||
exitCode: z.number().nullable().optional(),
|
||||
signal: z.string().nullable().optional(),
|
||||
at: z.number()
|
||||
}).nullable().optional()
|
||||
})
|
||||
|
||||
export type RunnerState = z.infer<typeof RunnerStateSchema>
|
||||
|
||||
export const MachineSchema = z.object({
|
||||
id: z.string(),
|
||||
namespace: z.string(),
|
||||
@@ -246,7 +266,7 @@ export const MachineSchema = z.object({
|
||||
activeAt: z.number(),
|
||||
metadata: MachineMetadataSchema.nullable(),
|
||||
metadataVersion: z.number(),
|
||||
runnerState: z.unknown().nullable(),
|
||||
runnerState: RunnerStateSchema.nullable(),
|
||||
runnerStateVersion: z.number()
|
||||
})
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ export type {
|
||||
DecryptedMessage,
|
||||
Metadata,
|
||||
Machine,
|
||||
MachineMetadata,
|
||||
MachinePatch,
|
||||
MachineUpdatedData,
|
||||
RunnerState,
|
||||
Session,
|
||||
SessionPatch,
|
||||
SessionUpdatedData,
|
||||
|
||||
+2
-16
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
DecryptedMessage as ProtocolDecryptedMessage,
|
||||
Machine,
|
||||
RunnerState,
|
||||
Session,
|
||||
SessionSummary,
|
||||
SyncEvent as ProtocolSyncEvent,
|
||||
@@ -33,6 +34,7 @@ export type {
|
||||
CodexCollaborationMode,
|
||||
PermissionMode,
|
||||
Machine,
|
||||
RunnerState,
|
||||
Session,
|
||||
SessionPatch,
|
||||
SessionSummary,
|
||||
@@ -71,22 +73,6 @@ export type DecryptedMessage = ProtocolDecryptedMessage & {
|
||||
invokedAt?: number | null
|
||||
}
|
||||
|
||||
export type RunnerState = {
|
||||
status?: string
|
||||
pid?: number
|
||||
httpPort?: number
|
||||
startedAt?: number
|
||||
shutdownRequestedAt?: number
|
||||
shutdownSource?: string
|
||||
lastSpawnError?: {
|
||||
message: string
|
||||
pid?: number
|
||||
exitCode?: number | null
|
||||
signal?: string | null
|
||||
at: number
|
||||
} | null
|
||||
}
|
||||
|
||||
export type AuthResponse = {
|
||||
token: string
|
||||
user: {
|
||||
|
||||
Reference in New Issue
Block a user