feat(apps): M3 installer — catalog/URL installs, updates, rollback, uninstall

- installer (§12): streaming download with size cap, extraction guards
  (zip-slip, symlink entries, entry-count, uncompressed cap), bundle-identity
  check, D18 capability-mismatch check against the previewed manifest,
  defaults->data on first install, pinned per-file sha256, folder suffixing
- URL installs (§12.5): two-phase preview from the bundle's own manifest,
  10-min retained staging, GitHub-provenance detection for later updates
- update (§12.3): D18 scoped to the diff (new_capabilities), modified-file
  warning against pinned hashes, .previous/ swap with one-step rollback
- uninstall (§12.4) deletes app-owned bg-tasks; startup tmp cleanup
- IPC: apps:catalogIndex/Search/Detail, install, installFromUrl, uninstall,
  checkUpdate, update, rollback + analytics events
This commit is contained in:
Gagan 2026-07-06 15:02:00 +05:30
parent d7af51c10b
commit 6dea528fb4
3 changed files with 652 additions and 1 deletions

View file

@ -18,7 +18,7 @@ import { RequestedAgent, type TurnEvent } from './turns.js';
import type { SessionBusEvent, SessionIndexEntry, SessionState } from './sessions.js';
import { RowboatApiConfig } from './rowboat-account.js';
import { ZListToolkitsResponse } from './composio.js';
import { AppSummarySchema } from './rowboat-app.js';
import { AppSummarySchema, RegistryRecordSchema, RowboatAppManifestSchema } from './rowboat-app.js';
import { BrowserStateSchema, HttpAuthRequestSchema } from './browser-control.js';
import { BillingInfoSchema } from './billing.js';
import { EmailBlockSchema, GmailThreadSchema } from './blocks.js';
@ -1256,6 +1256,69 @@ const ipcSchemas = {
req: z.object({ theme: z.enum(['light', 'dark']) }),
res: z.object({ ok: z.literal(true) }),
},
// Catalog + install/update (spec §1213).
'apps:catalogIndex': {
req: z.object({ force: z.boolean().optional() }),
res: z.object({ records: z.array(RegistryRecordSchema), stale: z.boolean(), fetchedAt: z.string() }),
},
'apps:catalogSearch': {
req: z.object({ query: z.string() }),
res: z.object({ records: z.array(RegistryRecordSchema) }),
},
'apps:catalogDetail': {
req: z.object({ name: z.string() }),
res: z.object({
record: RegistryRecordSchema,
manifest: RowboatAppManifestSchema.optional(),
readme: z.string().optional(),
installedFolder: z.string().optional(),
}),
},
'apps:install': {
req: z.object({ name: z.string(), confirmed: z.boolean().optional() }),
res: z.object({
status: z.enum(['preview', 'installed']),
name: z.string().optional(),
version: z.string().optional(),
description: z.string().optional(),
capabilities: z.array(z.string()).optional(),
agents: z.array(z.string()).optional(),
app: AppSummarySchema.optional(),
}),
},
'apps:installFromUrl': {
req: z.object({ url: z.string(), confirmed: z.boolean() }),
res: z.object({
status: z.enum(['preview', 'installed']),
name: z.string().optional(),
version: z.string().optional(),
description: z.string().optional(),
capabilities: z.array(z.string()).optional(),
agents: z.array(z.string()).optional(),
updateSource: z.enum(['github', 'none']).optional(),
app: AppSummarySchema.optional(),
}),
},
'apps:uninstall': {
req: z.object({ folder: z.string() }),
res: z.object({ ok: z.literal(true) }),
},
'apps:checkUpdate': {
req: z.object({ folder: z.string() }),
res: z.object({ current: z.string(), latest: z.string(), updateAvailable: z.boolean() }),
},
'apps:update': {
req: z.object({
folder: z.string(),
confirmOverwriteModified: z.boolean().optional(),
confirmNewCapabilities: z.boolean().optional(),
}),
res: z.object({ app: AppSummarySchema }),
},
'apps:rollback': {
req: z.object({ folder: z.string() }),
res: z.object({ app: AppSummarySchema }),
},
// GitHub auth (device flow) — required only for publishing apps (spec §10).
'githubAuth:start': {
req: z.object({}),