Skip to content
Closed
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
2 changes: 2 additions & 0 deletions .github/scripts/verify-station.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export async function verifyAgentChat({ origin, fetchImpl = fetch, log = console
['/agent/chat.css', 'text/css', '--paper'],
['/agent/chat-core.mjs', 'text/javascript', 'class Conversation'],
['/agent/chat.mjs', 'text/javascript', 'chat-core.mjs'],
['/agent/markdown.mjs', 'text/javascript', 'renderMarkdown'],
['/agent/vendor/commonmark.min.js', 'application/javascript', 'commonmark'],
['/agent/mark.svg', 'image/svg+xml', '<svg'],
];
await Promise.all(assets.map(async ([path, mime, marker]) => {
Expand Down
4 changes: 3 additions & 1 deletion .github/scripts/verify-station.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ function chatAsset(path) {
'/agent/chat.css': ['text/css', ':root { --paper: white; }'],
'/agent/chat-core.mjs': ['text/javascript', 'export class Conversation {}'],
'/agent/chat.mjs': ['text/javascript', 'import "./chat-core.mjs";'],
'/agent/markdown.mjs': ['text/javascript', 'export function renderMarkdown() {}'],
'/agent/vendor/commonmark.min.js': ['application/javascript', 'commonmark'],
'/agent/mark.svg': ['image/svg+xml', '<svg></svg>'],
};
const [mime, text] = values[path];
Expand All @@ -139,7 +141,7 @@ test('production verification checks the chat webapp and every asset on the same
paths.push(url.pathname);
return chatAsset(url.pathname);
} });
assert.deepEqual(paths.sort(), ['/agent/', '/agent/chat.css', '/agent/chat-core.mjs', '/agent/chat.mjs', '/agent/mark.svg'].sort());
assert.deepEqual(paths.sort(), ['/agent/', '/agent/chat.css', '/agent/chat-core.mjs', '/agent/chat.mjs', '/agent/markdown.mjs', '/agent/vendor/commonmark.min.js', '/agent/mark.svg'].sort());
});

