dextunnel 0.1.0
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/LICENSE +211 -0
- package/README.md +112 -0
- package/SECURITY.md +27 -0
- package/SUPPORT.md +43 -0
- package/package.json +44 -0
- package/public/client-shared.js +1831 -0
- package/public/favicon.svg +11 -0
- package/public/host.html +29 -0
- package/public/host.js +2079 -0
- package/public/index.html +28 -0
- package/public/index.js +98 -0
- package/public/live-bridge-lifecycle.js +258 -0
- package/public/live-bridge-retry-state.js +61 -0
- package/public/live-selection-intent.js +79 -0
- package/public/remote-operator-state.js +316 -0
- package/public/remote.html +167 -0
- package/public/remote.js +3967 -0
- package/public/styles.css +2793 -0
- package/public/surface-view-state.js +89 -0
- package/public/voice-dictation.js +45 -0
- package/src/bin/desktop-rehydration-smoke.mjs +111 -0
- package/src/bin/dextunnel.mjs +41 -0
- package/src/bin/doctor.mjs +48 -0
- package/src/bin/launch-attest.mjs +39 -0
- package/src/bin/launch-status.mjs +49 -0
- package/src/bin/mobile-link-proxy.mjs +221 -0
- package/src/bin/mobile-proof.mjs +164 -0
- package/src/bin/mobile-transport-smoke.mjs +200 -0
- package/src/bin/probe-codex-app-server-write.mjs +36 -0
- package/src/bin/probe-codex-app-server.mjs +30 -0
- package/src/lib/agent-room-context.mjs +54 -0
- package/src/lib/agent-room-runtime.mjs +355 -0
- package/src/lib/agent-room-service.mjs +335 -0
- package/src/lib/agent-room-state.mjs +406 -0
- package/src/lib/agent-room-store.mjs +71 -0
- package/src/lib/agent-room-text.mjs +48 -0
- package/src/lib/app-server-contract.mjs +66 -0
- package/src/lib/app-server-runtime.mjs +60 -0
- package/src/lib/attachment-service.mjs +119 -0
- package/src/lib/bridge-api-handler.mjs +719 -0
- package/src/lib/bridge-runtime-lifecycle.mjs +51 -0
- package/src/lib/bridge-status-builder.mjs +60 -0
- package/src/lib/codex-app-server-client.mjs +1511 -0
- package/src/lib/companion-state.mjs +453 -0
- package/src/lib/control-lease-service.mjs +180 -0
- package/src/lib/debug-harness-service.mjs +173 -0
- package/src/lib/desktop-integration.mjs +146 -0
- package/src/lib/desktop-rehydration-smoke.mjs +269 -0
- package/src/lib/dextunnel-cli.mjs +122 -0
- package/src/lib/discovery-docs.mjs +1321 -0
- package/src/lib/fake-codex-app-server-bridge.mjs +340 -0
- package/src/lib/install-preflight.mjs +373 -0
- package/src/lib/interaction-resolution-service.mjs +185 -0
- package/src/lib/interaction-state.mjs +360 -0
- package/src/lib/launch-release-bar.mjs +158 -0
- package/src/lib/live-control-state.mjs +107 -0
- package/src/lib/live-payload-builder.mjs +298 -0
- package/src/lib/live-selection-transition-state.mjs +49 -0
- package/src/lib/live-transcript-state.mjs +549 -0
- package/src/lib/mobile-network-profile.mjs +39 -0
- package/src/lib/mock-codex-adapter.mjs +62 -0
- package/src/lib/operator-diagnostics.mjs +82 -0
- package/src/lib/repo-changes-service.mjs +527 -0
- package/src/lib/runtime-config.mjs +106 -0
- package/src/lib/selection-state-service.mjs +214 -0
- package/src/lib/session-store.mjs +355 -0
- package/src/lib/shared-room-state.mjs +473 -0
- package/src/lib/shared-selection-state.mjs +40 -0
- package/src/lib/sse-hub.mjs +35 -0
- package/src/lib/static-surface-service.mjs +71 -0
- package/src/lib/surface-access.mjs +189 -0
- package/src/lib/surface-presence-service.mjs +118 -0
- package/src/lib/surface-request-guard.mjs +52 -0
- package/src/lib/thread-sync-state.mjs +536 -0
- package/src/lib/watcher-lifecycle.mjs +287 -0
- package/src/server.mjs +1446 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
function printUsage() {
|
|
6
|
+
console.log(
|
|
7
|
+
"Usage: node src/bin/mobile-proof.mjs [--base-url http://127.0.0.1:4317] [--surface remote|host] [--transport-probe-send] [--no-native-probe-send] [--network-profile weak-mobile|weak-mobile-reconnect] [--proxy-port 4417]"
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
const options = {
|
|
13
|
+
baseUrl: "http://127.0.0.1:4317",
|
|
14
|
+
nativeProbeSend: true,
|
|
15
|
+
networkProfile: "",
|
|
16
|
+
proxyPort: 4417,
|
|
17
|
+
surface: "remote"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
const arg = argv[index];
|
|
22
|
+
|
|
23
|
+
if (arg === "--help" || arg === "-h") {
|
|
24
|
+
printUsage();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (arg === "--base-url") {
|
|
29
|
+
options.baseUrl = argv[index + 1] || options.baseUrl;
|
|
30
|
+
index += 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (arg === "--surface") {
|
|
35
|
+
options.surface = argv[index + 1] || options.surface;
|
|
36
|
+
index += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (arg === "--network-profile") {
|
|
41
|
+
options.networkProfile = argv[index + 1] || options.networkProfile;
|
|
42
|
+
index += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (arg === "--proxy-port") {
|
|
47
|
+
options.proxyPort = Number(argv[index + 1]) || options.proxyPort;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (arg === "--no-probe-send" || arg === "--no-native-probe-send") {
|
|
53
|
+
options.nativeProbeSend = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (arg === "--transport-probe-send") {
|
|
58
|
+
options.transportProbeSend = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return options;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function runCommand(command, args, options = {}) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const child = spawn(command, args, {
|
|
71
|
+
cwd: options.cwd || process.cwd(),
|
|
72
|
+
env: process.env,
|
|
73
|
+
stdio: "inherit"
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
child.on("error", reject);
|
|
77
|
+
child.on("exit", (code, signal) => {
|
|
78
|
+
if (signal) {
|
|
79
|
+
reject(new Error(`${command} exited via signal ${signal}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (code !== 0) {
|
|
84
|
+
reject(new Error(`${command} exited with code ${code}`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function main() {
|
|
94
|
+
const options = parseArgs(process.argv.slice(2));
|
|
95
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
96
|
+
const repoRoot = path.resolve(__dirname, "..", "..");
|
|
97
|
+
const nativeDir = path.join(repoRoot, "native", "apple");
|
|
98
|
+
let effectiveBaseUrl = options.baseUrl;
|
|
99
|
+
let proxyChild = null;
|
|
100
|
+
|
|
101
|
+
if (options.networkProfile) {
|
|
102
|
+
const proxyScript = path.join(repoRoot, "src", "bin", "mobile-link-proxy.mjs");
|
|
103
|
+
proxyChild = spawn(process.execPath, [
|
|
104
|
+
proxyScript,
|
|
105
|
+
"--target-base-url",
|
|
106
|
+
options.baseUrl,
|
|
107
|
+
"--listen-port",
|
|
108
|
+
String(options.proxyPort),
|
|
109
|
+
"--profile",
|
|
110
|
+
options.networkProfile
|
|
111
|
+
], {
|
|
112
|
+
cwd: repoRoot,
|
|
113
|
+
env: process.env,
|
|
114
|
+
stdio: "inherit"
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
proxyChild.on("error", (error) => {
|
|
118
|
+
console.error(`[mobile-proof] proxy failed: ${error.message}`);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
122
|
+
effectiveBaseUrl = `http://127.0.0.1:${options.proxyPort}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const transportArgs = [
|
|
126
|
+
path.join(repoRoot, "src", "bin", "mobile-transport-smoke.mjs"),
|
|
127
|
+
"--base-url",
|
|
128
|
+
effectiveBaseUrl,
|
|
129
|
+
"--surface",
|
|
130
|
+
options.surface
|
|
131
|
+
];
|
|
132
|
+
if (options.transportProbeSend) {
|
|
133
|
+
transportArgs.push("--probe-send");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nativeArgs = [
|
|
137
|
+
"run",
|
|
138
|
+
"DextunnelNativeBridgeSmoke",
|
|
139
|
+
"--base-url",
|
|
140
|
+
effectiveBaseUrl,
|
|
141
|
+
"--surface",
|
|
142
|
+
options.surface
|
|
143
|
+
];
|
|
144
|
+
if (options.nativeProbeSend) {
|
|
145
|
+
nativeArgs.push("--probe-send");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
console.log(`[mobile-proof] transport smoke -> ${effectiveBaseUrl} (${options.surface})`);
|
|
150
|
+
await runCommand(process.execPath, transportArgs, { cwd: repoRoot });
|
|
151
|
+
console.log("[mobile-proof] native bridge smoke");
|
|
152
|
+
await runCommand("swift", nativeArgs, { cwd: nativeDir });
|
|
153
|
+
console.log("[mobile-proof] complete");
|
|
154
|
+
} finally {
|
|
155
|
+
if (proxyChild && proxyChild.exitCode == null) {
|
|
156
|
+
proxyChild.kill("SIGTERM");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
main().catch((error) => {
|
|
162
|
+
console.error(`[mobile-proof] failed: ${error.message}`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
|
|
3
|
+
function parseArgs(argv) {
|
|
4
|
+
const options = {
|
|
5
|
+
baseUrl: "http://127.0.0.1:4317",
|
|
6
|
+
json: false,
|
|
7
|
+
probeSend: false,
|
|
8
|
+
surface: "remote"
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
12
|
+
const argument = argv[index];
|
|
13
|
+
if (argument === "--base-url" && argv[index + 1]) {
|
|
14
|
+
options.baseUrl = argv[index + 1];
|
|
15
|
+
index += 1;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (argument === "--surface" && argv[index + 1]) {
|
|
19
|
+
options.surface = argv[index + 1];
|
|
20
|
+
index += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (argument === "--probe-send") {
|
|
24
|
+
options.probeSend = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (argument === "--json") {
|
|
28
|
+
options.json = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (argument === "--help" || argument === "-h") {
|
|
32
|
+
printHelpAndExit(0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return options;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printHelpAndExit(code = 0) {
|
|
40
|
+
console.log("Usage: node src/bin/mobile-transport-smoke.mjs [--base-url http://127.0.0.1:4317] [--surface remote|host] [--probe-send] [--json]");
|
|
41
|
+
process.exit(code);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function measuredJson(url, init = {}) {
|
|
45
|
+
const startedAt = performance.now();
|
|
46
|
+
const response = await fetch(url, init);
|
|
47
|
+
const rawText = await response.text();
|
|
48
|
+
const elapsedMs = Math.round(performance.now() - startedAt);
|
|
49
|
+
const bytes = Buffer.byteLength(rawText || "", "utf8");
|
|
50
|
+
const payload = rawText ? JSON.parse(rawText) : null;
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const error = new Error(payload?.error || payload?.message || response.statusText || "Request failed.");
|
|
53
|
+
error.status = response.status;
|
|
54
|
+
error.payload = payload;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
bytes,
|
|
59
|
+
elapsedMs,
|
|
60
|
+
payload,
|
|
61
|
+
status: response.status,
|
|
62
|
+
url
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function surfaceHeaders(token) {
|
|
67
|
+
return {
|
|
68
|
+
"x-dextunnel-surface-token": token
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function summarizeMetric(name, result) {
|
|
73
|
+
return {
|
|
74
|
+
bytes: result.bytes,
|
|
75
|
+
elapsedMs: result.elapsedMs,
|
|
76
|
+
name,
|
|
77
|
+
status: result.status
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function main() {
|
|
82
|
+
const options = parseArgs(process.argv.slice(2));
|
|
83
|
+
const baseUrl = new URL(options.baseUrl);
|
|
84
|
+
const bootstrapUrl = new URL("/api/codex-app-server/bootstrap", baseUrl);
|
|
85
|
+
bootstrapUrl.searchParams.set("surface", options.surface);
|
|
86
|
+
|
|
87
|
+
const bootstrap = await measuredJson(bootstrapUrl);
|
|
88
|
+
const accessToken = bootstrap.payload?.accessToken;
|
|
89
|
+
if (!accessToken) {
|
|
90
|
+
throw new Error("Bootstrap did not return a surface token.");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const metrics = [summarizeMetric("bootstrap", bootstrap)];
|
|
94
|
+
|
|
95
|
+
const liveState = await measuredJson(new URL("/api/codex-app-server/live-state", baseUrl), {
|
|
96
|
+
headers: surfaceHeaders(accessToken)
|
|
97
|
+
});
|
|
98
|
+
metrics.push(summarizeMetric("live-state", liveState));
|
|
99
|
+
|
|
100
|
+
const threads = await measuredJson(new URL("/api/codex-app-server/threads", baseUrl), {
|
|
101
|
+
headers: surfaceHeaders(accessToken)
|
|
102
|
+
});
|
|
103
|
+
metrics.push(summarizeMetric("threads", threads));
|
|
104
|
+
|
|
105
|
+
const refresh = await measuredJson(new URL("/api/codex-app-server/refresh", baseUrl), {
|
|
106
|
+
headers: surfaceHeaders(accessToken),
|
|
107
|
+
method: "POST"
|
|
108
|
+
});
|
|
109
|
+
metrics.push(summarizeMetric("refresh", refresh));
|
|
110
|
+
|
|
111
|
+
const selectedThreadId = refresh.payload?.state?.selectedThreadId || liveState.payload?.selectedThreadId || null;
|
|
112
|
+
const selectedProjectCwd = refresh.payload?.state?.selectedProjectCwd || liveState.payload?.selectedProjectCwd || null;
|
|
113
|
+
|
|
114
|
+
if (selectedThreadId || selectedProjectCwd) {
|
|
115
|
+
const selection = await measuredJson(new URL("/api/codex-app-server/selection", baseUrl), {
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
cwd: selectedProjectCwd,
|
|
118
|
+
threadId: selectedThreadId
|
|
119
|
+
}),
|
|
120
|
+
headers: {
|
|
121
|
+
...surfaceHeaders(accessToken),
|
|
122
|
+
"Content-Type": "application/json"
|
|
123
|
+
},
|
|
124
|
+
method: "POST"
|
|
125
|
+
});
|
|
126
|
+
metrics.push(summarizeMetric("selection", selection));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (options.probeSend) {
|
|
130
|
+
if (!selectedThreadId) {
|
|
131
|
+
throw new Error("Probe send requires a selected thread.");
|
|
132
|
+
}
|
|
133
|
+
const stamp = new Date().toISOString().replaceAll(":", "").replaceAll(".", "");
|
|
134
|
+
const claim = await measuredJson(new URL("/api/codex-app-server/control", baseUrl), {
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
action: "claim",
|
|
137
|
+
reason: "transport_smoke",
|
|
138
|
+
threadId: selectedThreadId
|
|
139
|
+
}),
|
|
140
|
+
headers: {
|
|
141
|
+
...surfaceHeaders(accessToken),
|
|
142
|
+
"Content-Type": "application/json"
|
|
143
|
+
},
|
|
144
|
+
method: "POST"
|
|
145
|
+
});
|
|
146
|
+
metrics.push(summarizeMetric("claim", claim));
|
|
147
|
+
|
|
148
|
+
const send = await measuredJson(new URL("/api/codex-app-server/turn", baseUrl), {
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
attachments: [],
|
|
151
|
+
text: `TRANSPORT_SMOKE_PROBE_${stamp}. Reply with exactly TRANSPORT_SMOKE_ACK_${stamp}.`,
|
|
152
|
+
threadId: selectedThreadId
|
|
153
|
+
}),
|
|
154
|
+
headers: {
|
|
155
|
+
...surfaceHeaders(accessToken),
|
|
156
|
+
"Content-Type": "application/json"
|
|
157
|
+
},
|
|
158
|
+
method: "POST"
|
|
159
|
+
});
|
|
160
|
+
metrics.push(summarizeMetric("send", send));
|
|
161
|
+
|
|
162
|
+
const release = await measuredJson(new URL("/api/codex-app-server/control", baseUrl), {
|
|
163
|
+
body: JSON.stringify({
|
|
164
|
+
action: "release",
|
|
165
|
+
reason: "transport_smoke_cleanup",
|
|
166
|
+
threadId: selectedThreadId
|
|
167
|
+
}),
|
|
168
|
+
headers: {
|
|
169
|
+
...surfaceHeaders(accessToken),
|
|
170
|
+
"Content-Type": "application/json"
|
|
171
|
+
},
|
|
172
|
+
method: "POST"
|
|
173
|
+
});
|
|
174
|
+
metrics.push(summarizeMetric("release", release));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const summary = {
|
|
178
|
+
metrics,
|
|
179
|
+
selectedProjectCwd,
|
|
180
|
+
selectedThreadId,
|
|
181
|
+
surface: options.surface
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (options.json) {
|
|
185
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const metric of metrics) {
|
|
190
|
+
console.log(`[${options.surface}] ${metric.name} elapsedMs=${metric.elapsedMs} bytes=${metric.bytes} status=${metric.status}`);
|
|
191
|
+
}
|
|
192
|
+
if (selectedThreadId) {
|
|
193
|
+
console.log(`[${options.surface}] selectedThreadId=${selectedThreadId}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
main().catch((error) => {
|
|
198
|
+
console.error(error?.message || String(error));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { createCodexAppServerBridge } from "../lib/codex-app-server-client.mjs";
|
|
6
|
+
|
|
7
|
+
const cwd = process.argv[2] || path.join(tmpdir(), "dextunnel-app-server-write-probe");
|
|
8
|
+
const bridge = createCodexAppServerBridge();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await mkdir(cwd, { recursive: true });
|
|
12
|
+
const result = await bridge.sendText({
|
|
13
|
+
cwd,
|
|
14
|
+
text: "Reply with REMOTE_WRITE_OK only.",
|
|
15
|
+
createThreadIfMissing: true,
|
|
16
|
+
timeoutMs: 45000
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log(
|
|
20
|
+
JSON.stringify(
|
|
21
|
+
{
|
|
22
|
+
ok: true,
|
|
23
|
+
cwd,
|
|
24
|
+
mode: result.mode,
|
|
25
|
+
threadId: result.thread.id,
|
|
26
|
+
turnId: result.turn.id,
|
|
27
|
+
turnStatus: result.turn.status,
|
|
28
|
+
preview: result.snapshot.transcript.slice(-6)
|
|
29
|
+
},
|
|
30
|
+
null,
|
|
31
|
+
2
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
} finally {
|
|
35
|
+
await bridge.dispose();
|
|
36
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createCodexAppServerBridge, mapThreadToCompanionSnapshot } from "../lib/codex-app-server-client.mjs";
|
|
2
|
+
|
|
3
|
+
const cwd = process.argv[2] || process.cwd();
|
|
4
|
+
const bridge = createCodexAppServerBridge();
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
const thread = await bridge.getLatestThreadForCwd(cwd);
|
|
8
|
+
|
|
9
|
+
if (!thread) {
|
|
10
|
+
console.log(JSON.stringify({ cwd, found: false }, null, 2));
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const snapshot = mapThreadToCompanionSnapshot(thread);
|
|
15
|
+
console.log(
|
|
16
|
+
JSON.stringify(
|
|
17
|
+
{
|
|
18
|
+
found: true,
|
|
19
|
+
cwd,
|
|
20
|
+
thread: snapshot.thread,
|
|
21
|
+
transcriptCount: snapshot.transcript.length,
|
|
22
|
+
preview: snapshot.transcript.slice(-8)
|
|
23
|
+
},
|
|
24
|
+
null,
|
|
25
|
+
2
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
} finally {
|
|
29
|
+
await bridge.dispose();
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
function formatTimestampLabel(value) {
|
|
2
|
+
const date = new Date(value || 0);
|
|
3
|
+
if (Number.isNaN(date.getTime())) {
|
|
4
|
+
return "";
|
|
5
|
+
}
|
|
6
|
+
return date.toISOString();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatContextEntries(entries = [], limit = 10, { trimTopicText }) {
|
|
10
|
+
return entries
|
|
11
|
+
.slice(-limit)
|
|
12
|
+
.map((entry) => {
|
|
13
|
+
const label = entry?.participant?.label || entry?.participantId || entry?.origin || entry?.lane || entry?.role || "voice";
|
|
14
|
+
const text = trimTopicText(entry?.text || "", 280);
|
|
15
|
+
const timestamp = entry?.timestamp ? formatTimestampLabel(entry.timestamp) : "";
|
|
16
|
+
return `- ${timestamp ? `[${timestamp}] ` : ""}${label}: ${text}`;
|
|
17
|
+
})
|
|
18
|
+
.join("\n");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createAgentRoomContextBuilder({
|
|
22
|
+
buildSelectedChannel,
|
|
23
|
+
decorateSnapshot,
|
|
24
|
+
nowIso,
|
|
25
|
+
trimTopicText
|
|
26
|
+
}) {
|
|
27
|
+
function buildAgentRoomContextMarkdown({ roomState, snapshot, threadId }) {
|
|
28
|
+
const selectedSnapshot = snapshot ? decorateSnapshot(snapshot) : null;
|
|
29
|
+
const channel = selectedSnapshot?.channel || buildSelectedChannel(selectedSnapshot);
|
|
30
|
+
const mainEntries = selectedSnapshot?.transcript || [];
|
|
31
|
+
const roomEntries = roomState?.messages || [];
|
|
32
|
+
|
|
33
|
+
return [
|
|
34
|
+
"# Dextunnel Council Room Context",
|
|
35
|
+
"",
|
|
36
|
+
`generated_at: ${nowIso()}`,
|
|
37
|
+
`thread_id: ${threadId || channel.channelId || ""}`,
|
|
38
|
+
`server: ${channel.serverLabel || ""}`,
|
|
39
|
+
`channel: ${channel.channelSlug || ""}`,
|
|
40
|
+
`topic: ${channel.topic || ""}`,
|
|
41
|
+
"",
|
|
42
|
+
"## Main Codex thread excerpt",
|
|
43
|
+
formatContextEntries(mainEntries, 10, { trimTopicText }) || "- No main-thread transcript available.",
|
|
44
|
+
"",
|
|
45
|
+
"## Council room transcript",
|
|
46
|
+
formatContextEntries(roomEntries, 20, { trimTopicText }) || "- No prior council-room messages.",
|
|
47
|
+
""
|
|
48
|
+
].join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
buildAgentRoomContextMarkdown
|
|
53
|
+
};
|
|
54
|
+
}
|