codex-agent-view 0.4.7 → 0.5.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/.codex-plugin/plugin.json +4 -3
- package/README.ko.md +36 -34
- package/README.md +33 -31
- package/bin/codex-agent-view.mjs +340 -25
- package/package.json +2 -2
- package/public/app.js +351 -41
- package/public/index.html +1 -1
- package/skills/codex-agent-view/SKILL.md +22 -201
- package/src/runtime/server.mjs +371 -14
- package/skills/show-agents/SKILL.md +0 -112
- package/skills/show-agents/agents/openai.yaml +0 -6
package/bin/codex-agent-view.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
writeFile,
|
|
11
11
|
} from "node:fs/promises";
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
13
14
|
import { homedir } from "node:os";
|
|
14
15
|
import { dirname, join, resolve } from "node:path";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -17,6 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
17
18
|
import { startMonitorServer } from "../src/runtime/server.mjs";
|
|
18
19
|
import {
|
|
19
20
|
DEFAULT_PORT,
|
|
21
|
+
LOOPBACK_HOST,
|
|
20
22
|
ensureViewerToken,
|
|
21
23
|
readRuntimeInfo,
|
|
22
24
|
readViewerToken,
|
|
@@ -32,6 +34,22 @@ const PLUGIN_ID = "codex-agent-view@codex-agent-view";
|
|
|
32
34
|
const MARKETPLACE_NAME = "codex-agent-view";
|
|
33
35
|
const BUNDLE_MARKER = ".codex-agent-view-owned.json";
|
|
34
36
|
const BUNDLE_MARKER_SCHEMA_VERSION = 1;
|
|
37
|
+
const PREPARE_LIVE_VIEW_WAIT_MS = 2_000;
|
|
38
|
+
const PREPARE_LIVE_VIEW_POLL_MS = 40;
|
|
39
|
+
const VIEWER_GRANT_TIMEOUT_MS = 1_000;
|
|
40
|
+
const MAX_BOOTSTRAP_CREDENTIAL_LENGTH = 1_024;
|
|
41
|
+
const SIGNED_BOOTSTRAP_CREDENTIAL_PATTERN =
|
|
42
|
+
/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{43}$/;
|
|
43
|
+
const OWNERSHIP_PROOF_DOMAIN = "codex-agent-view/runtime-ownership/v1";
|
|
44
|
+
const OWNERSHIP_PROOF_TIMEOUT_MS = 1_000;
|
|
45
|
+
const EXTERNAL_BROWSER_OPEN_TIMEOUT_MS = 3_000;
|
|
46
|
+
const KNOWN_PRE_PROOF_MANAGED_VERSIONS = new Set([
|
|
47
|
+
"0.2.0", "0.2.1",
|
|
48
|
+
"0.3.0", "0.3.1", "0.3.2",
|
|
49
|
+
"0.4.0", "0.4.1", "0.4.2", "0.4.3", "0.4.4", "0.4.5", "0.4.6", "0.4.7",
|
|
50
|
+
]);
|
|
51
|
+
const CANONICAL_THREAD_ID_PATTERN =
|
|
52
|
+
/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
|
|
35
53
|
const INSTALL_ENTRIES = [
|
|
36
54
|
".agents",
|
|
37
55
|
".codex-plugin",
|
|
@@ -57,6 +75,7 @@ function printHelp() {
|
|
|
57
75
|
|
|
58
76
|
Usage:
|
|
59
77
|
codex-agent-view start [--port <port>] [--open]
|
|
78
|
+
codex-agent-view open
|
|
60
79
|
codex-agent-view status [--json]
|
|
61
80
|
codex-agent-view doctor [--json]
|
|
62
81
|
codex-agent-view install
|
|
@@ -65,9 +84,17 @@ Usage:
|
|
|
65
84
|
|
|
66
85
|
The monitor is read-only and binds only to 127.0.0.1.
|
|
67
86
|
Start prints the local URL without opening an external browser unless --open is set.
|
|
87
|
+
Open prepares an authenticated live view and launches it in the default browser.
|
|
68
88
|
`);
|
|
69
89
|
}
|
|
70
90
|
|
|
91
|
+
class LiveViewPreparationError extends Error {
|
|
92
|
+
constructor(code) {
|
|
93
|
+
super(code);
|
|
94
|
+
this.code = code;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
71
98
|
function parseStartArgs(args) {
|
|
72
99
|
let open = false;
|
|
73
100
|
let legacyNoOpen = false;
|
|
@@ -142,20 +169,43 @@ function run(command, args, { allowFailure = false } = {}) {
|
|
|
142
169
|
});
|
|
143
170
|
}
|
|
144
171
|
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
172
|
+
function externalBrowserCommand(url) {
|
|
173
|
+
if (process.platform === "darwin") return ["open", [url]];
|
|
174
|
+
if (process.platform === "win32") {
|
|
175
|
+
return ["rundll32.exe", ["url.dll,FileProtocolHandler", url]];
|
|
176
|
+
}
|
|
177
|
+
return ["xdg-open", [url]];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function openExternalBrowser(url) {
|
|
181
|
+
const [command, args] = externalBrowserCommand(url);
|
|
182
|
+
return new Promise((resolvePromise, reject) => {
|
|
183
|
+
const child = spawn(command, args, {
|
|
184
|
+
shell: false,
|
|
185
|
+
stdio: "ignore",
|
|
186
|
+
});
|
|
187
|
+
let settled = false;
|
|
188
|
+
const settle = (callback, value) => {
|
|
189
|
+
if (settled) return;
|
|
190
|
+
settled = true;
|
|
191
|
+
clearTimeout(timeout);
|
|
192
|
+
callback(value);
|
|
193
|
+
};
|
|
194
|
+
const timeout = setTimeout(() => {
|
|
195
|
+
child.kill();
|
|
196
|
+
settle(reject, new LiveViewPreparationError("browser_open_timeout"));
|
|
197
|
+
}, EXTERNAL_BROWSER_OPEN_TIMEOUT_MS);
|
|
198
|
+
child.once("error", () => {
|
|
199
|
+
settle(reject, new LiveViewPreparationError("browser_open_failed"));
|
|
200
|
+
});
|
|
201
|
+
child.once("close", (code) => {
|
|
202
|
+
if (code === 0) {
|
|
203
|
+
settle(resolvePromise);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
settle(reject, new LiveViewPreparationError("browser_open_failed"));
|
|
207
|
+
});
|
|
156
208
|
});
|
|
157
|
-
child.unref();
|
|
158
|
-
child.on("error", () => {});
|
|
159
209
|
}
|
|
160
210
|
|
|
161
211
|
async function start(args) {
|
|
@@ -189,12 +239,20 @@ async function start(args) {
|
|
|
189
239
|
process.stdout.write(`Codex Agent View is running at ${monitor.url}\n`);
|
|
190
240
|
process.stdout.write("Press Ctrl+C to stop the in-memory monitor.\n");
|
|
191
241
|
if (options.open) {
|
|
192
|
-
|
|
242
|
+
try {
|
|
243
|
+
await openExternalBrowser(monitor.url);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
await monitor.close();
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
193
248
|
}
|
|
194
249
|
}
|
|
195
250
|
|
|
196
251
|
async function fetchState() {
|
|
197
252
|
const runtime = await readRuntimeInfo();
|
|
253
|
+
if ((await runtimeEndpointState(runtime)) !== "owned") {
|
|
254
|
+
throw new Error("the runtime endpoint was not identified as an owned monitor");
|
|
255
|
+
}
|
|
198
256
|
const response = await fetch(`http://${runtime.host}:${runtime.port}/api/state`, {
|
|
199
257
|
headers: { authorization: `Bearer ${runtime.token}` },
|
|
200
258
|
signal: AbortSignal.timeout(1_500),
|
|
@@ -483,7 +541,7 @@ async function revokeViewerCredential(preflight) {
|
|
|
483
541
|
};
|
|
484
542
|
}
|
|
485
543
|
|
|
486
|
-
async function
|
|
544
|
+
async function legacyRuntimeEndpointState(runtime) {
|
|
487
545
|
try {
|
|
488
546
|
const response = await fetch(`http://${runtime.host}:${runtime.port}/api/state`, {
|
|
489
547
|
headers: { authorization: `Bearer ${runtime.token}` },
|
|
@@ -502,15 +560,252 @@ async function runtimeEndpointState(runtime) {
|
|
|
502
560
|
}
|
|
503
561
|
}
|
|
504
562
|
|
|
505
|
-
async function
|
|
506
|
-
|
|
563
|
+
async function runtimeEndpointState(
|
|
564
|
+
runtime,
|
|
565
|
+
{ allowLegacyBearerProbe = false } = {},
|
|
566
|
+
) {
|
|
567
|
+
const ownership = await runtimeOwnershipState(runtime);
|
|
568
|
+
if (ownership !== "unrelated" || !allowLegacyBearerProbe) {
|
|
569
|
+
return ownership;
|
|
570
|
+
}
|
|
571
|
+
return legacyRuntimeEndpointState(runtime);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async function runtimeResponds(runtime, options) {
|
|
575
|
+
return (await runtimeEndpointState(runtime, options)) === "owned";
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function expectedOwnershipProof(nonce, runtimeToken) {
|
|
579
|
+
return createHmac("sha256", runtimeToken)
|
|
580
|
+
.update(OWNERSHIP_PROOF_DOMAIN)
|
|
581
|
+
.update("\0")
|
|
582
|
+
.update(nonce)
|
|
583
|
+
.digest("base64url");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function runtimeOwnershipState(runtime) {
|
|
587
|
+
const nonce = randomBytes(32).toString("base64url");
|
|
588
|
+
let response;
|
|
589
|
+
try {
|
|
590
|
+
response = await fetch(
|
|
591
|
+
`http://${runtime.host}:${runtime.port}/api/internal/ownership-proof`,
|
|
592
|
+
{
|
|
593
|
+
body: JSON.stringify({ nonce }),
|
|
594
|
+
headers: { "content-type": "application/json" },
|
|
595
|
+
method: "POST",
|
|
596
|
+
signal: AbortSignal.timeout(OWNERSHIP_PROOF_TIMEOUT_MS),
|
|
597
|
+
},
|
|
598
|
+
);
|
|
599
|
+
} catch {
|
|
600
|
+
return "absent";
|
|
601
|
+
}
|
|
602
|
+
if (response.status !== 200) {
|
|
603
|
+
await response.body?.cancel();
|
|
604
|
+
return "unrelated";
|
|
605
|
+
}
|
|
606
|
+
const payload = await response.json().catch(() => null);
|
|
607
|
+
if (
|
|
608
|
+
payload === null ||
|
|
609
|
+
typeof payload !== "object" ||
|
|
610
|
+
Array.isArray(payload) ||
|
|
611
|
+
Object.keys(payload).sort().join(",") !== "proof,status" ||
|
|
612
|
+
payload.status !== "owned" ||
|
|
613
|
+
typeof payload.proof !== "string" ||
|
|
614
|
+
!/^[A-Za-z0-9_-]{43}$/.test(payload.proof)
|
|
615
|
+
) {
|
|
616
|
+
return "unrelated";
|
|
617
|
+
}
|
|
618
|
+
const supplied = Buffer.from(payload.proof);
|
|
619
|
+
const expected = Buffer.from(expectedOwnershipProof(nonce, runtime.token));
|
|
620
|
+
return supplied.length === expected.length && timingSafeEqual(supplied, expected)
|
|
621
|
+
? "owned"
|
|
622
|
+
: "unrelated";
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function inspectInstalledBundleForLiveView() {
|
|
626
|
+
const destination = join(runtimeDirectory(), "marketplace");
|
|
627
|
+
const stats = await pathExists(destination);
|
|
628
|
+
if (!stats) {
|
|
629
|
+
throw new LiveViewPreparationError("plugin_not_installed");
|
|
630
|
+
}
|
|
631
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
632
|
+
throw new LiveViewPreparationError("plugin_bundle_unowned");
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const marker = await readJsonRegularFile(join(destination, BUNDLE_MARKER));
|
|
636
|
+
if (
|
|
637
|
+
marker?.schema_version !== BUNDLE_MARKER_SCHEMA_VERSION ||
|
|
638
|
+
marker?.package !== MARKETPLACE_NAME ||
|
|
639
|
+
marker?.plugin_id !== PLUGIN_ID
|
|
640
|
+
) {
|
|
641
|
+
throw new LiveViewPreparationError("plugin_bundle_unowned");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const manifest = await readJsonRegularFile(
|
|
645
|
+
join(destination, ".codex-plugin", "plugin.json"),
|
|
646
|
+
);
|
|
647
|
+
if (manifest?.name !== MARKETPLACE_NAME || typeof manifest.version !== "string") {
|
|
648
|
+
throw new LiveViewPreparationError("plugin_bundle_invalid");
|
|
649
|
+
}
|
|
650
|
+
if (manifest.version !== (await packageVersion())) {
|
|
651
|
+
throw new LiveViewPreparationError("plugin_version_mismatch");
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function autoStartEnvironment() {
|
|
656
|
+
const env = {};
|
|
657
|
+
for (const key of [
|
|
658
|
+
"CODEX_AGENT_VIEW_AUTO_START_PORT",
|
|
659
|
+
"CODEX_AGENT_VIEW_RUNTIME_DIR",
|
|
660
|
+
"SystemRoot",
|
|
661
|
+
]) {
|
|
662
|
+
if (typeof process.env[key] === "string") {
|
|
663
|
+
env[key] = process.env[key];
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return env;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function startMonitorDetached() {
|
|
670
|
+
const child = spawn(
|
|
671
|
+
process.execPath,
|
|
672
|
+
[fileURLToPath(new URL("../scripts/auto-start-monitor.mjs", import.meta.url))],
|
|
673
|
+
{
|
|
674
|
+
detached: true,
|
|
675
|
+
env: autoStartEnvironment(),
|
|
676
|
+
shell: false,
|
|
677
|
+
stdio: "ignore",
|
|
678
|
+
},
|
|
679
|
+
);
|
|
680
|
+
child.on("error", () => {});
|
|
681
|
+
child.unref();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async function liveViewRuntimeState() {
|
|
685
|
+
const runtime = await inspectRuntime();
|
|
686
|
+
if (runtime.kind === "unknown") {
|
|
687
|
+
throw new LiveViewPreparationError("runtime_record_invalid");
|
|
688
|
+
}
|
|
689
|
+
if (runtime.kind === "absent") {
|
|
690
|
+
return { kind: "not_running" };
|
|
691
|
+
}
|
|
692
|
+
const endpoint = await runtimeOwnershipState(runtime.info);
|
|
693
|
+
if (endpoint === "owned") {
|
|
694
|
+
return { info: runtime.info, kind: "owned" };
|
|
695
|
+
}
|
|
696
|
+
if (endpoint === "absent") {
|
|
697
|
+
return { kind: "not_running" };
|
|
698
|
+
}
|
|
699
|
+
throw new LiveViewPreparationError("unowned_runtime");
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function inheritedExcludedSessionId() {
|
|
703
|
+
const inheritedThreadId = process.env.CODEX_THREAD_ID;
|
|
704
|
+
return (
|
|
705
|
+
typeof inheritedThreadId === "string" &&
|
|
706
|
+
CANONICAL_THREAD_ID_PATTERN.test(inheritedThreadId)
|
|
707
|
+
? inheritedThreadId.toLowerCase()
|
|
708
|
+
: null
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async function requestViewerGrant(runtime, excludeSessionId) {
|
|
713
|
+
let response;
|
|
714
|
+
try {
|
|
715
|
+
response = await fetch(
|
|
716
|
+
`http://${runtime.host}:${runtime.port}/api/internal/viewer-grant`,
|
|
717
|
+
{
|
|
718
|
+
body: JSON.stringify({ exclude_session_id: excludeSessionId }),
|
|
719
|
+
headers: {
|
|
720
|
+
authorization: `Bearer ${runtime.token}`,
|
|
721
|
+
"content-type": "application/json",
|
|
722
|
+
},
|
|
723
|
+
method: "POST",
|
|
724
|
+
signal: AbortSignal.timeout(VIEWER_GRANT_TIMEOUT_MS),
|
|
725
|
+
},
|
|
726
|
+
);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
throw new LiveViewPreparationError(
|
|
729
|
+
error?.name === "TimeoutError" || error?.name === "AbortError"
|
|
730
|
+
? "viewer_grant_timeout"
|
|
731
|
+
: "viewer_grant_unavailable",
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
if (response.status !== 201) {
|
|
735
|
+
await response.body?.cancel();
|
|
736
|
+
throw new LiveViewPreparationError("viewer_grant_rejected");
|
|
737
|
+
}
|
|
738
|
+
const payload = await response.json().catch(() => null);
|
|
739
|
+
const credential = payload?.bootstrap_credential;
|
|
740
|
+
if (
|
|
741
|
+
payload === null ||
|
|
742
|
+
typeof payload !== "object" ||
|
|
743
|
+
Array.isArray(payload) ||
|
|
744
|
+
Object.keys(payload).sort().join(",") !==
|
|
745
|
+
"bootstrap_credential,expires_in_ms,status" ||
|
|
746
|
+
payload.status !== "granted" ||
|
|
747
|
+
payload.expires_in_ms !== 60_000 ||
|
|
748
|
+
typeof credential !== "string" ||
|
|
749
|
+
credential.length > MAX_BOOTSTRAP_CREDENTIAL_LENGTH ||
|
|
750
|
+
!SIGNED_BOOTSTRAP_CREDENTIAL_PATTERN.test(credential) ||
|
|
751
|
+
credential === runtime.token ||
|
|
752
|
+
credential === runtime.viewer_token
|
|
753
|
+
) {
|
|
754
|
+
throw new LiveViewPreparationError("viewer_grant_invalid_response");
|
|
755
|
+
}
|
|
756
|
+
return credential;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function liveViewTarget(runtime, bootstrapCredential) {
|
|
760
|
+
return `http://${LOOPBACK_HOST}:${runtime.port}/#grant=${encodeURIComponent(bootstrapCredential)}`;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
async function prepareLiveViewTarget(args) {
|
|
764
|
+
if (args.length > 0) {
|
|
765
|
+
throw new LiveViewPreparationError("invalid_arguments");
|
|
766
|
+
}
|
|
767
|
+
await inspectInstalledBundleForLiveView();
|
|
768
|
+
let runtime = await liveViewRuntimeState();
|
|
769
|
+
if (runtime.kind !== "owned") {
|
|
770
|
+
startMonitorDetached();
|
|
771
|
+
const deadline = Date.now() + PREPARE_LIVE_VIEW_WAIT_MS;
|
|
772
|
+
do {
|
|
773
|
+
await new Promise((resolvePromise) =>
|
|
774
|
+
setTimeout(resolvePromise, PREPARE_LIVE_VIEW_POLL_MS),
|
|
775
|
+
);
|
|
776
|
+
runtime = await liveViewRuntimeState();
|
|
777
|
+
if (runtime.kind === "owned") break;
|
|
778
|
+
} while (Date.now() < deadline);
|
|
779
|
+
}
|
|
780
|
+
if (runtime.kind !== "owned") {
|
|
781
|
+
throw new LiveViewPreparationError("monitor_start_timeout");
|
|
782
|
+
}
|
|
783
|
+
const bootstrapCredential = await requestViewerGrant(
|
|
784
|
+
runtime.info,
|
|
785
|
+
inheritedExcludedSessionId(),
|
|
786
|
+
);
|
|
787
|
+
return liveViewTarget(runtime.info, bootstrapCredential);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
async function openLiveView(args) {
|
|
791
|
+
try {
|
|
792
|
+
const target = await prepareLiveViewTarget(args);
|
|
793
|
+
await openExternalBrowser(target);
|
|
794
|
+
process.stdout.write("Codex Agent View opened in the default browser.\n");
|
|
795
|
+
} catch (error) {
|
|
796
|
+
const code = error instanceof LiveViewPreparationError
|
|
797
|
+
? error.code
|
|
798
|
+
: "live_view_open_failed";
|
|
799
|
+
process.stderr.write(`codex-agent-view: live view open failed (${code})\n`);
|
|
800
|
+
process.exitCode = 1;
|
|
801
|
+
}
|
|
507
802
|
}
|
|
508
803
|
|
|
509
|
-
async function stopRunningRuntime(preflight) {
|
|
804
|
+
async function stopRunningRuntime(preflight, options = {}) {
|
|
510
805
|
if (preflight.kind !== "valid") {
|
|
511
806
|
return false;
|
|
512
807
|
}
|
|
513
|
-
const endpointState = await runtimeEndpointState(preflight.info);
|
|
808
|
+
const endpointState = await runtimeEndpointState(preflight.info, options);
|
|
514
809
|
if (endpointState === "absent") {
|
|
515
810
|
return false;
|
|
516
811
|
}
|
|
@@ -560,7 +855,7 @@ async function stopRunningRuntime(preflight) {
|
|
|
560
855
|
"runtime ownership changed during uninstall; new or unrecognized runtime data was preserved",
|
|
561
856
|
);
|
|
562
857
|
}
|
|
563
|
-
if (!(await runtimeResponds(current.info))) {
|
|
858
|
+
if (!(await runtimeResponds(current.info, options))) {
|
|
564
859
|
await removeRuntimeInfo(current.info.token);
|
|
565
860
|
return true;
|
|
566
861
|
}
|
|
@@ -590,7 +885,15 @@ async function inspectPluginBundle(destination) {
|
|
|
590
885
|
) {
|
|
591
886
|
return { kind: "unmanaged" };
|
|
592
887
|
}
|
|
593
|
-
return { kind: "managed" };
|
|
888
|
+
return { kind: "managed", version: manifest.version };
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function legacyBearerProbeOptions(bundle) {
|
|
892
|
+
return {
|
|
893
|
+
allowLegacyBearerProbe:
|
|
894
|
+
bundle.kind === "managed" &&
|
|
895
|
+
KNOWN_PRE_PROOF_MANAGED_VERSIONS.has(bundle.version),
|
|
896
|
+
};
|
|
594
897
|
}
|
|
595
898
|
|
|
596
899
|
function unmanagedBundleError(destination) {
|
|
@@ -670,6 +973,9 @@ async function install() {
|
|
|
670
973
|
? runtime.info.viewer_token || runtime.info.token
|
|
671
974
|
: undefined;
|
|
672
975
|
await ensureViewerToken(process.env, { seedToken });
|
|
976
|
+
if (runtime.kind === "valid") {
|
|
977
|
+
await stopRunningRuntime(runtime, legacyBearerProbeOptions(bundle));
|
|
978
|
+
}
|
|
673
979
|
await copyPluginBundle(destination);
|
|
674
980
|
if (!existing) {
|
|
675
981
|
await run("codex", ["plugin", "marketplace", "add", destination, "--json"]);
|
|
@@ -711,7 +1017,7 @@ function isBroadRuntimeRoot(root) {
|
|
|
711
1017
|
);
|
|
712
1018
|
}
|
|
713
1019
|
|
|
714
|
-
async function purgeStaleRuntime(preflight) {
|
|
1020
|
+
async function purgeStaleRuntime(preflight, options = {}) {
|
|
715
1021
|
if (preflight.kind !== "valid") {
|
|
716
1022
|
return preflight.kind === "unknown";
|
|
717
1023
|
}
|
|
@@ -723,7 +1029,7 @@ async function purgeStaleRuntime(preflight) {
|
|
|
723
1029
|
if (current.kind !== "valid" || current.info.token !== preflight.info.token) {
|
|
724
1030
|
return true;
|
|
725
1031
|
}
|
|
726
|
-
const endpointState = await runtimeEndpointState(current.info);
|
|
1032
|
+
const endpointState = await runtimeEndpointState(current.info, options);
|
|
727
1033
|
if (endpointState === "owned") {
|
|
728
1034
|
throw new Error("the Codex Agent View monitor started during uninstall; runtime data was preserved");
|
|
729
1035
|
}
|
|
@@ -790,7 +1096,11 @@ async function uninstall(args) {
|
|
|
790
1096
|
|
|
791
1097
|
const runtimePreflight = await inspectRuntime();
|
|
792
1098
|
const viewerPreflight = await inspectViewerCredential();
|
|
793
|
-
const
|
|
1099
|
+
const lifecycleProbeOptions = legacyBearerProbeOptions(bundlePreflight);
|
|
1100
|
+
const stoppedMonitor = await stopRunningRuntime(
|
|
1101
|
+
runtimePreflight,
|
|
1102
|
+
lifecycleProbeOptions,
|
|
1103
|
+
);
|
|
794
1104
|
|
|
795
1105
|
await run("codex", ["plugin", "remove", PLUGIN_ID, "--json"], { allowFailure: true });
|
|
796
1106
|
await run("codex", ["plugin", "marketplace", "remove", MARKETPLACE_NAME, "--json"], {
|
|
@@ -799,7 +1109,10 @@ async function uninstall(args) {
|
|
|
799
1109
|
await removeManagedPluginBundle(bundle);
|
|
800
1110
|
const viewerCredential = await revokeViewerCredential(viewerPreflight);
|
|
801
1111
|
if (purge) {
|
|
802
|
-
const preservedRuntimeFile = await purgeStaleRuntime(
|
|
1112
|
+
const preservedRuntimeFile = await purgeStaleRuntime(
|
|
1113
|
+
runtimePreflight,
|
|
1114
|
+
lifecycleProbeOptions,
|
|
1115
|
+
);
|
|
803
1116
|
const removedRoot = await removeRuntimeRootIfEmpty(root);
|
|
804
1117
|
if (removedRoot) {
|
|
805
1118
|
process.stdout.write(`Removed plugin, marketplace, and runtime data from ${root}.\n`);
|
|
@@ -838,6 +1151,8 @@ async function main() {
|
|
|
838
1151
|
process.stdout.write(`${await packageVersion()}\n`);
|
|
839
1152
|
} else if (command === "start") {
|
|
840
1153
|
await start(args);
|
|
1154
|
+
} else if (command === "open") {
|
|
1155
|
+
await openLiveView(args);
|
|
841
1156
|
} else if (command === "status") {
|
|
842
1157
|
await status(args);
|
|
843
1158
|
} else if (command === "doctor") {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-agent-view",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Follow Codex work and participating agent progress in a clear, read-only live view
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Follow Codex work and participating agent progress in a clear, read-only live view opened from the official Codex app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/core/index.mjs",
|
|
7
7
|
"exports": "./src/core/index.mjs",
|