-
Notifications
You must be signed in to change notification settings - Fork 551
Add portable BotMRR Markdown playbooks #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d565c2d
7269382
e4759b1
c446c91
dfc3c26
06a0531
6550da6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| const ALLOWED_PACKAGE_HOSTS = new Set(["github.com", "www.github.com", "raw.githubusercontent.com"]); | ||
|
|
||
| export function packageUrlFromDeepLink(rawValue) { | ||
| let link; | ||
| try { | ||
| link = new URL(String(rawValue)); | ||
| } catch { | ||
| return null; | ||
| } | ||
| if (link.protocol !== "openmausbot:" || link.hostname !== "install") return null; | ||
| const rawPackage = link.searchParams.get("url"); | ||
| if (!rawPackage) return null; | ||
| let packageUrl; | ||
| try { | ||
| packageUrl = new URL(rawPackage); | ||
| } catch { | ||
| return null; | ||
| } | ||
| if ( | ||
| packageUrl.protocol !== "https:" || | ||
| packageUrl.username || | ||
| packageUrl.password || | ||
| packageUrl.port || | ||
| !ALLOWED_PACKAGE_HOSTS.has(packageUrl.hostname) || | ||
| !packageUrl.pathname.match(/\.(?:md|json)$/) | ||
| ) return null; | ||
| return packageUrl.toString(); | ||
| } | ||
|
|
||
| export function packageUrlFromCommandLine(argv) { | ||
| for (const value of argv) { | ||
| const parsed = packageUrlFromDeepLink(value); | ||
| if (parsed) return parsed; | ||
| } | ||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import assert from "node:assert/strict"; | ||
| import { describe, it } from "node:test"; | ||
|
|
||
| import { packageUrlFromCommandLine, packageUrlFromDeepLink } from "./package-link.mjs"; | ||
|
|
||
| describe("BotMRR package deep links", () => { | ||
| it("accepts a public GitHub package URL", () => { | ||
| const target = "https://raw.githubusercontent.com/acme/bots/main/reddit-lead-miner.md"; | ||
| assert.equal(packageUrlFromDeepLink(`openmausbot://install?url=${encodeURIComponent(target)}`), target); | ||
| assert.equal(packageUrlFromCommandLine(["OpenMausBot", "--flag", `openmausbot://install?url=${encodeURIComponent(target)}`]), target); | ||
| }); | ||
|
|
||
| it("rejects other commands, hosts, protocols, credentials, and unsupported file types", () => { | ||
| assert.equal(packageUrlFromDeepLink("openmausbot://settings"), null); | ||
| assert.equal(packageUrlFromDeepLink("openmausbot://install?url=https://evil.example/bot.json"), null); | ||
| assert.equal(packageUrlFromDeepLink("openmausbot://install?url=http://raw.githubusercontent.com/a/b/main/bot.json"), null); | ||
| assert.equal(packageUrlFromDeepLink("openmausbot://install?url=https://user@example.com/bot.json"), null); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win Test credential rejection on an approved host. This URL is rejected because 🤖 Prompt for AI Agents |
||
| assert.equal(packageUrlFromDeepLink("openmausbot://install?url=https://github.com/acme/bot/run.sh"), null); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { packageAgentAsMember, parseBotPackage, renderBotPackageMarkdown } from "./bot-package.ts"; | ||
|
|
||
| const validPackage: any = { | ||
| format: "openmaus.package", | ||
| version: 1, | ||
| package: { | ||
| id: "research-desk", | ||
| release: "1.0.0", | ||
| name: "Research Desk", | ||
| tagline: "Turn a question into a sourced brief.", | ||
| summary: "A small research team.", | ||
| category: "Research", | ||
| author: { name: "OpenMausBot" }, | ||
| license: "MIT", | ||
| outcomes: ["Produce a sourced brief."], | ||
| setupMinutes: 3, | ||
| requirements: { apps: [], capabilities: [] }, | ||
| agents: [ | ||
| { | ||
| key: "lead", | ||
| name: "Ada", | ||
| title: "Research Lead", | ||
| description: "Own the brief.", | ||
| appearance: { color: "purple" }, | ||
| playbooks: ["source-check"], | ||
| autoApprove: true, | ||
| }, | ||
| ], | ||
| chiefOfStaff: "lead", | ||
| rooms: [ | ||
| { | ||
| key: "desk", | ||
| name: "Research Desk", | ||
| members: ["lead"], | ||
| bulletin: "Cite sources.", | ||
| defaultResponder: { kind: "agent", agent: "lead" }, | ||
| }, | ||
| ], | ||
| playbooks: [ | ||
| { | ||
| key: "source-check", | ||
| name: "Source Check", | ||
| summary: "Verify sources.", | ||
| triggers: ["research brief"], | ||
| instructions: "Separate facts from inference.", | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
|
|
||
| describe("bot packages", () => { | ||
| it("parses the complete portable structure and strips authority fields", () => { | ||
| const parsed = parseBotPackage(validPackage); | ||
| expect(parsed.package.rooms![0]?.defaultResponder).toEqual({ kind: "agent", agent: "lead" }); | ||
| expect(parsed.package.agents[0]).not.toHaveProperty("autoApprove"); | ||
| expect(packageAgentAsMember(parsed.package.agents[0]!)).toEqual({ | ||
| key: "lead", | ||
| name: "Ada", | ||
| title: "Research Lead", | ||
| description: "Own the brief.", | ||
| appearance: { color: "purple" }, | ||
| }); | ||
| }); | ||
|
|
||
| it("round-trips one Chief-of-Staff-readable Markdown playbook", () => { | ||
| const markdown = renderBotPackageMarkdown(parseBotPackage(validPackage)); | ||
| expect(markdown).toContain("## Activation"); | ||
| expect(markdown).toContain("Give this file to your Chief of Staff"); | ||
| expect(markdown).not.toContain("autoApprove"); | ||
| expect(parseBotPackage(markdown).package).toMatchObject({ | ||
| id: "research-desk", | ||
| chiefOfStaff: "lead", | ||
| agents: [{ key: "lead", name: "Ada" }], | ||
| }); | ||
| }); | ||
|
|
||
| it("rejects dangling agent, room, playbook, chief, and routine references", () => { | ||
| expect(() => parseBotPackage({ | ||
| ...validPackage, | ||
| package: { ...validPackage.package, chiefOfStaff: "missing" }, | ||
| })).toThrow("Unknown Chief of Staff"); | ||
| expect(() => parseBotPackage({ | ||
| ...validPackage, | ||
| package: { | ||
| ...validPackage.package, | ||
| agents: [{ ...validPackage.package.agents[0], playbooks: ["missing"] }], | ||
| }, | ||
| })).toThrow("unknown playbook"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve every pending package installation URL.
Both processes store only one pending URL. If two valid deep links arrive before
did-finish-loador before the Sidebar callback registers, the later URL overwrites the earlier URL. The first installation request is then lost.Use FIFO queues in the main process and preload bridge. Drain URLs in order after the renderer and callback are ready. Remove each URL after delivery. Add coverage for two startup or second-instance links.
electron/main.mjs#L43-L43: Initialize a pending URL queue.electron/main.mjs#L56-L69: Deliver all queued URLs in order.electron/main.mjs#L78-L83: Append second-instance URLs instead of replacing a queued URL.electron/main.mjs#L582-L582: Flush the complete queue after page load.electron/preload.cjs#L5-L11: Buffer all IPC URLs until a listener exists.electron/preload.cjs#L111-L115: Drain buffered URLs once and prevent stale replay to later subscribers.📍 Affects 2 files
electron/main.mjs#L43-L43(this comment)electron/main.mjs#L56-L69electron/main.mjs#L78-L83electron/main.mjs#L582-L582electron/preload.cjs#L5-L11electron/preload.cjs#L111-L115🤖 Prompt for AI Agents