@wangyaoshen/remux 0.3.8-dev.a8ceb0c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/.github/dependabot.yml +33 -0
- package/.github/workflows/ci.yml +65 -0
- package/.github/workflows/deploy.yml +65 -0
- package/.github/workflows/publish.yml +312 -0
- package/.github/workflows/release-please.yml +21 -0
- package/.gitmodules +3 -0
- package/.nvmrc +1 -0
- package/.release-please-manifest.json +3 -0
- package/CLAUDE.md +104 -0
- package/Dockerfile +23 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/apps/ios/Config/signing.xcconfig +4 -0
- package/apps/ios/Package.swift +26 -0
- package/apps/ios/Remux.xcodeproj/project.pbxproj +477 -0
- package/apps/ios/Remux.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/Contents.json +23 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_120x120.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_152x152.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_167x167.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_180x180.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_20x20.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_29x29.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_40x40.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_58x58.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_60x60.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_76x76.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_80x80.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_87x87.png +0 -0
- package/apps/ios/Sources/Remux/Assets.xcassets/Contents.json +6 -0
- package/apps/ios/Sources/Remux/Extensions/FaceIDManager.swift +29 -0
- package/apps/ios/Sources/Remux/Extensions/InspectCache.swift +66 -0
- package/apps/ios/Sources/Remux/MainTabView.swift +32 -0
- package/apps/ios/Sources/Remux/Remux.entitlements +8 -0
- package/apps/ios/Sources/Remux/RemuxiOSApp.swift +14 -0
- package/apps/ios/Sources/Remux/RootView.swift +130 -0
- package/apps/ios/Sources/Remux/Views/Control/ControlView.swift +102 -0
- package/apps/ios/Sources/Remux/Views/Inspect/InspectView.swift +98 -0
- package/apps/ios/Sources/Remux/Views/Live/LiveTerminalView.swift +132 -0
- package/apps/ios/Sources/Remux/Views/Now/NowView.swift +173 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/ManualConnectView.swift +55 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/OnboardingView.swift +70 -0
- package/apps/ios/Sources/Remux/Views/Onboarding/QRScannerView.swift +92 -0
- package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +136 -0
- package/apps/macos/Package.swift +37 -0
- package/apps/macos/Resources/shell-integration/bash/bash-preexec.sh +382 -0
- package/apps/macos/Resources/shell-integration/bash/ghostty.bash +315 -0
- package/apps/macos/Resources/shell-integration/elvish/lib/ghostty-integration.elv +191 -0
- package/apps/macos/Resources/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +246 -0
- package/apps/macos/Resources/shell-integration/nushell/vendor/autoload/ghostty.nu +110 -0
- package/apps/macos/Resources/shell-integration/zsh/.zshenv +61 -0
- package/apps/macos/Resources/shell-integration/zsh/ghostty-integration +458 -0
- package/apps/macos/Resources/terminfo/67/ghostty +0 -0
- package/apps/macos/Resources/terminfo/78/xterm-ghostty +0 -0
- package/apps/macos/Sources/Remux/AppDelegate.swift +257 -0
- package/apps/macos/Sources/Remux/CrashReporter.swift +210 -0
- package/apps/macos/Sources/Remux/FinderIntegration.swift +117 -0
- package/apps/macos/Sources/Remux/GhosttyConfig.swift +311 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutAction.swift +115 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutSettingsView.swift +271 -0
- package/apps/macos/Sources/Remux/KeyboardShortcuts/StoredShortcut.swift +149 -0
- package/apps/macos/Sources/Remux/MainContentView.swift +308 -0
- package/apps/macos/Sources/Remux/MenuBarManager.swift +275 -0
- package/apps/macos/Sources/Remux/NotificationManager.swift +145 -0
- package/apps/macos/Sources/Remux/PortScanner.swift +152 -0
- package/apps/macos/Sources/Remux/RemuxApp.swift +13 -0
- package/apps/macos/Sources/Remux/SSHDetector.swift +151 -0
- package/apps/macos/Sources/Remux/SessionPersistence.swift +226 -0
- package/apps/macos/Sources/Remux/SocketController.swift +258 -0
- package/apps/macos/Sources/Remux/UpdateChecker.swift +152 -0
- package/apps/macos/Sources/Remux/Views/CommandPalette.swift +198 -0
- package/apps/macos/Sources/Remux/Views/ConnectionView.swift +84 -0
- package/apps/macos/Sources/Remux/Views/InspectView.swift +127 -0
- package/apps/macos/Sources/Remux/Views/SettingsView.swift +77 -0
- package/apps/macos/Sources/Remux/Views/Sidebar/SidebarView.swift +410 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/BrowserPanel.swift +193 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/MarkdownPanel.swift +277 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/PanelProtocol.swift +14 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitNode.swift +149 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +234 -0
- package/apps/macos/Sources/Remux/Views/SplitTree/TerminalPanel.swift +26 -0
- package/apps/macos/Sources/Remux/Views/TabBarView.swift +94 -0
- package/apps/macos/Sources/Remux/Views/Terminal/ClipboardHelper.swift +101 -0
- package/apps/macos/Sources/Remux/Views/Terminal/CopyModeOverlay.swift +325 -0
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeTerminalView.swift +39 -0
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +559 -0
- package/apps/macos/Sources/Remux/Views/Terminal/SurfaceSearchOverlay.swift +109 -0
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +95 -0
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalRelay.swift +117 -0
- package/build.mjs +33 -0
- package/native/android/DecodeGoldenPayloads.kt +487 -0
- package/native/android/ProtocolModels.kt +188 -0
- package/native/ios/DecodeGoldenPayloads.swift +711 -0
- package/native/ios/ProtocolModels.swift +200 -0
- package/package.json +45 -0
- package/packages/RemuxKit/Package.swift +27 -0
- package/packages/RemuxKit/Sources/RemuxKit/Device/DeviceManager.swift +27 -0
- package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +206 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +108 -0
- package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +395 -0
- package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +188 -0
- package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +142 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyBridge.swift +145 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyTerminalView.swift +35 -0
- package/packages/RemuxKit/Sources/RemuxKit/Terminal/Resources/ghostty-terminal.html +91 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +74 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +81 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +179 -0
- package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +62 -0
- package/playwright.config.ts +17 -0
- package/pnpm-lock.yaml +1588 -0
- package/pty-daemon.js +303 -0
- package/release-please-config.json +14 -0
- package/scripts/auto-deploy.sh +46 -0
- package/scripts/build-dmg.sh +121 -0
- package/scripts/build-ghostty-kit.sh +43 -0
- package/scripts/check-active-terminology.mjs +132 -0
- package/scripts/setup-ci-secrets.sh +80 -0
- package/scripts/sync-ghostty-web.sh +28 -0
- package/scripts/upload-testflight.sh +100 -0
- package/server.js +7074 -0
- package/src/adapters/agent-events.ts +246 -0
- package/src/adapters/claude-code.ts +158 -0
- package/src/adapters/codex.ts +210 -0
- package/src/adapters/generic-shell.ts +58 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +99 -0
- package/src/adapters/types.ts +41 -0
- package/src/auth.ts +174 -0
- package/src/e2ee.ts +236 -0
- package/src/git-service.ts +168 -0
- package/src/message-buffer.ts +137 -0
- package/src/pty-daemon.ts +357 -0
- package/src/push.ts +127 -0
- package/src/renderers.ts +455 -0
- package/src/server.ts +2407 -0
- package/src/service.ts +226 -0
- package/src/session.ts +978 -0
- package/src/store.ts +1422 -0
- package/src/team.ts +123 -0
- package/src/tunnel.ts +126 -0
- package/src/types.d.ts +50 -0
- package/src/vt-tracker.ts +188 -0
- package/src/workspace-head.ts +144 -0
- package/src/workspace.ts +153 -0
- package/src/ws-handler.ts +1526 -0
- package/start.ps1 +83 -0
- package/tests/adapters.test.js +171 -0
- package/tests/auth.test.js +243 -0
- package/tests/codex-adapter.test.js +535 -0
- package/tests/durable-stream.test.js +153 -0
- package/tests/e2e/app.spec.js +530 -0
- package/tests/e2ee.test.js +325 -0
- package/tests/message-buffer.test.js +245 -0
- package/tests/message-routing.test.js +305 -0
- package/tests/pty-daemon.test.js +346 -0
- package/tests/push.test.js +281 -0
- package/tests/renderers.test.js +391 -0
- package/tests/search-shell.test.js +499 -0
- package/tests/server.test.js +882 -0
- package/tests/service.test.js +267 -0
- package/tests/store.test.js +369 -0
- package/tests/tunnel.test.js +67 -0
- package/tests/workspace-head.test.js +116 -0
- package/tests/workspace.test.js +417 -0
- package/tsconfig.backend.json +11 -0
- package/tsconfig.json +15 -0
- package/tui/client/client_test.go +125 -0
- package/tui/client/connection.go +342 -0
- package/tui/client/host_manager.go +141 -0
- package/tui/config/cache.go +81 -0
- package/tui/config/config.go +53 -0
- package/tui/config/config_test.go +89 -0
- package/tui/go.mod +32 -0
- package/tui/go.sum +50 -0
- package/tui/main.go +261 -0
- package/tui/tests/integration_test.go +283 -0
- package/tui/ui/model.go +310 -0
- package/vitest.config.js +10 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// E10-009: Unified agent event types and parsers
|
|
2
|
+
// Normalizes events from different AI agents (Claude Code, Codex, etc.)
|
|
3
|
+
// into a common format for structured rendering on clients.
|
|
4
|
+
|
|
5
|
+
export interface AgentToolCall {
|
|
6
|
+
tool: string; // "file_read", "file_write", "bash", "search", etc.
|
|
7
|
+
args: Record<string, unknown>;
|
|
8
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
9
|
+
output?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AgentApproval {
|
|
13
|
+
id: string;
|
|
14
|
+
tool: string;
|
|
15
|
+
description: string;
|
|
16
|
+
status: "pending" | "approved" | "rejected";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AgentTurn {
|
|
20
|
+
role: "user" | "assistant";
|
|
21
|
+
content: string;
|
|
22
|
+
toolCalls?: AgentToolCall[];
|
|
23
|
+
approvals?: AgentApproval[];
|
|
24
|
+
timestamp: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AgentSessionSummary {
|
|
28
|
+
agentId: string; // "claude-code" | "codex" | "generic-shell"
|
|
29
|
+
agentName: string;
|
|
30
|
+
state: "idle" | "running" | "waiting_approval" | "error";
|
|
31
|
+
currentTurn?: AgentTurn;
|
|
32
|
+
recentToolCalls: AgentToolCall[];
|
|
33
|
+
pendingApprovals: AgentApproval[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Claude Code event parser ──────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse a Claude Code events.jsonl / conversation.jsonl entry
|
|
40
|
+
* into the unified AgentTurn format.
|
|
41
|
+
*
|
|
42
|
+
* Claude Code event shapes observed:
|
|
43
|
+
* { type: "assistant", message: { content: [...] } }
|
|
44
|
+
* { type: "tool_use", name: "Bash", input: { command: "..." } }
|
|
45
|
+
* { type: "tool_result", tool_use_id: "...", content: "..." }
|
|
46
|
+
* { type: "result", result: "..." }
|
|
47
|
+
* { type: "permission_request", tool: "...", description: "..." }
|
|
48
|
+
* { type: "end_turn" }
|
|
49
|
+
* { type: "error", error: "..." }
|
|
50
|
+
*/
|
|
51
|
+
export function parseClaudeCodeEvent(
|
|
52
|
+
event: Record<string, unknown>,
|
|
53
|
+
): Partial<AgentTurn> | null {
|
|
54
|
+
const type = event.type as string | undefined;
|
|
55
|
+
if (!type) return null;
|
|
56
|
+
|
|
57
|
+
switch (type) {
|
|
58
|
+
case "assistant": {
|
|
59
|
+
const message = event.message as Record<string, unknown> | undefined;
|
|
60
|
+
const contentBlocks = (message?.content ?? []) as Array<Record<string, unknown>>;
|
|
61
|
+
const textParts = contentBlocks
|
|
62
|
+
.filter((b) => b.type === "text")
|
|
63
|
+
.map((b) => b.text as string);
|
|
64
|
+
return {
|
|
65
|
+
role: "assistant",
|
|
66
|
+
content: textParts.join("\n") || "",
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case "tool_use": {
|
|
72
|
+
const toolCall: AgentToolCall = {
|
|
73
|
+
tool: (event.name as string) ?? "unknown",
|
|
74
|
+
args: (event.input as Record<string, unknown>) ?? {},
|
|
75
|
+
status: "running",
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
role: "assistant",
|
|
79
|
+
content: "",
|
|
80
|
+
toolCalls: [toolCall],
|
|
81
|
+
timestamp: new Date().toISOString(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "tool_result": {
|
|
86
|
+
const toolCall: AgentToolCall = {
|
|
87
|
+
tool: (event.name as string) ?? "unknown",
|
|
88
|
+
args: {},
|
|
89
|
+
status: "completed",
|
|
90
|
+
output:
|
|
91
|
+
typeof event.content === "string"
|
|
92
|
+
? event.content
|
|
93
|
+
: JSON.stringify(event.content ?? ""),
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
role: "assistant",
|
|
97
|
+
content: "",
|
|
98
|
+
toolCalls: [toolCall],
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "permission_request": {
|
|
104
|
+
const approval: AgentApproval = {
|
|
105
|
+
id: (event.id as string) ?? `perm-${Date.now()}`,
|
|
106
|
+
tool: (event.tool as string) ?? "unknown",
|
|
107
|
+
description: (event.description as string) ?? "",
|
|
108
|
+
status: "pending",
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
role: "assistant",
|
|
112
|
+
content: "",
|
|
113
|
+
approvals: [approval],
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case "result":
|
|
119
|
+
case "end_turn":
|
|
120
|
+
return {
|
|
121
|
+
role: "assistant",
|
|
122
|
+
content: typeof event.result === "string" ? event.result : "",
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
case "error":
|
|
127
|
+
return {
|
|
128
|
+
role: "assistant",
|
|
129
|
+
content: `Error: ${(event.error as string) ?? "unknown error"}`,
|
|
130
|
+
timestamp: new Date().toISOString(),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
default:
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Codex event parser ────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse a Codex CLI JSONL event into the unified AgentTurn format.
|
|
142
|
+
*
|
|
143
|
+
* Codex uses a JSON-RPC style with items/turns/threads:
|
|
144
|
+
* { type: "item.created", item: { type: "message", role: "assistant", content: [...] } }
|
|
145
|
+
* { type: "turn.started" }
|
|
146
|
+
* { type: "turn.completed" }
|
|
147
|
+
* { type: "tool_use", name: "shell", input: { command: "..." } }
|
|
148
|
+
* { type: "tool_result", output: "..." }
|
|
149
|
+
* { type: "permission_request", command: "...", description: "..." }
|
|
150
|
+
* { type: "error", message: "..." }
|
|
151
|
+
*/
|
|
152
|
+
export function parseCodexEvent(
|
|
153
|
+
event: Record<string, unknown>,
|
|
154
|
+
): Partial<AgentTurn> | null {
|
|
155
|
+
const type = event.type as string | undefined;
|
|
156
|
+
if (!type) return null;
|
|
157
|
+
|
|
158
|
+
switch (type) {
|
|
159
|
+
case "item.created": {
|
|
160
|
+
const item = event.item as Record<string, unknown> | undefined;
|
|
161
|
+
if (!item) return null;
|
|
162
|
+
const role =
|
|
163
|
+
(item.role as string) === "user" ? "user" : "assistant";
|
|
164
|
+
const contentBlocks = (item.content ?? []) as Array<Record<string, unknown>>;
|
|
165
|
+
const textParts = contentBlocks
|
|
166
|
+
.filter((b) => b.type === "text" || b.type === "output_text")
|
|
167
|
+
.map((b) => (b.text ?? b.output ?? "") as string);
|
|
168
|
+
return {
|
|
169
|
+
role: role as "user" | "assistant",
|
|
170
|
+
content: textParts.join("\n") || "",
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
case "tool_use": {
|
|
176
|
+
const toolCall: AgentToolCall = {
|
|
177
|
+
tool: (event.name as string) ?? "unknown",
|
|
178
|
+
args: (event.input as Record<string, unknown>) ?? {},
|
|
179
|
+
status: "running",
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
role: "assistant",
|
|
183
|
+
content: "",
|
|
184
|
+
toolCalls: [toolCall],
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case "tool_result": {
|
|
190
|
+
const toolCall: AgentToolCall = {
|
|
191
|
+
tool: (event.name as string) ?? "unknown",
|
|
192
|
+
args: {},
|
|
193
|
+
status: "completed",
|
|
194
|
+
output:
|
|
195
|
+
typeof event.output === "string"
|
|
196
|
+
? event.output
|
|
197
|
+
: JSON.stringify(event.output ?? ""),
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
role: "assistant",
|
|
201
|
+
content: "",
|
|
202
|
+
toolCalls: [toolCall],
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case "permission_request": {
|
|
208
|
+
const approval: AgentApproval = {
|
|
209
|
+
id: (event.id as string) ?? `perm-${Date.now()}`,
|
|
210
|
+
tool: (event.command as string) ?? "unknown",
|
|
211
|
+
description: (event.description as string) ?? "",
|
|
212
|
+
status: "pending",
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
role: "assistant",
|
|
216
|
+
content: "",
|
|
217
|
+
approvals: [approval],
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
case "turn.started":
|
|
223
|
+
return {
|
|
224
|
+
role: "assistant",
|
|
225
|
+
content: "",
|
|
226
|
+
timestamp: new Date().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
case "turn.completed":
|
|
230
|
+
return {
|
|
231
|
+
role: "assistant",
|
|
232
|
+
content: "",
|
|
233
|
+
timestamp: new Date().toISOString(),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
case "error":
|
|
237
|
+
return {
|
|
238
|
+
role: "assistant",
|
|
239
|
+
content: `Error: ${(event.message as string) ?? "unknown error"}`,
|
|
240
|
+
timestamp: new Date().toISOString(),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
default:
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// E10-005: claude-code adapter — passive mode, monitors Claude Code events.jsonl
|
|
2
|
+
// Reports run status: idle/running/waiting_approval/error
|
|
3
|
+
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as os from "os";
|
|
7
|
+
import { SemanticAdapter, AdapterState, SemanticEvent } from "./types.js";
|
|
8
|
+
|
|
9
|
+
export class ClaudeCodeAdapter implements SemanticAdapter {
|
|
10
|
+
id = "claude-code";
|
|
11
|
+
name = "Claude Code";
|
|
12
|
+
mode = "passive" as const;
|
|
13
|
+
capabilities = ["run-status", "conversation-events", "tool-use"];
|
|
14
|
+
|
|
15
|
+
private state: AdapterState = {
|
|
16
|
+
adapterId: "claude-code",
|
|
17
|
+
name: "Claude Code",
|
|
18
|
+
mode: "passive",
|
|
19
|
+
capabilities: this.capabilities,
|
|
20
|
+
currentState: "idle",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
private watcher: fs.FSWatcher | null = null;
|
|
24
|
+
private fileSizes = new Map<string, number>();
|
|
25
|
+
private eventsDir: string;
|
|
26
|
+
private onEmit?: (event: SemanticEvent) => void;
|
|
27
|
+
|
|
28
|
+
constructor(onEmit?: (event: SemanticEvent) => void) {
|
|
29
|
+
this.onEmit = onEmit;
|
|
30
|
+
// Claude Code stores events in ~/.claude/ or project-specific paths
|
|
31
|
+
this.eventsDir = path.join(os.homedir(), ".claude", "projects");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
start(): void {
|
|
35
|
+
this.watchForEvents();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
stop(): void {
|
|
39
|
+
this.watcher?.close();
|
|
40
|
+
this.watcher = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onTerminalData(sessionName: string, data: string): void {
|
|
44
|
+
// Detect Claude Code activity from terminal output patterns
|
|
45
|
+
if (data.includes("claude") || data.includes("Claude")) {
|
|
46
|
+
// Check for common Claude Code output patterns
|
|
47
|
+
if (data.includes("Thinking...") || data.includes("⏳")) {
|
|
48
|
+
this.updateState("running");
|
|
49
|
+
} else if (
|
|
50
|
+
data.includes("Done") ||
|
|
51
|
+
data.includes("✓") ||
|
|
52
|
+
data.includes("Complete")
|
|
53
|
+
) {
|
|
54
|
+
this.updateState("idle");
|
|
55
|
+
} else if (
|
|
56
|
+
data.includes("Permission") ||
|
|
57
|
+
data.includes("approve") ||
|
|
58
|
+
data.includes("Allow")
|
|
59
|
+
) {
|
|
60
|
+
this.updateState("waiting_approval");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getCurrentState(): AdapterState {
|
|
66
|
+
return { ...this.state };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private updateState(
|
|
70
|
+
newState: "idle" | "running" | "waiting_approval" | "error",
|
|
71
|
+
): void {
|
|
72
|
+
if (this.state.currentState !== newState) {
|
|
73
|
+
this.state.currentState = newState;
|
|
74
|
+
|
|
75
|
+
if (this.onEmit) {
|
|
76
|
+
this.onEmit({
|
|
77
|
+
type: "state_change",
|
|
78
|
+
seq: Date.now(),
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
data: { state: newState },
|
|
81
|
+
adapterId: this.id,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private watchForEvents(): void {
|
|
88
|
+
// Watch for events.jsonl files in Claude Code project directories
|
|
89
|
+
if (!fs.existsSync(this.eventsDir)) return;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
this.watcher = fs.watch(
|
|
93
|
+
this.eventsDir,
|
|
94
|
+
{ recursive: true },
|
|
95
|
+
(eventType, filename) => {
|
|
96
|
+
if (
|
|
97
|
+
filename &&
|
|
98
|
+
(filename.endsWith("events.jsonl") ||
|
|
99
|
+
filename.endsWith("conversation.jsonl"))
|
|
100
|
+
) {
|
|
101
|
+
this.processEventFile(path.join(this.eventsDir, filename));
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
} catch {
|
|
106
|
+
// Directory may not exist or not be watchable
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private processEventFile(filePath: string): void {
|
|
111
|
+
try {
|
|
112
|
+
const stat = fs.statSync(filePath);
|
|
113
|
+
const lastSize = this.fileSizes.get(filePath) ?? 0;
|
|
114
|
+
if (stat.size <= lastSize) return;
|
|
115
|
+
|
|
116
|
+
// Read only new content (with proper fd cleanup)
|
|
117
|
+
let newData: Buffer;
|
|
118
|
+
const fd = fs.openSync(filePath, "r");
|
|
119
|
+
try {
|
|
120
|
+
newData = Buffer.alloc(stat.size - lastSize);
|
|
121
|
+
fs.readSync(fd, newData, 0, newData.length, lastSize);
|
|
122
|
+
this.fileSizes.set(filePath, stat.size);
|
|
123
|
+
} finally {
|
|
124
|
+
fs.closeSync(fd);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Parse JSONL lines
|
|
128
|
+
const lines = newData
|
|
129
|
+
.toString()
|
|
130
|
+
.split("\n")
|
|
131
|
+
.filter((l) => l.trim());
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
try {
|
|
134
|
+
const event = JSON.parse(line);
|
|
135
|
+
this.handleConversationEvent(event);
|
|
136
|
+
} catch {
|
|
137
|
+
// Skip malformed lines
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// File access error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private handleConversationEvent(event: Record<string, unknown>): void {
|
|
146
|
+
const type = event.type as string;
|
|
147
|
+
|
|
148
|
+
if (type === "assistant" || type === "tool_use") {
|
|
149
|
+
this.updateState("running");
|
|
150
|
+
} else if (type === "result" || type === "end_turn") {
|
|
151
|
+
this.updateState("idle");
|
|
152
|
+
} else if (type === "permission_request") {
|
|
153
|
+
this.updateState("waiting_approval");
|
|
154
|
+
} else if (type === "error") {
|
|
155
|
+
this.updateState("error");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// E10-008: codex adapter — passive mode, monitors Codex CLI events
|
|
2
|
+
// Reports run status: idle/running/waiting_approval/error
|
|
3
|
+
// Watches ~/.codex/ for session JSONL files and detects terminal patterns.
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
import { SemanticAdapter, AdapterState, SemanticEvent } from "./types.js";
|
|
9
|
+
|
|
10
|
+
export class CodexAdapter implements SemanticAdapter {
|
|
11
|
+
id = "codex";
|
|
12
|
+
name = "OpenAI Codex";
|
|
13
|
+
mode = "passive" as const;
|
|
14
|
+
capabilities = ["run-status", "conversation-events", "tool-use"];
|
|
15
|
+
|
|
16
|
+
private state: AdapterState = {
|
|
17
|
+
adapterId: "codex",
|
|
18
|
+
name: "OpenAI Codex",
|
|
19
|
+
mode: "passive",
|
|
20
|
+
capabilities: this.capabilities,
|
|
21
|
+
currentState: "idle",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
private watcher: fs.FSWatcher | null = null;
|
|
25
|
+
private fileSizes = new Map<string, number>();
|
|
26
|
+
private eventsDir: string;
|
|
27
|
+
private onEmit?: (event: SemanticEvent) => void;
|
|
28
|
+
|
|
29
|
+
constructor(onEmit?: (event: SemanticEvent) => void) {
|
|
30
|
+
this.onEmit = onEmit;
|
|
31
|
+
// Codex CLI stores session data in ~/.codex/
|
|
32
|
+
this.eventsDir = path.join(os.homedir(), ".codex");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
start(): void {
|
|
36
|
+
this.watchForEvents();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
stop(): void {
|
|
40
|
+
this.watcher?.close();
|
|
41
|
+
this.watcher = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onTerminalData(sessionName: string, data: string): void {
|
|
45
|
+
// Detect Codex CLI activity from terminal output patterns.
|
|
46
|
+
// Only process data that looks like Codex output to avoid false positives.
|
|
47
|
+
|
|
48
|
+
// Running indicators: thinking/working state
|
|
49
|
+
if (
|
|
50
|
+
data.includes("Thinking...") ||
|
|
51
|
+
data.includes("Working...") ||
|
|
52
|
+
data.includes("⠋") ||
|
|
53
|
+
data.includes("⠙") ||
|
|
54
|
+
data.includes("⠹") ||
|
|
55
|
+
data.includes("⠸") ||
|
|
56
|
+
data.includes("⠼") ||
|
|
57
|
+
data.includes("⠴") ||
|
|
58
|
+
data.includes("⠦") ||
|
|
59
|
+
data.includes("⠧") ||
|
|
60
|
+
data.includes("⠇") ||
|
|
61
|
+
data.includes("⠏")
|
|
62
|
+
) {
|
|
63
|
+
this.updateState("running");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Tool use indicators
|
|
68
|
+
if (
|
|
69
|
+
data.includes("Reading file:") ||
|
|
70
|
+
data.includes("Writing file:") ||
|
|
71
|
+
data.includes("Editing file:") ||
|
|
72
|
+
data.includes("Running:") ||
|
|
73
|
+
data.includes("Patch:") ||
|
|
74
|
+
data.includes("[tool_use]")
|
|
75
|
+
) {
|
|
76
|
+
this.updateState("running");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Approval / permission request indicators
|
|
81
|
+
if (
|
|
82
|
+
data.includes("Approve?") ||
|
|
83
|
+
data.includes("[y/N]") ||
|
|
84
|
+
data.includes("[y/n]") ||
|
|
85
|
+
data.includes("Allow this?") ||
|
|
86
|
+
data.includes("Run command?")
|
|
87
|
+
) {
|
|
88
|
+
this.updateState("waiting_approval");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Error indicators
|
|
93
|
+
if (data.includes("Error:") || data.includes("Failed:")) {
|
|
94
|
+
// Only count as error if it looks like Codex produced it,
|
|
95
|
+
// not arbitrary program output. Check for codex context.
|
|
96
|
+
if (
|
|
97
|
+
data.includes("codex") ||
|
|
98
|
+
data.includes("Codex") ||
|
|
99
|
+
data.includes("codex>")
|
|
100
|
+
) {
|
|
101
|
+
this.updateState("error");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Completion: return to codex prompt or explicit "Done"
|
|
107
|
+
if (data.includes("codex>") || data.includes("Done")) {
|
|
108
|
+
this.updateState("idle");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getCurrentState(): AdapterState {
|
|
114
|
+
return { ...this.state };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private updateState(
|
|
118
|
+
newState: "idle" | "running" | "waiting_approval" | "error",
|
|
119
|
+
): void {
|
|
120
|
+
if (this.state.currentState !== newState) {
|
|
121
|
+
this.state.currentState = newState;
|
|
122
|
+
|
|
123
|
+
if (this.onEmit) {
|
|
124
|
+
this.onEmit({
|
|
125
|
+
type: "state_change",
|
|
126
|
+
seq: Date.now(),
|
|
127
|
+
timestamp: new Date().toISOString(),
|
|
128
|
+
data: { state: newState },
|
|
129
|
+
adapterId: this.id,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private watchForEvents(): void {
|
|
136
|
+
// Watch for JSONL session files in ~/.codex/
|
|
137
|
+
if (!fs.existsSync(this.eventsDir)) return;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
this.watcher = fs.watch(
|
|
141
|
+
this.eventsDir,
|
|
142
|
+
{ recursive: true },
|
|
143
|
+
(eventType, filename) => {
|
|
144
|
+
if (
|
|
145
|
+
filename &&
|
|
146
|
+
(filename.endsWith(".jsonl") || filename.endsWith(".json"))
|
|
147
|
+
) {
|
|
148
|
+
this.processEventFile(path.join(this.eventsDir, filename));
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
} catch {
|
|
153
|
+
// Directory may not exist or not be watchable
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private processEventFile(filePath: string): void {
|
|
158
|
+
try {
|
|
159
|
+
const stat = fs.statSync(filePath);
|
|
160
|
+
const lastSize = this.fileSizes.get(filePath) ?? 0;
|
|
161
|
+
if (stat.size <= lastSize) return;
|
|
162
|
+
|
|
163
|
+
// Read only new content (with proper fd cleanup)
|
|
164
|
+
let newData: Buffer;
|
|
165
|
+
const fd = fs.openSync(filePath, "r");
|
|
166
|
+
try {
|
|
167
|
+
newData = Buffer.alloc(stat.size - lastSize);
|
|
168
|
+
fs.readSync(fd, newData, 0, newData.length, lastSize);
|
|
169
|
+
this.fileSizes.set(filePath, stat.size);
|
|
170
|
+
} finally {
|
|
171
|
+
fs.closeSync(fd);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Parse JSONL lines
|
|
175
|
+
const lines = newData
|
|
176
|
+
.toString()
|
|
177
|
+
.split("\n")
|
|
178
|
+
.filter((l) => l.trim());
|
|
179
|
+
for (const line of lines) {
|
|
180
|
+
try {
|
|
181
|
+
const event = JSON.parse(line);
|
|
182
|
+
this.handleSessionEvent(event);
|
|
183
|
+
} catch {
|
|
184
|
+
// Skip malformed lines
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// File access error — file may have been deleted between watch and read
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private handleSessionEvent(event: Record<string, unknown>): void {
|
|
193
|
+
const type = event.type as string;
|
|
194
|
+
|
|
195
|
+
// Codex JSON-RPC style events (items/turns/threads)
|
|
196
|
+
if (
|
|
197
|
+
type === "item.created" ||
|
|
198
|
+
type === "tool_use" ||
|
|
199
|
+
type === "turn.started"
|
|
200
|
+
) {
|
|
201
|
+
this.updateState("running");
|
|
202
|
+
} else if (type === "turn.completed" || type === "done") {
|
|
203
|
+
this.updateState("idle");
|
|
204
|
+
} else if (type === "permission_request") {
|
|
205
|
+
this.updateState("waiting_approval");
|
|
206
|
+
} else if (type === "error") {
|
|
207
|
+
this.updateState("error");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// E10-004: generic-shell adapter — baseline adapter, always available as fallback
|
|
2
|
+
// Reports basic shell info from OSC 133 shell integration data
|
|
3
|
+
|
|
4
|
+
import { SemanticAdapter, AdapterState } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export class GenericShellAdapter implements SemanticAdapter {
|
|
7
|
+
id = "generic-shell";
|
|
8
|
+
name = "Shell";
|
|
9
|
+
mode = "passive" as const;
|
|
10
|
+
capabilities = ["cwd", "last-command", "exit-code"];
|
|
11
|
+
|
|
12
|
+
private state: AdapterState = {
|
|
13
|
+
adapterId: "generic-shell",
|
|
14
|
+
name: "Shell",
|
|
15
|
+
mode: "passive",
|
|
16
|
+
capabilities: this.capabilities,
|
|
17
|
+
currentState: "idle",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
private lastCwd: string | null = null;
|
|
21
|
+
private lastCommand: string | null = null;
|
|
22
|
+
|
|
23
|
+
onTerminalData(sessionName: string, data: string): void {
|
|
24
|
+
// Detect command prompts and working directory from OSC sequences
|
|
25
|
+
// OSC 7: working directory
|
|
26
|
+
const osc7Match = data.match(/\x1b\]7;file:\/\/[^/]*([^\x07\x1b]+)/);
|
|
27
|
+
if (osc7Match) {
|
|
28
|
+
this.lastCwd = decodeURIComponent(osc7Match[1]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// OSC 133;B: command start — the command text follows
|
|
32
|
+
const osc133B = data.match(/\x1b\]133;B\x07/);
|
|
33
|
+
if (osc133B) {
|
|
34
|
+
this.state.currentState = "running";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// OSC 133;D: command finished
|
|
38
|
+
const osc133D = data.match(/\x1b\]133;D;?(\d*)\x07/);
|
|
39
|
+
if (osc133D) {
|
|
40
|
+
this.state.currentState = "idle";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getCurrentState(): AdapterState {
|
|
45
|
+
return {
|
|
46
|
+
...this.state,
|
|
47
|
+
lastEvent: this.lastCwd
|
|
48
|
+
? {
|
|
49
|
+
type: "cwd",
|
|
50
|
+
seq: 0,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
data: { cwd: this.lastCwd, lastCommand: this.lastCommand },
|
|
53
|
+
adapterId: this.id,
|
|
54
|
+
}
|
|
55
|
+
: undefined,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// E10 Adapter Platform — entry point
|
|
2
|
+
|
|
3
|
+
export { SemanticEvent, AdapterState, SemanticAdapter } from "./types.js";
|
|
4
|
+
export { AdapterRegistry } from "./registry.js";
|
|
5
|
+
export { GenericShellAdapter } from "./generic-shell.js";
|
|
6
|
+
export { ClaudeCodeAdapter } from "./claude-code.js";
|
|
7
|
+
export { CodexAdapter } from "./codex.js";
|
|
8
|
+
export {
|
|
9
|
+
AgentToolCall,
|
|
10
|
+
AgentApproval,
|
|
11
|
+
AgentTurn,
|
|
12
|
+
AgentSessionSummary,
|
|
13
|
+
parseClaudeCodeEvent,
|
|
14
|
+
parseCodexEvent,
|
|
15
|
+
} from "./agent-events.js";
|