Skip to content
Merged
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ T4 Code needs an OMP build with desktop appserver support. For v0.1.23, use the

T4 Code v0.1.23 was verified with OMP 17.0.4 built from [`d57dcd85`](https://github.com/lyc-aon/oh-my-pi/commit/d57dcd855006c673d8d530237d474fe5ba5645c4), tagged [`t4code-17.0.4-appserver-5`](https://github.com/lyc-aon/oh-my-pi/tree/t4code-17.0.4-appserver-5). That public integration is based on the official upstream [`v17.0.4`](https://github.com/can1357/oh-my-pi/tree/v17.0.4) tag at [`3fdd85ab`](https://github.com/can1357/oh-my-pi/commit/3fdd85ab6c6bab6c0cdee80abbbec0981740a5c0). It adds redacted Codex transport diagnostics, the versioned Agent View lifecycle contract, session-owned cancellation, macOS system-temp aliases, workspace-native build artifacts, retry-safe release metadata, lock-aware session observation, complete transcript reconciliation, missing-lock-only promotion, the cooperative `/continue-in-t4` handoff, and deterministic session ordering. Fork CI verifies the exact upstream base, ancestry, release gates, and published binaries. The official upstream v17.0.4 tag has no `appserver` command, so it cannot host T4 Code. The verified runtime is a normal build from the public `lyc-aon/oh-my-pi` source. T4 Code vendors `@oh-my-pi/app-wire` 0.5.10 from integration commit [`d57dcd85`](https://github.com/lyc-aon/oh-my-pi/commit/d57dcd855006c673d8d530237d474fe5ba5645c4), source tree `5cf488966e3c233764780d3ca7a8d8ea1e3a1f68`.

The current source tree advances the vendored contract to `@oh-my-pi/app-wire` 0.5.10 from integration commit [`93f48ab6`](https://github.com/lyc-aon/oh-my-pi/commit/93f48ab62e2002b48a0dc2734de33d5328ea76d6), source tree `ea8608496731f29addc95d43ea68e44c5c42cb22`. The published v0.1.23 package remains pinned to the `d57dcd85` contract above.
T4 Code vendors `@oh-my-pi/app-wire` 0.6.0 from integration commit [`ae4b53b4`](https://github.com/lyc-aon/oh-my-pi/commit/ae4b53b416f32b200865a32ed9baabd5a4666fa4), source tree `2b8a5f697273f5044789b8ae638b6c264f9f8499`. The published v0.1.23 package remains pinned to the `d57dcd85` contract above.

| Platform | Arch | Package |
| -------- | --------------------- | ---------------------------------------- |
Expand Down Expand Up @@ -89,6 +89,7 @@ chmod +x T4-Code-0.1.23-linux-x86_64.AppImage
- **Sessions.** Browse sessions grouped by their working folder, create new ones, and switch between them. Rename, terminate a stuck runtime, archive, restore, or permanently delete a session from its menu. Recently used sessions stay warm, so switching back is instant and nothing is replayed twice.
- **Composer.** Send prompts, use slash commands (`/model`, `/compact`, `/retry`, `/review`, `/terminal`, and more), and change the session's model, thinking level, or fast mode inline.
- **Panes.** Watch subagents (and cancel them), apply reviews, browse and preview files on the host, and attach to live terminals with real keyboard input and resize.
- **Browser preview.** Open session-linked browser previews to inspect page layouts, follow live navigations, and interact with the page via coordinate-mapped clicks and keyboard input. Previews use pluggable authority gates, lease-based concurrency locks, and strict opt-in security boundaries.
- **Settings.** Edit host settings over the wire, with an explicit host selector when several hosts are connected; each host keeps its own drafts. Edits stage locally and only apply when the host confirms; a dropped connection never silently writes anything.
- **Hosts & usage.** Run one local appserver per OMP profile, pair remote machines, and read each connected host's account usage and broker status. Everything shown is redacted host truth.
- **Keyboard.** `Ctrl/Cmd+K` search, `Ctrl/Cmd+B` sidebar, `Ctrl/Cmd+1..9` session switch, `Ctrl/Cmd+,` settings. Every workflow is keyboard-operable.
Expand Down
2 changes: 1 addition & 1 deletion THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ T3 Code is selectively referenced for future ports from https://github.com/pingd

## Oh My Pi

Future adaptations of OMP source use the OMP repository under its repository license. OMP remains runtime authority; adapted files retain OMP attribution and the applicable source license. The vendored `@oh-my-pi/app-wire@0.5.10` package is packed from the public `lyc-aon/oh-my-pi` integration commit `93f48ab62e2002b48a0dc2734de33d5328ea76d6`, source tree `ea8608496731f29addc95d43ea68e44c5c42cb22`; tarball SHA-256 `d30da820ff2bb8a7efa024fc829b654a2dfaf2600688fa369abc64b053ae8ede`; golden corpus SHA-256 `63480a2359c1b2b4ec2f5cc8890683f0eefc13e92597d1464e442b563bc7375e`. Target integration commit is recorded in the Desktop commit history and compatibility matrix.
Future adaptations of OMP source use the OMP repository under its repository license. OMP remains runtime authority; adapted files retain OMP attribution and the applicable source license. The vendored `@oh-my-pi/app-wire@0.6.0` package is packed from the public `lyc-aon/oh-my-pi` integration commit `ae4b53b416f32b200865a32ed9baabd5a4666fa4`, source tree `2b8a5f697273f5044789b8ae638b6c264f9f8499`; tarball SHA-256 `92256497bb8086ab9cefa30e4890293060a52b9d0c5349743ee94ae3224ca32c`; golden corpus SHA-256 `7ebd5fa6cbc37ae0f28cf1d957d9cab841b875581cb42ffbe81cea66f1dc2ef1`. Target integration commit is recorded in the Desktop commit history and compatibility matrix.

## Oh My Pi icon

Expand Down
2 changes: 1 addition & 1 deletion apps/site/src/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const OMP_RUNTIME_URL = `https://github.com/lyc-aon/oh-my-pi/tree/${OMP_R
export const OMP_UPSTREAM_TAG = "v17.0.4";
export const OMP_UPSTREAM_COMMIT = "3fdd85ab6c6bab6c0cdee80abbbec0981740a5c0";
export const OMP_UPSTREAM_URL = `${OMP_URL}/tree/${OMP_UPSTREAM_TAG}`;
export const APP_WIRE_VERSION = "0.5.10";
export const APP_WIRE_VERSION = "0.6.0";
export const RELEASE_TAG = "v0.1.23";
export const RELEASE_VERSION = "0.1.23";
export const RELEASES_URL = `${REPO_URL}/releases/tag/${RELEASE_TAG}`;
Expand Down
2 changes: 1 addition & 1 deletion apps/site/test/release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe("OMP integration contract", () => {
expect(OMP_UPSTREAM_TAG).toBe("v17.0.4");
expect(OMP_UPSTREAM_COMMIT).toBe("3fdd85ab6c6bab6c0cdee80abbbec0981740a5c0");
expect(OMP_UPSTREAM_URL).toBe("https://github.com/can1357/oh-my-pi/tree/v17.0.4");
expect(APP_WIRE_VERSION).toBe("0.5.10");
expect(APP_WIRE_VERSION).toBe("0.6.0");
});
});

Expand Down
23 changes: 22 additions & 1 deletion apps/web/src/components/SessionScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
useReducedMotion,
} from "@t4-code/ui";
import { Popover } from "@base-ui/react/popover";
import { Link } from "@tanstack/react-router";
import { Check, PanelBottomClose, PanelBottomOpen, PanelRight, X } from "lucide-react";
import { useEffect, useState } from "react";

Expand All @@ -25,6 +26,8 @@ import { TerminalDrawer } from "../features/terminal/TerminalDrawer.tsx";
import { FreshnessBadge, SessionMain, SessionOwnershipBadge } from "../features/transcript/SessionMain.tsx";
import { RIGHT_PANE_DOCK_QUERY, useMediaQuery } from "../hooks/useMediaQuery.ts";
import { useWorkspace, workspaceStore } from "../state/store-instance.ts";
import { useDesktopRuntimeSnapshot } from "../platform/desktop-runtime.ts";
import { resolveLiveSession } from "../platform/live-workspace.ts";
import {
type PaneFamily,
RIGHT_PANE_WIDTH,
Expand Down Expand Up @@ -159,6 +162,15 @@ export function SessionScreen({
(state) => selectSessionView(state, session.id).terminalDrawerOpen,
);
const paneDocks = useMediaQuery(RIGHT_PANE_DOCK_QUERY);
const runtimeSnapshot = useDesktopRuntimeSnapshot();
const previewAddress =
runtimeSnapshot === null ? null : resolveLiveSession(runtimeSnapshot, session.id);
const previewCount =
previewAddress === null
? 0
: (runtimeSnapshot?.projection.sessions
.get(`${previewAddress.hostId}\u0000${previewAddress.sessionId}`)
?.previews.size ?? 0);
const [panePreviewWidth, setPanePreviewWidth] = useState<number | null>(null);

// Transcript scroll ownership lives in TranscriptTimeline (virtualized
Expand Down Expand Up @@ -208,7 +220,16 @@ export function SessionScreen({
<FreshnessBadge session={session} />
<SessionOwnershipBadge session={session} />
</span>
<span className="min-w-0 flex-1" />
{previewCount > 0 && (
<Link
aria-label={`Open browser preview${previewCount === 1 ? "" : ` (${previewCount})`}`}
className="shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-ring"
params={{ sessionId: session.id }}
to="/sessions/$sessionId/preview"
>
<Badge variant="outline">Preview{previewCount === 1 ? "" : ` · ${previewCount}`}</Badge>
</Link>
)}
{!archived && (
<FamilyToggles paneFamily={viewPaneFamily} paneOpen={viewPaneOpen} sessionId={session.id} />
)}
Expand Down
66 changes: 66 additions & 0 deletions apps/web/src/features/panes/activity-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// filter/search them, redact secrets from the raw inspector, and export.
// Pure functions — the store applies them, tests drive them directly.
import { isSessionEvent, type SessionEvent } from "@t4-code/protocol";
import type { PreviewEventProjection, PreviewFreshness } from "@t4-code/client";

import { sessionEventSpec } from "../session-runtime/session-event-vocabulary.ts";
import type { ActivityEntry, ActivityFilter, ActivityKind } from "./model.ts";
Expand Down Expand Up @@ -83,6 +84,71 @@ export function classifySessionEvent(
};
}

function previewEventAt(event: PreviewEventProjection, fallbackAt: string): string {
if (
event.timestamp === undefined ||
!Number.isFinite(event.timestamp) ||
event.timestamp < 0 ||
event.timestamp > 8.64e15
) {
return fallbackAt;
}
return new Date(event.timestamp).toISOString();
}


/**
* Browser-preview activity is projection metadata, not a browser audit log.
* Keep only the already-sanitized origin/path, capture identity, timestamp,
* and a generic error outcome. Query/hash values, pixels, credentials, and
* backend error text never enter Activity or its export payload.
*/
export function classifyPreviewEvent(
event: PreviewEventProjection,
seq: number,
fallbackAt: string,
freshness?: PreviewFreshness,
): ActivityEntry {
const url = event.url === undefined ? null : `${event.url.origin}${event.url.pathname}`;
const context = freshness === "cached" || freshness === "stale" ? freshness : null;
const detailParts: string[] = [];
if (url !== null) detailParts.push(url);
if (event.type === "capture" && event.captureId !== undefined) {
detailParts.push(`Capture ${event.captureId}`);
}
if (context !== null) detailParts.push(context);
const title =
event.type === "launch"
? "Browser preview launched"
: event.type === "navigation"
? "Browser preview navigated"
: event.type === "capture"
? "Browser preview captured"
: "Browser preview failed";
return {
seq,
at: previewEventAt(event, fallbackAt),
kind: event.type === "error" ? "error" : "system",
title,
detail: event.type === "error" ? "The browser preview request did not complete." : (detailParts.join(" · ") || null),
agentId: null,
terminalId: null,
raw: {
type: `preview.${event.type}`,
previewId: event.previewId,
cursor: event.cursor,
...(url === null ? {} : { url }),
...(event.type === "capture" && event.captureId !== undefined
? { captureId: event.captureId }
: {}),
...(event.timestamp === undefined ? {} : { timestamp: event.timestamp }),
...(context === null ? {} : { freshness: context }),
},
unknown: false,
shellOutput: null,
};
}

/** Append with the retention cap; order is seq order, oldest dropped first. */
export function appendActivity(
entries: readonly ActivityEntry[],
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/features/panes/live-inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@ function emptyProjection(): SessionProjection {
audit: [],
confirmations: new Map(),
results: new Map(),
previews: new Map(),
previewEvents: [],
freshness: "cached",
transcriptEventArrivalOrdinal: 0,
contextMaintenanceEventArrivalOrdinal: 0,
Expand Down
22 changes: 20 additions & 2 deletions apps/web/src/features/panes/live-projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
// desktop runtime already validated and bounded) to the inspector pane
// view models. Every function here is derivation only: unknown fields stay
// null, unsafe paths disappear, and nothing is invented to fill a gap.
import type { AgentTranscriptProjection, ResultProjection, SessionProjection } from "@t4-code/client";
import type {
AgentTranscriptProjection,
PreviewFreshness,
ResultProjection,
SessionProjection,
} from "@t4-code/client";

import { classifySessionEvent } from "./activity-log.ts";
import { classifyPreviewEvent, classifySessionEvent } from "./activity-log.ts";
import { displayStateFromWire } from "./model.ts";
import type {
ActivityEntry,
Expand Down Expand Up @@ -205,6 +210,19 @@ export function collectActivity(session: SessionProjection): KeyedActivityEntry[
entry: classifySessionEvent(frame.event, 0, ""),
});
}
for (const event of session.previewEvents) {
let freshness: PreviewFreshness | undefined;
for (const preview of session.previews.values()) {
if (preview.previewId === event.previewId) {
freshness = preview.freshness;
break;
}
}
entries.push({
key: `preview:${event.cursor.epoch}:${event.cursor.seq}`,
entry: classifyPreviewEvent(event, 0, "", freshness),
});
}
for (const frame of session.audit) {
entries.push({
key: `audit:${frame.timestamp}\u0000${frame.action}\u0000${frame.actor}`,
Expand Down
Loading
Loading