diff --git a/promo-video/agentcompany-promo.mp4 b/promo-video/agentcompany-promo.mp4
new file mode 100644
index 00000000..2794d8df
Binary files /dev/null and b/promo-video/agentcompany-promo.mp4 differ
diff --git a/promo-video/assets/AI-Internal-Realtime-Message.jpeg b/promo-video/assets/AI-Internal-Realtime-Message.jpeg
new file mode 100644
index 00000000..bd8235e9
Binary files /dev/null and b/promo-video/assets/AI-Internal-Realtime-Message.jpeg differ
diff --git a/promo-video/assets/Approval-by-Human.jpeg b/promo-video/assets/Approval-by-Human.jpeg
new file mode 100644
index 00000000..d5e459ff
Binary files /dev/null and b/promo-video/assets/Approval-by-Human.jpeg differ
diff --git a/promo-video/assets/Auto-OrgChart-Team.jpeg b/promo-video/assets/Auto-OrgChart-Team.jpeg
new file mode 100644
index 00000000..156ed2ad
Binary files /dev/null and b/promo-video/assets/Auto-OrgChart-Team.jpeg differ
diff --git a/promo-video/assets/Auto-Task.jpeg b/promo-video/assets/Auto-Task.jpeg
new file mode 100644
index 00000000..df5096cd
Binary files /dev/null and b/promo-video/assets/Auto-Task.jpeg differ
diff --git a/promo-video/assets/Dashboard.jpeg b/promo-video/assets/Dashboard.jpeg
new file mode 100644
index 00000000..cf4f53e0
Binary files /dev/null and b/promo-video/assets/Dashboard.jpeg differ
diff --git a/promo-video/assets/Document-Center.jpeg b/promo-video/assets/Document-Center.jpeg
new file mode 100644
index 00000000..a40bfb89
Binary files /dev/null and b/promo-video/assets/Document-Center.jpeg differ
diff --git a/promo-video/assets/Onboarding.jpeg b/promo-video/assets/Onboarding.jpeg
new file mode 100644
index 00000000..d80dee0d
Binary files /dev/null and b/promo-video/assets/Onboarding.jpeg differ
diff --git a/promo-video/assets/icon.png b/promo-video/assets/icon.png
new file mode 100644
index 00000000..5888660f
Binary files /dev/null and b/promo-video/assets/icon.png differ
diff --git a/promo-video/assets/icon.svg b/promo-video/assets/icon.svg
new file mode 100644
index 00000000..dd6927c4
--- /dev/null
+++ b/promo-video/assets/icon.svg
@@ -0,0 +1,7 @@
+
diff --git a/promo-video/index.html b/promo-video/index.html
new file mode 100644
index 00000000..2b3bda26
--- /dev/null
+++ b/promo-video/index.html
@@ -0,0 +1,1019 @@
+
+
+
+
+ AgentCompany — Promo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AgentCompany
+
+
v0.4.167
+
+
+
+
FOR AUTONOMOUS AI TEAMS
+
+ Launch an AI company.
+
+
+ Describe your mission. A CEO agent recruits the team,
+ delegates work, and delivers real results.
+
+
+
+
+
ENTERPRISE · LOCAL-FIRST · 26 PROVIDERS
+
agent-company.dev→
+
+
+
+
+
+
+
+
01 · DESCRIBE
+
+ Describe your mission.
+
+
+
+
+
COMPANY MISSION · PLAIN ENGLISH
+
+ Build a marketing agency that ships weekly content, tracks KPIs, and auto-reports to stakeholders.
+
+
+
⌘ + ↵ TO SUBMIT
+
Create Company
+
+
+
+
+
+
+
+
+
+
02 · ASSEMBLE
+
+ A CEO agent recruits the team.
+
+
+
+
+
+
+
CEO
+
+
Chief Executive
+
AUTONOMOUS · CLAUDE OPUS
+
+
+
+
MK
+
+
Marketing Lead
+
HIRED · 00:03
+
+
+
+
CW
+
+
Content Writer
+
HIRED · 00:05
+
+
+
+
DA
+
+
Data Analyst
+
HIRED · 00:07
+
+
+
+
OP
+
+
Ops Manager
+
HIRED · 00:09
+
+
+
+
+
+
+
+
+
+
+
+
03 · EXECUTE
+
+ A team that actually works.
+
+
+
+
+
+
COMMAND DECK
+
Live company dashboard
+
+
+
+
COLLABORATION
+
Real-time agent messages
+
+
+
+
ORCHESTRATION
+
Self-assigning task queue
+
+
+
+
DELIVERABLES
+
AI-generated document center
+
+
+
+
+
+
+
+
+
+
04 · APPROVE
+
+ You stay in control.
+
+
+ Gate sensitive moves behind human approval. Hiring, budget,
+ outbound messages — every critical decision waits for your sign-off.
+
+
+
AI CEO · full autonomy
+
AI CO-FOUNDER · ask first
+
AUDIT TRAIL · every decision
+
+
+
+
+
+
+
+ ✓
+ Approved by founder
+
+
+
+
+
+
+
+
+
+
READY TO LAUNCH?
+
+ Start your AI company today.
+
+
+
+
26
+
AI PROVIDERS
+
+
+
+
13,674+
+
DOWNLOADS
+
+
+
+
100%
+
LOCAL-FIRST
+
+
+
agent-company.dev →
+
+
+
+
AgentCompany
+
+
+
+
+
+
+
+
+
+
+
diff --git a/promo-video/snapshots/frame-00-at-0.5s.png b/promo-video/snapshots/frame-00-at-0.5s.png
new file mode 100644
index 00000000..8425d7cb
Binary files /dev/null and b/promo-video/snapshots/frame-00-at-0.5s.png differ
diff --git a/promo-video/snapshots/frame-01-at-5.0s.png b/promo-video/snapshots/frame-01-at-5.0s.png
new file mode 100644
index 00000000..dbafe92b
Binary files /dev/null and b/promo-video/snapshots/frame-01-at-5.0s.png differ
diff --git a/promo-video/snapshots/frame-02-at-7.0s.png b/promo-video/snapshots/frame-02-at-7.0s.png
new file mode 100644
index 00000000..ccc0bc94
Binary files /dev/null and b/promo-video/snapshots/frame-02-at-7.0s.png differ
diff --git a/promo-video/snapshots/frame-03-at-11.5s.png b/promo-video/snapshots/frame-03-at-11.5s.png
new file mode 100644
index 00000000..ef31f11e
Binary files /dev/null and b/promo-video/snapshots/frame-03-at-11.5s.png differ
diff --git a/promo-video/snapshots/frame-04-at-17.0s.png b/promo-video/snapshots/frame-04-at-17.0s.png
new file mode 100644
index 00000000..f3d01637
Binary files /dev/null and b/promo-video/snapshots/frame-04-at-17.0s.png differ
diff --git a/promo-video/snapshots/frame-05-at-22.5s.png b/promo-video/snapshots/frame-05-at-22.5s.png
new file mode 100644
index 00000000..c027054c
Binary files /dev/null and b/promo-video/snapshots/frame-05-at-22.5s.png differ
diff --git a/promo-video/snapshots/frame-06-at-28.5s.png b/promo-video/snapshots/frame-06-at-28.5s.png
new file mode 100644
index 00000000..1c54481e
Binary files /dev/null and b/promo-video/snapshots/frame-06-at-28.5s.png differ
diff --git a/src/main/agent-runtime/internal-tools/file-approval-handler.ts b/src/main/agent-runtime/internal-tools/file-approval-handler.ts
index e32d9427..c7d4f08d 100644
--- a/src/main/agent-runtime/internal-tools/file-approval-handler.ts
+++ b/src/main/agent-runtime/internal-tools/file-approval-handler.ts
@@ -14,7 +14,7 @@
*/
import type { AppDatabase } from "@main/database";
-import { approvalInputSchema } from "@shared/contracts";
+import { approvalInputSchema, mergeApprovalAttachments } from "@shared/contracts";
import { resolveApprovalDecision } from "@main/approval-policy";
import { shouldAutoApprove } from "@main/execution-policy";
import { UserInputError, UpstreamError, classifyUnknownError } from "@main/errors";
@@ -90,6 +90,25 @@ export function handleFileApprovalToolCall(
}
}
+ // Fold schema-level documentIds / workProductIds into payloadJson so the
+ // desktop inspector has a single source of truth. payloadJson remains the
+ // escape hatch for type-specific structured fields. Malformed payloadJson
+ // (non-JSON or non-object) is a producer bug — surface it to the agent as
+ // UserInputError so the tool call can be corrected, not silently stashed.
+ let mergedPayloadJson: string | null;
+ try {
+ mergedPayloadJson = mergeApprovalAttachments(
+ input.payloadJson,
+ input.documentIds,
+ input.workProductIds,
+ );
+ } catch (error) {
+ throw new UserInputError({
+ code: "file_approval.payload_json.invalid",
+ message: error instanceof Error ? error.message : "payloadJson must be a JSON object or null.",
+ });
+ }
+
let approvalId: string;
try {
approvalId = db.approvalsMod.requestApproval({
@@ -100,7 +119,7 @@ export function handleFileApprovalToolCall(
relatedAgentId: input.relatedAgentId ?? null,
payloadSummary: input.payloadSummary,
impactSummary: input.impactSummary,
- payloadJson: input.payloadJson ?? null,
+ payloadJson: mergedPayloadJson,
});
} catch (error) {
throw classifyUnknownError(error, {
diff --git a/src/main/api-server/routes-approvals.ts b/src/main/api-server/routes-approvals.ts
index eab189e5..ab541de5 100644
--- a/src/main/api-server/routes-approvals.ts
+++ b/src/main/api-server/routes-approvals.ts
@@ -1,6 +1,6 @@
import { z } from "zod";
import log from "electron-log/main.js";
-import { approvalInputSchema } from "@shared/contracts";
+import { approvalInputSchema, mergeApprovalAttachments } from "@shared/contracts";
import { resolveApprovalDecision } from "../approval-policy";
import { shouldAutoApprove } from "../execution-policy";
import { incrementDeprecationCall } from "./deprecation-counter";
@@ -94,6 +94,27 @@ export function registerApprovalRoutes(server: AgentApiServer) {
}
}
+ // Fold the schema-level documentIds / workProductIds into payloadJson so
+ // the ApprovalInspector and extractApprovalDocumentReferences can read a
+ // single source of truth. Legacy callers that stuffed documentIds inside
+ // payloadJson keep working unchanged. Malformed payloadJson (non-JSON
+ // or non-object) surfaces as 400 at the API boundary — no silent
+ // rawPayload stashing, because that hid producer bugs from the operator.
+ let mergedPayloadJson: string | null;
+ try {
+ mergedPayloadJson = mergeApprovalAttachments(
+ input.payloadJson,
+ input.documentIds,
+ input.workProductIds,
+ );
+ } catch (error) {
+ server.json(res, 400, {
+ error: error instanceof Error ? error.message : "Invalid approval payloadJson",
+ code: "INVALID_PAYLOAD_JSON",
+ });
+ return;
+ }
+
let id: string;
try {
id = db.approvalsMod.requestApproval({
@@ -104,7 +125,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
relatedAgentId: input.relatedAgentId ?? null,
payloadSummary: input.payloadSummary,
impactSummary: input.impactSummary,
- payloadJson: input.payloadJson ?? null,
+ payloadJson: mergedPayloadJson,
});
} catch (error) {
server.json(res, 422, { error: error instanceof Error ? error.message : "Approval could not be created" });
diff --git a/src/main/prompt-builder/api-docs/approvals.en.md b/src/main/prompt-builder/api-docs/approvals.en.md
index 03c45c7d..bc74059c 100644
--- a/src/main/prompt-builder/api-docs/approvals.en.md
+++ b/src/main/prompt-builder/api-docs/approvals.en.md
@@ -5,7 +5,9 @@ Body: {
"relatedTaskId": "{taskId — what task is this for}",
"requestedByAgentId": "{{agentId}}",
"payloadSummary": "Clear description of what you need and why",
- "impactSummary": "What happens if this is approved vs denied"
+ "impactSummary": "What happens if this is approved vs denied",
+ "documentIds": ["{docUuid1}", "{docUuid2}"],
+ "workProductIds": ["{workProductUuid}"]
}
Approval types:
- secret_access: Need API keys, credentials, or account access
@@ -13,6 +15,30 @@ Approval types:
- approve_ceo_strategy: CEO submitting org strategy for Board review
- dangerous_command: Irreversible action that needs human sign-off
+Attaching artifacts:
+ - `documentIds` — UUIDs of existing company documents. The board inspector
+ renders each as a clickable row on the first screen of the approval modal,
+ so reviewers can open and edit inline.
+ - `workProductIds` — UUIDs of existing work products (files on disk,
+ external URLs). The inspector surfaces each row with a click-to-open
+ action that hands off to the operator's default application.
+ - Prefer these schema-typed arrays over stuffing `{documentIds:[...]}` into
+ `payloadJson`. Agents using the `file_approval` internal tool pass the
+ same fields directly as arguments.
+
+Example — deliverable_review that asks the CEO to sign off on a spec and an
+exported PDF:
+```
+{
+ "type": "deliverable_review",
+ "relatedTaskId": "8f7a...",
+ "payloadSummary": "Q2 launch plan ready for CEO review",
+ "impactSummary": "Unblocks the kickoff email if approved; revision_requested loops back to me.",
+ "documentIds": ["2c9d...e41a"],
+ "workProductIds": ["55ab...90ff"]
+}
+```
+
### Check Approval Status
GET /api/approvals/{approvalId}
diff --git a/src/main/worker/runtime/internal-tools/file-approval-tool.ts b/src/main/worker/runtime/internal-tools/file-approval-tool.ts
index f19f996c..0cc0a5c7 100644
--- a/src/main/worker/runtime/internal-tools/file-approval-tool.ts
+++ b/src/main/worker/runtime/internal-tools/file-approval-tool.ts
@@ -76,7 +76,19 @@ export const fileApprovalSchema = Type.Object({
payloadJson: Type.Optional(
Type.String({
description:
- "Optional JSON string carrying structured approval payload. Must be valid JSON if provided.",
+ "Optional JSON string carrying type-specific structured payload (hire proposal, social draft, etc.). Must be valid JSON if provided. For attaching existing documents or work products, use documentIds / workProductIds — the board UI renders those directly on the first screen.",
+ }),
+ ),
+ documentIds: Type.Optional(
+ Type.Array(Type.String(), {
+ description:
+ "UUIDs of existing company documents to attach to this approval. The board inspector surfaces each attachment on the first screen of the approval modal. Prefer this over jamming {documentIds:[...]} into payloadJson.",
+ }),
+ ),
+ workProductIds: Type.Optional(
+ Type.Array(Type.String(), {
+ description:
+ "UUIDs of existing work products (files on disk, external URLs) to attach to this approval. The board inspector renders each row as a clickable link that opens in the operator's default application.",
}),
),
relatedTaskId: Type.Optional(
@@ -104,7 +116,7 @@ export function createFileApprovalTool(rpc: WorkerRpcClient, runId: string): Age
name: FILE_APPROVAL_TOOL_NAME,
label: "file approval",
description:
- "File an approval request with the company board. Use for 'approve_ceo_strategy' after filing initial hires, or for any other decision that requires board sign-off. The request is auto-approved when the company is in hands-off mode; otherwise it waits for the human operator.",
+ "File an approval request with the company board. Use for 'approve_ceo_strategy' after filing initial hires, or for any other decision that requires board sign-off. When the approval references existing documents or work products (e.g. deliverable_review, document_review), pass their UUIDs via documentIds / workProductIds so the board inspector can render them as clickable attachments on the first screen — do not bury IDs inside payloadJson. The request is auto-approved when the company is in hands-off mode; otherwise it waits for the human operator.",
parameters: fileApprovalSchema,
async execute(toolCallId, params, _signal?, _onUpdate?) {
const payload: InvokeInternalToolRpcPayload = {
diff --git a/src/renderer/components/TwoPaneLayout.tsx b/src/renderer/components/TwoPaneLayout.tsx
index c6119e30..8775313c 100644
--- a/src/renderer/components/TwoPaneLayout.tsx
+++ b/src/renderer/components/TwoPaneLayout.tsx
@@ -2,7 +2,17 @@ import type { ReactNode } from "react";
import { Eyebrow } from "./ui";
/**
- * Two-pane layout with a section header.
+ * Two-pane layout with an optional section header.
+ * - `split="vertical"` (default): stacks primary over secondary with rhythmic whitespace.
+ * - `split="horizontal"`: primary (register) on the left, secondary (focus) on the right.
+ * Use horizontal when the two panes share a single focus — selecting a row on the
+ * left updates the inspector on the right, and the page should present one idea.
+ *
+ * `action` docks a single control (e.g. a primary "+ Add" button) to the right of the
+ * header row. When a title/eyebrow is present the action sits on the header's baseline;
+ * when the header is absent, the action sits in its own right-aligned row above the body.
+ * Keep it to one element — this slot exists so the control has a home, not a stage.
+ *
* Use `Panel` (from ui/) for single-slot sections. Use this when rendering a primary + secondary pane.
*/
export function TwoPaneLayout({
@@ -10,19 +20,40 @@ export function TwoPaneLayout({
eyebrow,
primary,
secondary,
+ split = "vertical",
+ action,
}: {
- title: string;
+ title?: string;
eyebrow?: string;
primary: ReactNode;
secondary: ReactNode;
+ split?: "vertical" | "horizontal";
+ action?: ReactNode;
}) {
+ const hasHeader = Boolean(title || eyebrow);
+ const body =
+ split === "horizontal" ? (
+
+
{primary}
+
{secondary}
+
+ ) : (
+
{primary}{secondary}
+ );
return (
-
- {eyebrow ? {eyebrow} : null}
-
{title}
-
-
{primary}{secondary}
+ {hasHeader ? (
+
+
+ {eyebrow ? {eyebrow} : null}
+ {title ?
{title}
: null}
+
+ {action ?
{action}
: null}
+
+ ) : action ? (
+
{action}
+ ) : null}
+ {body}
);
}
diff --git a/src/renderer/components/automation/AutomationPanel.tsx b/src/renderer/components/automation/AutomationPanel.tsx
index 58f14e08..c77b242a 100644
--- a/src/renderer/components/automation/AutomationPanel.tsx
+++ b/src/renderer/components/automation/AutomationPanel.tsx
@@ -139,9 +139,6 @@ function RulesPanel({ rules, companyId, onRefresh, t }: { rules: AutomationRuleR