test('production verification rejects an undeployed page, SPA fallback and wrong JavaScript MIME', async () => {
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ jobs:
- name: Run core tests
run: ctest --test-dir build --output-on-failure

- name: Verify pinned terminal assets
run: python3 scripts/setup-terminal-assets.py --verify

- name: Build Swift package
run: swift build -c release

Expand Down Expand Up @@ -99,7 +102,7 @@ jobs:
node webapp/tests/codec.test.mjs
node webapp/tests/nginx.test.js
node --test webapp/tests/screen-share.test.mjs webapp/tests/rtc-channel.test.mjs
node --test webapp/tests/agent-chat.test.mjs
node --test webapp/tests/agent-chat.test.mjs webapp/tests/agent-markdown.test.mjs
node --test webapp/tests/stoa-session.test.mjs

- name: Verify Stoa and Agent webapps
Expand Down
40 changes: 40 additions & 0 deletions Config/terminal-assets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"atem_commit": "396a0f3e0a68b99e6527594511706add9b046a1b",
"packages": [
{
"name": "@xterm/xterm",
"version": "6.0.0",
"url": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz",
"integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==",
"files": {
"package/lib/xterm.js": "xterm.js",
"package/css/xterm.css": "xterm.css",
"package/LICENSE": "xterm-LICENSE"
}
},
{
"name": "@xterm/addon-fit",
"version": "0.11.0",
"url": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz",
"integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==",
"files": {
"package/lib/addon-fit.js": "addon-fit.js",
"package/LICENSE": "addon-fit-LICENSE"
}
}
],
"atem_assets": {
"atem-unicode.cjs": "2892c88e7e91898fe38fa682b75284a27250efa95a6f0c02b8b073c74c82a0da",
"unicode-width-0.1.14.json": "1de5b2f92418719364e85507348c4aecdb53e34758a009ba13e696f5d2154f98"
},
"asset_sha256": {
"addon-fit-LICENSE": "e256f01188af527e4d06d21d06fbf785ae9c50d4b328bf03cbe0ba7f0aa4228f",
"addon-fit.js": "ba3ea256ce0620a0992a197d6c9baea64823fc93d8da07a9e366ca9943c18527",
"atem-unicode.cjs": "2892c88e7e91898fe38fa682b75284a27250efa95a6f0c02b8b073c74c82a0da",
"atem-unicode.js": "dece792785b991b9a08c00b8c2ec40371491b6b7ca25be5b32963af3c756be50",
"unicode-width-0.1.14.json": "1de5b2f92418719364e85507348c4aecdb53e34758a009ba13e696f5d2154f98",
"xterm-LICENSE": "b569f629d00f2626a8100df2a1798210535621e42164dfd426a6fe5aac7b0ccd",
"xterm.css": "854a7c0fb70e8b1a083c16797ab827299fb18744f5ad34f227b48337e33293c6",
"xterm.js": "14903579ff54664cd72f8e8699e6961a6272c21863ec1c3b118cdc8af5d4a972"
}
}
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ let package = Package(
],
path: "Sources/Menubar",
resources: [.copy("Resources/transcription-model.json"), .copy("Resources/whisper-large-v3-turbo-coreml-v1.json"),
.copy("Resources/whisper-large-v3-coreml-v1.json")],
.copy("Resources/whisper-large-v3-coreml-v1.json"), .copy("Resources/RemoteTerminal")],
linkerSettings: [
.unsafeFlags(["-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist",
"-Xlinker", executableInfoPlist]),
Expand Down
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,30 @@ Messages carry only IDs, status, and descriptions -- no images or file lists flo
### Remote Agent Control

Open **Connections > Clients & Agents**, select a connected Atem and an agent,
and open its remote-control window to send text or control keys. Input goes to
the selected client and agent; the agent's output remains in its Atem terminal.
The `agentInput` payload contains `agentId`, `kind` (`text` or `key`), and the
corresponding `text` or `key` field. Relay routing identifiers belong to the
transport envelope. Voice continues through the existing voice-coding flow.
then click **Open Terminal**. The **Terminals** button also opens the session
chooser without requiring an active agent row. The resizable window shows the
original Claude/Codex terminal, including live output, colors and prompts.
It starts in viewing mode; **Take Control** enables typing, control keys,
multiline paste and resizing. **Release** returns local ownership. Closing the
window keeps the agent running; **Stop Agent** asks before ending its process.

This development branch requires the Atem session-v1 r6.2 host protocol (Atem PR #69),
in addition to terminal rendering. Launch the original CLI with
`atem agent launch claude` or `atem agent launch codex`, pair and verify once,
then select its exact session. A shared lease governs typing, resize, voice,
prompt submission, interrupt and stop. Reconnect returns to viewing mode,
queries durable receipts and never resends uncertain input. See the
[integration handoff](designs/protocol/atem-implementation-handoff.md) for host
requirements and current validation limits.

See [remote terminal control](docs/remote-terminal.md) for setup, ownership,
clipboard, limitations and validation.

**Open in Browser** is disabled on this development branch. Browser companions
require the section 9 security contract and a common host lease; there is no
unbound chat fallback. The `/agent/` webapp and its offline conformance tests
remain available for that follow-up. Use **Open Terminal** for current host
access. See the [joint integration handoff](designs/protocol/atem-implementation-handoff.md).

### Local Audio Recording

Expand Down Expand Up @@ -413,7 +432,7 @@ relay-server/
## Configuration

Settings > Credentials manages named API keys and passwords in this Mac's
protected Keychain, with Global or Git-project scopes and Touch ID for saves,
protected Keychain, with Global or Git-project scopes and Touch ID or Mac-password authentication for saves,
updates, copies and deletion. The project picker combines encrypted suggestions
from verified, unlocked Atem devices with saved scopes; another Git remote can
always be entered manually. Original capitalization is retained, while identity
Expand All @@ -422,8 +441,9 @@ retrieval is not implemented. See the
[credentials guide](docs/credentials.md) for setup and current availability.

Atem can run on another machine. Pair with `atem pair`, compare the safety code
on both machines, then click **Codes Match** and approve Touch ID on the Mac.
Check `atem cred status` for verification; a connected or SSO-paired session
on both machines, then click **Codes Match** and approve Touch ID on the Mac,
or choose **Use Mac Password...** to authenticate in macOS's system dialog.
Check `atem status` for verification; a connected or SSO-paired session
alone does not establish device trust. `atem cred unlock` requests key access
from this Mac, and `atem cred projects` sends Git-project suggestions. Use a
provisioned Astation build for protected storage; the ad hoc development app
Expand Down
3 changes: 3 additions & 0 deletions Sources/Menubar/AstationApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ class AstationApp: NSObject, NSApplicationDelegate {
hubManager.sendHandler = { [weak webSocketServer] message, clientId in
webSocketServer?.sendMessageToClient(message, clientId: clientId)
}
hubManager.sessionsRemovedHandler = { [weak webSocketServer] sessionIds in
webSocketServer?.disconnectClients(authenticatedWith: sessionIds)
}

// Start network monitoring to detect IP changes
NetworkMonitor.shared.startMonitoring()
Expand Down
Loading
Loading