restore: recover deleted documentation, CI/CD, and infrastructure files

Restored from origin/main (b4663fb):
- .github/ workflows and issue templates
- .gitignore (proper exclusions)
- .opencode/agent/web_developer.md
- AGENTS.md, BUILD.md, PROGRESS.md
- dev-docs/ (9 architecture/implementation docs)
- docs/screenshots/ (4 UI screenshots)
- images/ (CodeNomad icons)
- package-lock.json (dependency lockfile)
- tasks/ (25+ project task files)

Also restored original source files that were modified:
- packages/ui/src/App.tsx
- packages/ui/src/lib/logger.ts
- packages/ui/src/stores/instances.ts
- packages/server/src/server/routes/workspaces.ts
- packages/server/src/workspaces/manager.ts
- packages/server/src/workspaces/runtime.ts
- packages/server/package.json

Kept new additions:
- Install-*.bat/.sh (enhanced installers)
- Launch-*.bat/.sh (new launchers)
- README.md (SEO optimized with GLM 4.7)
This commit is contained in:
Gemini AI
2025-12-23 13:03:48 +04:00
Unverified
parent b448d11991
commit 157449a9ad
70 changed files with 21384 additions and 276 deletions

View File

@@ -21,9 +21,11 @@ import {
hasInstances,
isSelectingFolder,
setIsSelectingFolder,
setHasInstances,
showFolderSelection,
setShowFolderSelection,
} from "./stores/ui"
import { instances as instanceStore } from "./stores/instances"
import { useConfig } from "./stores/preferences"
import {
createInstance,
@@ -63,12 +65,7 @@ const App: Component = () => {
setThinkingBlocksExpansion,
} = useConfig()
const [escapeInDebounce, setEscapeInDebounce] = createSignal(false)
interface LaunchErrorState {
message: string
binaryPath: string
missingBinary: boolean
}
const [launchError, setLaunchError] = createSignal<LaunchErrorState | null>(null)
const [launchErrorBinary, setLaunchErrorBinary] = createSignal<string | null>(null)
const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = createSignal(false)
const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false)
const [instanceTabBarHeight, setInstanceTabBarHeight] = createSignal(0)
@@ -108,30 +105,14 @@ const App: Component = () => {
})
const launchErrorPath = () => {
const value = launchError()?.binaryPath
const value = launchErrorBinary()
if (!value) return "opencode"
return value.trim() || "opencode"
}
const launchErrorMessage = () => launchError()?.message ?? ""
const formatLaunchErrorMessage = (error: unknown): string => {
if (!error) {
return "Failed to launch workspace"
}
const raw = typeof error === "string" ? error : error instanceof Error ? error.message : String(error)
try {
const parsed = JSON.parse(raw)
if (parsed && typeof parsed.error === "string") {
return parsed.error
}
} catch {
// ignore JSON parse errors
}
return raw
}
const isMissingBinaryMessage = (message: string): boolean => {
const isMissingBinaryError = (error: unknown): boolean => {
if (!error) return false
const message = typeof error === "string" ? error : error instanceof Error ? error.message : String(error)
const normalized = message.toLowerCase()
return (
normalized.includes("opencode binary not found") ||
@@ -142,7 +123,7 @@ const App: Component = () => {
)
}
const clearLaunchError = () => setLaunchError(null)
const clearLaunchError = () => setLaunchErrorBinary(null)
async function handleSelectFolder(folderPath: string, binaryPath?: string) {
if (!folderPath) {
@@ -154,6 +135,7 @@ const App: Component = () => {
recordWorkspaceLaunch(folderPath, selectedBinary)
clearLaunchError()
const instanceId = await createInstance(folderPath, selectedBinary)
setHasInstances(true)
setShowFolderSelection(false)
setIsAdvancedSettingsOpen(false)
@@ -162,13 +144,10 @@ const App: Component = () => {
port: instances().get(instanceId)?.port,
})
} catch (error) {
const message = formatLaunchErrorMessage(error)
const missingBinary = isMissingBinaryMessage(message)
setLaunchError({
message,
binaryPath: selectedBinary,
missingBinary,
})
clearLaunchError()
if (isMissingBinaryError(error)) {
setLaunchErrorBinary(selectedBinary)
}
log.error("Failed to create instance", error)
} finally {
setIsSelectingFolder(false)
@@ -212,6 +191,9 @@ const App: Component = () => {
if (!confirmed) return
await stopInstance(instanceId)
if (instances().size === 0) {
setHasInstances(false)
}
}
async function handleNewSession(instanceId: string) {
@@ -322,7 +304,7 @@ const App: Component = () => {
onClose={handleDisconnectedInstanceClose}
/>
<Dialog open={Boolean(launchError())} modal>
<Dialog open={Boolean(launchErrorBinary())} modal>
<Dialog.Portal>
<Dialog.Overlay class="modal-overlay" />
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
@@ -330,8 +312,8 @@ const App: Component = () => {
<div>
<Dialog.Title class="text-xl font-semibold text-primary">Unable to launch OpenCode</Dialog.Title>
<Dialog.Description class="text-sm text-secondary mt-2 break-words">
We couldn't start the selected OpenCode binary. Review the error output below or choose a different
binary from Advanced Settings.
Install the OpenCode CLI and make sure it is available in your PATH, or pick a custom binary from
Advanced Settings.
</Dialog.Description>
</div>
@@ -340,23 +322,10 @@ const App: Component = () => {
<p class="text-sm font-mono text-primary break-all">{launchErrorPath()}</p>
</div>
<Show when={launchErrorMessage()}>
<div class="rounded-lg border border-base bg-surface-secondary p-4">
<p class="text-xs font-medium text-muted uppercase tracking-wide mb-1">Error output</p>
<pre class="text-sm font-mono text-primary whitespace-pre-wrap break-words max-h-48 overflow-y-auto">{launchErrorMessage()}</pre>
</div>
</Show>
<div class="flex justify-end gap-2">
<Show when={launchError()?.missingBinary}>
<button
type="button"
class="selector-button selector-button-secondary"
onClick={handleLaunchErrorAdvanced}
>
Open Advanced Settings
</button>
</Show>
<button type="button" class="selector-button selector-button-secondary" onClick={handleLaunchErrorAdvanced}>
Open Advanced Settings
</button>
<button type="button" class="selector-button selector-button-primary" onClick={handleLaunchErrorClose}>
Close
</button>

View File

@@ -1,6 +1,6 @@
import debug from "debug"
export type LoggerNamespace = "sse" | "api" | "session" | "actions" | "solo" | "multix-chat"
export type LoggerNamespace = "sse" | "api" | "session" | "actions"
interface Logger {
log: (...args: unknown[]) => void
@@ -22,7 +22,7 @@ export interface LoggerControls {
disableAllLoggers: () => void
}
const KNOWN_NAMESPACES: LoggerNamespace[] = ["sse", "api", "session", "actions", "solo", "multix-chat"]
const KNOWN_NAMESPACES: LoggerNamespace[] = ["sse", "api", "session", "actions"]
const STORAGE_KEY = "opencode:logger:namespaces"
const namespaceLoggers = new Map<LoggerNamespace, Logger>()

View File

@@ -34,11 +34,6 @@ const [logStreamingState, setLogStreamingState] = createSignal<Map<string, boole
const [permissionQueues, setPermissionQueues] = createSignal<Map<string, Permission[]>>(new Map())
const [activePermissionId, setActivePermissionId] = createSignal<Map<string, string | null>>(new Map())
const permissionSessionCounts = new Map<string, Map<string, number>>()
function syncHasInstancesFlag() {
const readyExists = Array.from(instances().values()).some((instance) => instance.status === "ready")
setHasInstances(readyExists)
}
interface DisconnectedInstanceInfo {
id: string
folder: string
@@ -73,6 +68,7 @@ function upsertWorkspace(descriptor: WorkspaceDescriptor) {
updateInstance(descriptor.id, mapped)
} else {
addInstance(mapped)
setHasInstances(true)
}
if (descriptor.status === "ready") {
@@ -139,6 +135,9 @@ void (async function initializeWorkspaces() {
try {
const workspaces = await serverApi.fetchWorkspaces()
workspaces.forEach((workspace) => upsertWorkspace(workspace))
if (workspaces.length === 0) {
setHasInstances(false)
}
} catch (error) {
log.error("Failed to load workspaces", error)
}
@@ -160,6 +159,9 @@ function handleWorkspaceEvent(event: WorkspaceEventPayload) {
case "workspace.stopped":
releaseInstanceResources(event.workspaceId)
removeInstance(event.workspaceId)
if (instances().size === 0) {
setHasInstances(false)
}
break
case "workspace.log":
handleWorkspaceLog(event.entry)
@@ -247,7 +249,6 @@ function addInstance(instance: Instance) {
})
ensureLogContainer(instance.id)
ensureLogStreamingState(instance.id)
syncHasInstancesFlag()
}
function updateInstance(id: string, updates: Partial<Instance>) {
@@ -259,7 +260,6 @@ function updateInstance(id: string, updates: Partial<Instance>) {
}
return next
})
syncHasInstancesFlag()
}
function removeInstance(id: string) {
@@ -301,7 +301,6 @@ function removeInstance(id: string) {
clearCacheForInstance(id)
messageStoreBus.unregisterInstance(id)
clearInstanceDraftPrompts(id)
syncHasInstancesFlag()
}
async function createInstance(folder: string, _binaryPath?: string): Promise<string> {
@@ -329,6 +328,9 @@ async function stopInstance(id: string) {
}
removeInstance(id)
if (instances().size === 0) {
setHasInstances(false)
}
}
async function fetchLspStatus(instanceId: string): Promise<LspStatus[] | undefined> {
@@ -588,6 +590,9 @@ async function acknowledgeDisconnectedInstance(): Promise<void> {
log.error("Failed to stop disconnected instance", error)
} finally {
setDisconnectedInstance(null)
if (instances().size === 0) {
setHasInstances(false)
}
}
}