summaryrefslogtreecommitdiffhomepage
path: root/src/app/App.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-25 17:09:57 +0900
committerAdam Malczewski <[email protected]>2026-06-25 17:09:57 +0900
commit92dddf9b3fbf6fee9c28bc8a345b206e24d39f36 (patch)
tree8488b9166471e732d26df0ca811dba24fa322e68 /src/app/App.svelte
parente7d8cdbd68954034d2a4f6979a233b216212967b (diff)
downloaddispatch-web-92dddf9b3fbf6fee9c28bc8a345b206e24d39f36.tar.gz
dispatch-web-92dddf9b3fbf6fee9c28bc8a345b206e24d39f36.zip
feat(computer): SSH computer selection + status + workspace default (handoff #2)
Mirrors the cwd/workspace UI for the SSH-computer feature: - New feature library src/features/computer/: - logic/view-model.ts (pure): viewComputer/viewComputerStatus/viewTestResult/ summarizeComputers/formatHost/knownHostLabel + state->badge for the 4 ComputerStatusResponse states + SaveComputer/LoadComputerStatus/ TestComputer/LoadComputers ports. 20 view-model tests. - ui/ComputerField.svelte: per-conversation selector (dropdown + connection-status badge + Test-connection, polling the selected alias). - ui/ComputerSelect.svelte: reusable Local/computers dropdown, shared with the workspace default-computer control. - AppStore: computerId state + refreshComputer (at every focus site, parallel to refreshCwd) + setComputer (PUT /conversations/:id/computer, null=clear) + global computers catalog (GET /computers on boot, like models) + computerStatus(alias) + testComputer(alias). chat.send UNCHANGED (resolved server-side like cwd). - App.svelte: ComputerField in the Model sidebar view next to CwdField; adapted ports wrap the store. - workspaces: setDefaultComputer on WorkspaceHttp+WorkspaceStore (PUT /workspaces/:id/default-computer); default-computer selector in WorkspaceCard (reuses ComputerSelect); router passes store.computers through. - Re-mirrored .dispatch/transport-contract.reference.md (Computers section + ChatRequest.computerId); updated .dispatch/wire.reference.md (Computer/ ComputerEntry/defaultComputerId + the provider-retry divergence note from handoff #1); GLOSSARY + backend-handoff.md (handoff #2, §2e). Transparency invariant: the computer is USER-facing only (a tool-execution target, never part of the model prompt); the agent never sees it. Verify: 795/795 tests green; biome clean; vite build succeeds; 0 typecheck errors from the computer feature. (11 pre-existing svelte-check errors remain from the open §2d provider-retry divergence — backend feature/ssh-support still lacks TurnProviderRetryEvent; not from this feature.)
Diffstat (limited to 'src/app/App.svelte')
-rw-r--r--src/app/App.svelte42
1 files changed, 42 insertions, 0 deletions
diff --git a/src/app/App.svelte b/src/app/App.svelte
index 18be3ea..78504bb 100644
--- a/src/app/App.svelte
+++ b/src/app/App.svelte
@@ -50,6 +50,16 @@
manifest as cwdLspManifest,
} from "../features/cwd-lsp";
import {
+ ComputerField,
+ manifest as computerManifest,
+ type ComputerSaveResult,
+ type ComputerStatusResult,
+ type LoadComputerStatus,
+ type SaveComputer,
+ type TestComputer,
+ type TestComputerResult,
+ } from "../features/computer";
+ import {
SystemPromptBuilder,
type LoadSystemPrompt as LoadSystemPromptAlias,
type LoadSystemPromptVariables as LoadSystemPromptVariablesAlias,
@@ -116,6 +126,7 @@
cacheWarmingManifest,
cwdLspManifest,
mcpManifest,
+ computerManifest,
smartScrollManifest,
settingsManifest,
systemPromptManifest,
@@ -311,6 +322,29 @@
: { ok: false, error: result.error };
}
+ // Adapt the store's computer results to the computer feature's ports.
+ async function saveComputer(computerId: string | null): Promise<ComputerSaveResult | null> {
+ const result = await store.setComputer(computerId);
+ if (result === null) return null;
+ return result.ok ? { ok: true, computerId: result.computerId } : { ok: false, error: result.error };
+ }
+
+ const loadComputerStatus: LoadComputerStatus = async (
+ alias: string,
+ ): Promise<ComputerStatusResult | null> => {
+ const result = await store.computerStatus(alias);
+ if (result === null) return null;
+ return result.ok ? { ok: true, status: result.response } : { ok: false, error: result.error };
+ };
+
+ const testComputer: TestComputer = async (
+ alias: string,
+ ): Promise<TestComputerResult | null> => {
+ const result = await store.testComputer(alias);
+ if (result === null) return null;
+ return result.ok ? { ok: true, response: result.response } : { ok: false, error: result.error };
+ };
+
async function loadMcpStatus(): Promise<McpStatusResult | null> {
const result = await store.mcpStatus();
if (result === null) return null;
@@ -493,6 +527,14 @@
{#key store.currentConversationId}
<ReasoningEffortSelector persisted={store.reasoningEffort} save={saveReasoningEffort} />
<CwdField cwd={store.cwd} canEdit={true} save={saveCwd} />
+ <ComputerField
+ computerId={store.computerId}
+ canEdit={true}
+ computers={store.computers}
+ save={saveComputer}
+ loadStatus={loadComputerStatus}
+ test={testComputer}
+ />
{/key}
</div>
{:else if kind === "lsp"}