mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
Merge pull request #665 from rowboatlabs/feature/apps-v1
Rowboat Apps V1 — M1 + M2 (spec implementation)
This commit is contained in:
commit
b7d1019538
32 changed files with 3332 additions and 1317 deletions
|
|
@ -33,6 +33,9 @@ export type BackgroundTask = {
|
|||
projectId?: string;
|
||||
model?: string;
|
||||
provider?: string;
|
||||
// Folder slug of the Rowboat App that installed this task (spec §8.2).
|
||||
// Runtime-managed; tasks with sourceApp are owned by the app lifecycle.
|
||||
sourceApp?: string;
|
||||
createdAt: string;
|
||||
// Runtime-managed — never hand-write. Mirrors live-note's flat-field
|
||||
// pattern: `lastAttemptAt` is bumped at every run start (backoff anchor),
|
||||
|
|
@ -53,6 +56,7 @@ export type BackgroundTaskSummary = {
|
|||
active: boolean;
|
||||
triggers?: Triggers;
|
||||
projectId?: string;
|
||||
sourceApp?: string;
|
||||
createdAt: string;
|
||||
lastAttemptAt?: string;
|
||||
lastRunId?: string;
|
||||
|
|
@ -71,6 +75,7 @@ export const BackgroundTaskSchema = z.object({
|
|||
projectId: z.string().optional().describe('When set, marks this as a coding task pinned to a registered code project (repo). The agent implements detected work via the launch-code-task tool, each launch in its own isolated worktree.'),
|
||||
model: z.string().optional().describe('ADVANCED — leave unset. Per-task model override.'),
|
||||
provider: z.string().optional().describe('ADVANCED — leave unset. Per-task provider name override.'),
|
||||
sourceApp: z.string().optional().describe('Folder slug of the app that installed this task. Runtime-managed.'),
|
||||
createdAt: z.string().describe('ISO timestamp set once at create-time.'),
|
||||
lastAttemptAt: z.string().optional().describe('Runtime-managed — never write this yourself. Bumped at the start of every agent run; used by the scheduler for backoff so failures do not retry-storm.'),
|
||||
lastRunId: z.string().optional().describe('Runtime-managed — never write this yourself. The id of the most recent run (success or failure); used by the bg-task:stop handler.'),
|
||||
|
|
@ -86,6 +91,7 @@ export const BackgroundTaskSummarySchema = z.object({
|
|||
active: z.boolean(),
|
||||
triggers: TriggersSchema.optional(),
|
||||
projectId: z.string().optional(),
|
||||
sourceApp: z.string().optional(),
|
||||
createdAt: z.string(),
|
||||
lastAttemptAt: z.string().optional(),
|
||||
lastRunId: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -20,4 +20,5 @@ export * as billing from './billing.js';
|
|||
export * as notificationSettings from './notification-settings.js';
|
||||
export * as codeSessions from './code-sessions.js';
|
||||
export * as channels from './channels.js';
|
||||
export * as rowboatApp from './rowboat-app.js';
|
||||
export { PrefixLogger };
|
||||
|
|
|
|||
|
|
@ -18,6 +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 { BrowserStateSchema, HttpAuthRequestSchema } from './browser-control.js';
|
||||
import { BillingInfoSchema } from './billing.js';
|
||||
import { EmailBlockSchema, GmailThreadSchema } from './blocks.js';
|
||||
|
|
@ -1226,6 +1227,35 @@ const ipcSchemas = {
|
|||
shouldShow: z.boolean(),
|
||||
}),
|
||||
},
|
||||
// Rowboat Apps (spec §13) — M1 local channels.
|
||||
'apps:list': {
|
||||
req: z.object({}),
|
||||
res: z.object({
|
||||
serverRunning: z.boolean(),
|
||||
serverError: z.string().optional(),
|
||||
apps: z.array(AppSummarySchema),
|
||||
}),
|
||||
},
|
||||
'apps:get': {
|
||||
req: z.object({ folder: z.string() }),
|
||||
res: z.object({
|
||||
app: AppSummarySchema,
|
||||
readme: z.string().optional(),
|
||||
rollbackAvailable: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'apps:create': {
|
||||
req: z.object({ folder: z.string(), name: z.string(), description: z.string() }),
|
||||
res: z.object({ app: AppSummarySchema }),
|
||||
},
|
||||
'apps:delete': {
|
||||
req: z.object({ folder: z.string() }),
|
||||
res: z.object({ ok: z.literal(true) }),
|
||||
},
|
||||
'apps:setTheme': {
|
||||
req: z.object({ theme: z.enum(['light', 'dark']) }),
|
||||
res: z.object({ ok: z.literal(true) }),
|
||||
},
|
||||
'composio:didConnect': {
|
||||
req: z.object({
|
||||
toolkitSlug: z.string(),
|
||||
|
|
@ -1239,6 +1269,34 @@ const ipcSchemas = {
|
|||
req: z.object({}),
|
||||
res: ZListToolkitsResponse,
|
||||
},
|
||||
// Mini Apps: execute a Composio tool by slug (scoped to a connected toolkit).
|
||||
'composio:execute-tool': {
|
||||
req: z.object({
|
||||
toolkitSlug: z.string(),
|
||||
toolSlug: z.string(),
|
||||
arguments: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
res: z.object({
|
||||
successful: z.boolean(),
|
||||
data: z.unknown().optional(),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
// Mini Apps: search Composio tools within a toolkit (returns slugs + schemas).
|
||||
'composio:search-tools': {
|
||||
req: z.object({
|
||||
toolkitSlug: z.string(),
|
||||
query: z.string(),
|
||||
}),
|
||||
res: z.object({
|
||||
tools: z.array(z.object({
|
||||
slug: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
})),
|
||||
error: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
// Agent schedule channels
|
||||
'agent-schedule:getConfig': {
|
||||
req: z.null(),
|
||||
|
|
|
|||
113
apps/x/packages/shared/src/rowboat-app.ts
Normal file
113
apps/x/packages/shared/src/rowboat-app.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
// Rowboat Apps schemas (spec §4.2, §5.1, §9.1, §11.4, §12.2).
|
||||
|
||||
export const PACKAGE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||
export const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Manifest — rowboat-app.json (§4.2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const RowboatAppManifestSchema = z.object({
|
||||
schemaVersion: z.literal(1),
|
||||
name: z.string().min(3).max(64).regex(PACKAGE_NAME_RE)
|
||||
.describe('Package identity. Globally unique and immutable once published.'),
|
||||
version: z.string().regex(SEMVER_RE)
|
||||
.describe('Strict semver, no prerelease/build suffix in V1.'),
|
||||
description: z.string().max(500).default(''),
|
||||
icon: z.string().optional()
|
||||
.describe('Path relative to dist/. Same traversal rules as entry.'),
|
||||
entry: z.string().default('index.html')
|
||||
.describe('Path relative to dist/. Serves as app root and SPA fallback.'),
|
||||
agents: z.array(z.string().regex(/^[a-z0-9][a-z0-9-_]*\.yaml$/)).default([])
|
||||
.describe('Filenames under agents/. Each must exist in the package.'),
|
||||
capabilities: z.array(z.string()).default([])
|
||||
.describe('Capability identifiers this app may use (D7): Composio toolkit slugs for /_rowboat/tools/*, plus the reserved identifiers "llm" (§7.6) and "copilot" (§7.7). Empty = none.'),
|
||||
dataContracts: z.array(z.object({
|
||||
file: z.string(), // path relative to data/, e.g. "data.json"
|
||||
requiredKeys: z.array(z.string()).default([]),
|
||||
nonEmptyArrayKeys: z.array(z.string()).default([]),
|
||||
})).default([])
|
||||
.describe('Write guards for specific data/ files: required top-level keys and keys that must stay non-empty arrays. Enforced on writes (§7.3, §8.6) so a buggy agent run cannot corrupt the app\'s data shape or wipe good series with empties.'),
|
||||
// RESERVED — validated if present, ignored by V1 runtime:
|
||||
build: z.object({ command: z.string() }).optional()
|
||||
.describe('RESERVED. Rowboat MUST NOT execute this in V1.'),
|
||||
minRowboatVersion: z.string().regex(SEMVER_RE).optional()
|
||||
.describe('RESERVED. Minimum compatible Rowboat version; not enforced in V1.'),
|
||||
}).passthrough(); // unknown fields survive round-trips (forward compatibility)
|
||||
|
||||
export type RowboatAppManifest = z.infer<typeof RowboatAppManifestSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Install record — .rowboat-install.json (§12.2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const AppInstallRecordSchema = z.object({
|
||||
name: z.string(),
|
||||
repo: z.string().optional(), // owner/repo at install time; absent only for non-GitHub URL installs (§12.5)
|
||||
sourceUrl: z.string().optional(), // present only for §12.5 URL installs
|
||||
version: z.string(),
|
||||
sha256: z.string(), // bundle checksum, pinned at install
|
||||
installedAt: z.string(),
|
||||
updatedAt: z.string().optional(),
|
||||
files: z.record(z.string(), z.string()), // relpath → sha256 of release-managed files
|
||||
previousVersion: z.string().optional(), // set while .previous/ exists
|
||||
});
|
||||
|
||||
export type AppInstallRecord = z.infer<typeof AppInstallRecordSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Publish record — .rowboat-publish.json (§11.4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const AppPublishRecordSchema = z.object({
|
||||
name: z.string(),
|
||||
login: z.string(), // publisher GitHub login
|
||||
repo: z.string(), // owner/repo
|
||||
lastPublishedVersion: z.string().optional(),
|
||||
lastSha256: z.string().optional(),
|
||||
pendingSteps: z.object({ // present only mid-publish (resume state)
|
||||
version: z.string(),
|
||||
completed: z.array(z.string()), // step names from §11.2
|
||||
releaseId: z.number().optional(),
|
||||
prUrl: z.string().optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export type AppPublishRecord = z.infer<typeof AppPublishRecordSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// App summary — apps:list / apps:get (§5.1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const AppSummarySchema = z.object({
|
||||
folder: z.string(), // folder slug
|
||||
status: z.enum(['ok', 'invalid']),
|
||||
manifest: RowboatAppManifestSchema.optional(), // present when ok
|
||||
manifestError: z.string().optional(), // present when invalid
|
||||
origin: z.string(), // http://<folder>.apps.localhost:3210
|
||||
kind: z.enum(['local', 'installed']),
|
||||
install: AppInstallRecordSchema.optional(), // §12.2
|
||||
publish: AppPublishRecordSchema.optional(), // §11.4
|
||||
hasDist: z.boolean(),
|
||||
agentSlugs: z.array(z.string()), // materialized bg-task slugs (§8.3)
|
||||
});
|
||||
|
||||
export type AppSummary = z.infer<typeof AppSummarySchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registry record — apps/<name>.json in the registry repo (§9.1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const RegistryRecordSchema = z.object({
|
||||
schemaVersion: z.literal(1),
|
||||
name: z.string().min(3).max(64).regex(PACKAGE_NAME_RE),
|
||||
owner: z.string().min(1), // GitHub login of the publisher
|
||||
repo: z.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/), // "owner/repo"
|
||||
description: z.string().max(500).default(''),
|
||||
iconUrl: z.string().url().optional(), // https URL for the catalog listing icon
|
||||
createdAt: z.string(), // ISO 8601
|
||||
}).strict();
|
||||
|
||||
export type RegistryRecord = z.infer<typeof RegistryRecordSchema>;
|
||||
|
|
@ -32,6 +32,8 @@ export const StartEvent = BaseRunEvent.extend({
|
|||
"meeting_note",
|
||||
"knowledge_sync",
|
||||
"code_session",
|
||||
"app_llm_generate",
|
||||
"app_copilot_run",
|
||||
]).optional(),
|
||||
subUseCase: z.string().optional(),
|
||||
});
|
||||
|
|
@ -202,6 +204,8 @@ export const UseCase = z.enum([
|
|||
"meeting_note",
|
||||
"knowledge_sync",
|
||||
"code_session",
|
||||
"app_llm_generate",
|
||||
"app_copilot_run",
|
||||
]);
|
||||
|
||||
export const Run = z.object({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue