Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Maka
Copyright 2026 The Maka Authors

The macOS Computer History helper includes clean-room code from
open-codex-computer-history, Copyright 2026 Haoqing Wang, used under the MIT
License. See licenses/open-computer-history/LICENSE in packaged applications.

This product includes software developed by the Maka project.

Third-party components remain subject to their respective licenses and
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/e2e/computer-history.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { COMPOSER_INPUT, expect, test } from './fixtures';

test('Computer History opens as a workbar tool and adds reduced context to chat', async ({
computerHistoryWindow: page,
}, testInfo) => {
const composer = page.locator(COMPOSER_INPUT);
await composer.fill('create history session');
await composer.press('Enter');
await expect(page.getByText(/Fake backend received: create history session/)).toBeVisible();

await page.getByRole('button', { name: '展开任务工作栏' }).click();
await page.getByRole('button', { name: /电脑历史.*查看本机应用活动/ }).click();
const panel = page.locator('.computerHistoryPanel');
await expect(panel.getByText('Notes · Launch checklist')).toBeVisible();
await expect(panel.getByText('Browser · Release dashboard')).toBeVisible();
await expect(panel.getByText('输入文本采集关闭;只保留应用、窗口和交互类型。')).toBeVisible();
await page.screenshot({
path: testInfo.outputPath('computer-history-desktop.png'),
fullPage: true,
});

await panel.getByRole('button', { name: '加入对话' }).first().click();
await expect(composer).toContainText('<computer-history-context');
await expect(composer).not.toContainText('secret');

await page.setViewportSize({ width: 375, height: 780 });
await expect(panel).toBeVisible();
const overflow = await panel.evaluate(
(node) => node.scrollWidth > node.clientWidth,
);
expect(overflow).toBe(false);
await page.screenshot({
path: testInfo.outputPath('computer-history-375.png'),
fullPage: true,
});
});
42 changes: 42 additions & 0 deletions apps/desktop/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,35 @@ async function seedE2eLocale(userDataDir: string, locale: 'zh' | 'en'): Promise<
});
}

async function seedE2eComputerHistory(userDataDir: string): Promise<void> {
const historyRoot = path.join(userDataDir, 'computer-history');
const segmentRoot = path.join(historyRoot, 'segments', '2026-08-15T09-20-00Z-e2e');
await mkdir(segmentRoot, { recursive: true });
const event = (timestamp: string, kind: string, app: string, window: string) =>
JSON.stringify({
timestamp,
kind,
app: { name: app, bundleIdentifier: `com.maka.e2e.${app.toLowerCase()}` },
window: { title: window },
});
await writeFile(
path.join(segmentRoot, 'events.jsonl'),
[
event('2026-08-15T09:20:00.000Z', 'window.changed', 'Notes', 'Launch checklist'),
event('2026-08-15T09:20:03.000Z', 'keyboard.shortcut', 'Notes', 'Launch checklist'),
event('2026-08-15T09:20:08.000Z', 'mouse.click', 'Notes', 'Launch checklist'),
event('2026-08-15T09:34:00.000Z', 'window.changed', 'Browser', 'Release dashboard'),
event('2026-08-15T09:34:05.000Z', 'mouse.click', 'Browser', 'Release dashboard'),
].join('\n') + '\n',
'utf8',
);
await writeFile(
path.join(segmentRoot, 'metadata.json'),
JSON.stringify({ suppressedEventCount: 1 }),
'utf8',
);
}

async function seedE2eInvocableSkills(userDataDir: string): Promise<void> {
const workspaceRoot = path.join(userDataDir, 'workspaces', 'default');
const projectRoot = path.join(userDataDir, 'project');
Expand Down Expand Up @@ -249,6 +278,7 @@ async function withE2eWindow(
scrollMotion,
invocableSkills,
gitReviewExtraFiles,
computerHistory,
}: {
seed: boolean;
readinessSelector: string;
Expand All @@ -262,6 +292,7 @@ async function withE2eWindow(
showWindow?: boolean;
invocableSkills?: boolean;
gitReviewExtraFiles?: number;
computerHistory?: boolean;
},
use: (page: Page, context: { userDataDir: string }) => Promise<void>,
): Promise<void> {
Expand All @@ -279,6 +310,7 @@ async function withE2eWindow(
if (gitReviewExtraFiles !== undefined) {
await seedE2eGitReviewProject(userDataDir, gitReviewExtraFiles);
}
if (computerHistory) await seedE2eComputerHistory(userDataDir);
// Legacy E2E specs assert Chinese labels and should not inherit the CI
// host locale. E2e-fixture workspaces use the explicit renderer override.
if (locale && !e2eFixtureScenario) await seedE2eLocale(userDataDir, locale);
Expand Down Expand Up @@ -345,6 +377,7 @@ export const test = base.extend<{
projectSidebarWindow: Page;
promptRailWindow: Page;
promptRailMotionWindow: Page;
computerHistoryWindow: Page;
}>({
// Seeded: a pre-staged connection clears onboarding so the composer is ready.
window: async ({}, use) => {
Expand Down Expand Up @@ -420,6 +453,15 @@ export const test = base.extend<{
scrollMotion: 'smooth',
}, use);
},
computerHistoryWindow: async ({}, use) => {
await withE2eWindow({
seed: true,
readinessSelector: COMPOSER_INPUT,
locale: 'zh',
computerHistory: true,
showWindow: true,
}, use);
},
});

export { expect };
10 changes: 10 additions & 0 deletions apps/desktop/electron-builder.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ export default {
from: '../../LICENSE',
to: 'licenses/renderer/MINGCUTE_APACHE_LICENSE.txt',
},
{
from: 'resources/licenses/open-computer-history/LICENSE',
to: 'licenses/open-computer-history/LICENSE',
},
{
// Vendored copy of the installed tarball's LICENSE (CC0-1.0): the
// package hoists to different node_modules depths across majors.
Expand All @@ -118,6 +122,12 @@ export default {
},
],
mac: {
extraResources: [
{
from: 'resources/bin/open-history',
to: 'bin/open-history',
},
],
target: [
{ target: 'dmg', arch: ['arm64'] },
{ target: 'zip', arch: ['arm64'] },
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/native/computer-history/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.build/
23 changes: 23 additions & 0 deletions apps/desktop/native/computer-history/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// swift-tools-version: 5.10

import PackageDescription

let package = Package(
name: "OpenCodexComputerHistory",
platforms: [.macOS(.v14)],
products: [
.executable(name: "open-history", targets: ["OpenHistory"]),
.library(name: "HistoryCore", targets: ["HistoryCore"]),
],
targets: [
.target(name: "HistoryCore"),
.executableTarget(
name: "OpenHistory",
dependencies: ["HistoryCore"]
),
.testTarget(
name: "HistoryCoreTests",
dependencies: ["HistoryCore"]
),
]
)
12 changes: 12 additions & 0 deletions apps/desktop/native/computer-history/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Maka Computer History helper

This directory vendors the macOS event-stream collector from
`hqhq1025/open-codex-computer-history` version 0.2.0.

The collector is a clean-room implementation based on public product behavior
and locally observable interfaces. It records Accessibility and Core Graphics
interaction events without screenshots, video, or audio.

Maka owns the Electron integration, process lifecycle, privacy defaults,
timeline projection, and user controls. The vendored collector remains under
the MIT license copied to `apps/desktop/resources/licenses/open-computer-history`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Foundation

public struct AXTreeRevisionSnapshot: Equatable, Sendable {
public let lines: [Int: String]

public init(lines: [Int: String]) {
self.lines = lines
}

public func fullText() -> String {
lines.keys.sorted().compactMap { id in
lines[id].map { "[\(id)] \($0)" }
}.joined(separator: "\n")
}

public func diff(from previous: AXTreeRevisionSnapshot) -> String {
var output: [String] = []
for id in lines.keys.sorted() {
guard let current = lines[id] else {
continue
}
if let old = previous.lines[id] {
if old != current {
output.append("~ [\(id)] \(current)")
}
} else {
output.append("+ [\(id)] \(current)")
}
}
let removed = previous.lines.keys.filter { lines[$0] == nil }.sorted()
if !removed.isEmpty {
output.append("- Removed element IDs: \(compressedRanges(removed))")
}
if output.isEmpty {
return "There has been no change in the accessibility tree."
}
return output.joined(separator: "\n")
}

private func compressedRanges(_ values: [Int]) -> String {
guard let first = values.first else {
return ""
}
var ranges: [String] = []
var start = first
var previous = first
for value in values.dropFirst() {
if value == previous + 1 {
previous = value
continue
}
ranges.append(start == previous ? "\(start)" : "\(start)-\(previous)")
start = value
previous = value
}
ranges.append(start == previous ? "\(start)" : "\(start)-\(previous)")
return ranges.joined(separator: ", ")
}
}
Loading
Loading