remcodex 0.1.0-beta.5 → 0.1.0-beta.6
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/README.md +11 -10
- package/package.json +1 -1
- package/web/api.js +7 -0
- package/web/app.js +59 -10
- package/web/i18n/locales/en.js +1 -0
- package/web/i18n/locales/zh-CN.js +1 -0
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# 🚀 RemCodex
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> Remote control for Codex.
|
|
4
|
+
> From your browser and phone.
|
|
5
5
|
|
|
6
|
-
Run Codex
|
|
7
|
-
|
|
6
|
+
Run Codex on one machine.
|
|
7
|
+
Monitor, approve, and control the same session from another.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
>
|
|
9
|
+
🌐 https://remcodex.com
|
|
10
|
+
|
|
11
|
+
> Not a remote desktop. Not a proxy.
|
|
12
|
+
> A local-first way to control Codex away from the terminal.
|
|
12
13
|
|
|
13
14
|

|
|
14
15
|
|
|
@@ -16,10 +17,10 @@ Watch, approve, and control it from any device.
|
|
|
16
17
|
|
|
17
18
|
## ✨ What is RemCodex?
|
|
18
19
|
|
|
19
|
-
RemCodex is
|
|
20
|
+
RemCodex is **remote control for Codex**.
|
|
20
21
|
|
|
21
|
-
It
|
|
22
|
-
|
|
22
|
+
It lets you start Codex on one machine, then keep the same session visible,
|
|
23
|
+
interruptible, and controllable from another.
|
|
23
24
|
|
|
24
25
|
- 👀 See what the AI is doing — in real time
|
|
25
26
|
- ✅ Approve or reject actions before execution
|
package/package.json
CHANGED
package/web/api.js
CHANGED
|
@@ -138,6 +138,13 @@ export function resolveSessionApproval(sessionId, requestId, decision) {
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
export function retrySessionApproval(sessionId, requestId, payload = {}) {
|
|
142
|
+
return request(`/api/sessions/${sessionId}/approvals/${encodeURIComponent(requestId)}/retry`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
body: JSON.stringify(payload),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
141
148
|
export function getHealth() {
|
|
142
149
|
return request("/health");
|
|
143
150
|
}
|
package/web/app.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getSessionTimelineEvents,
|
|
16
16
|
getSessions,
|
|
17
17
|
resolveSessionApproval,
|
|
18
|
+
retrySessionApproval,
|
|
18
19
|
sendMessage,
|
|
19
20
|
stopSession,
|
|
20
21
|
syncImportedSession,
|
|
@@ -613,14 +614,10 @@ function resolveDetailPendingApproval(session, timelineState) {
|
|
|
613
614
|
const sessionPending = session?.pendingApproval || null;
|
|
614
615
|
const liveBusy = session?.liveBusy === true;
|
|
615
616
|
const sessionId = String(session?.sessionId || "").trim();
|
|
616
|
-
|
|
617
|
-
if (!liveBusy) {
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
617
|
+
const canResolve = liveBusy && sessionPending?.resumable !== false;
|
|
620
618
|
|
|
621
619
|
if (!timelinePending) {
|
|
622
620
|
return sessionPending &&
|
|
623
|
-
sessionPending.resumable !== false &&
|
|
624
621
|
!isApprovalSuppressed(sessionId, sessionPending.requestId, sessionPending.callId)
|
|
625
622
|
? sessionPending
|
|
626
623
|
: null;
|
|
@@ -629,7 +626,7 @@ function resolveDetailPendingApproval(session, timelineState) {
|
|
|
629
626
|
return {
|
|
630
627
|
...timelinePending,
|
|
631
628
|
...(sessionPending && sessionPending.requestId === timelinePending.requestId ? sessionPending : {}),
|
|
632
|
-
resumable:
|
|
629
|
+
resumable: canResolve,
|
|
633
630
|
};
|
|
634
631
|
}
|
|
635
632
|
|
|
@@ -7041,6 +7038,15 @@ function renderPendingApprovalBar(detailState) {
|
|
|
7041
7038
|
const restoreHint = !canResolve
|
|
7042
7039
|
? `<p class="approval-banner-meta approval-banner-meta--warning">${escapeHtml(t("approval.restoreHint"))}</p>`
|
|
7043
7040
|
: "";
|
|
7041
|
+
const actionHtml = canResolve
|
|
7042
|
+
? `
|
|
7043
|
+
<button type="button" class="secondary-button" data-approval-decision="decline">${escapeHtml(t("approval.deny"))}</button>
|
|
7044
|
+
<button type="button" class="secondary-button" data-approval-decision="accept">${escapeHtml(t("approval.allowOnce"))}</button>
|
|
7045
|
+
<button type="button" class="primary-button" data-approval-decision="acceptForSession">${escapeHtml(t("approval.allowForTurn"))}</button>
|
|
7046
|
+
`
|
|
7047
|
+
: `
|
|
7048
|
+
<button type="button" class="primary-button" data-approval-retry="true">${escapeHtml(t("approval.retryAction"))}</button>
|
|
7049
|
+
`;
|
|
7044
7050
|
|
|
7045
7051
|
return `
|
|
7046
7052
|
<section class="approval-banner" data-approval-id="${escapeHtml(approval.requestId)}" data-approval-resumable="${canResolve ? "true" : "false"}">
|
|
@@ -7073,9 +7079,7 @@ function renderPendingApprovalBar(detailState) {
|
|
|
7073
7079
|
${restoreHint}
|
|
7074
7080
|
</div>
|
|
7075
7081
|
<div class="approval-banner-actions">
|
|
7076
|
-
|
|
7077
|
-
<button type="button" class="secondary-button" data-approval-decision="accept" ${canResolve ? "" : "disabled"}>${escapeHtml(t("approval.allowOnce"))}</button>
|
|
7078
|
-
<button type="button" class="primary-button" data-approval-decision="acceptForSession" ${canResolve ? "" : "disabled"}>${escapeHtml(t("approval.allowForTurn"))}</button>
|
|
7082
|
+
${actionHtml}
|
|
7079
7083
|
</div>
|
|
7080
7084
|
</section>
|
|
7081
7085
|
`;
|
|
@@ -7083,7 +7087,52 @@ function renderPendingApprovalBar(detailState) {
|
|
|
7083
7087
|
|
|
7084
7088
|
function bindPendingApprovalControls(sessionId) {
|
|
7085
7089
|
const banner = document.querySelector("#session-approval-slot .approval-banner");
|
|
7086
|
-
if (!banner
|
|
7090
|
+
if (!banner) {
|
|
7091
|
+
return;
|
|
7092
|
+
}
|
|
7093
|
+
|
|
7094
|
+
const retryButton = banner.querySelector("[data-approval-retry]");
|
|
7095
|
+
if (retryButton instanceof HTMLButtonElement) {
|
|
7096
|
+
retryButton.onclick = async () => {
|
|
7097
|
+
const approval = state.detail.pendingApproval;
|
|
7098
|
+
const requestId = banner.getAttribute("data-approval-id");
|
|
7099
|
+
if (!approval || !requestId) {
|
|
7100
|
+
return;
|
|
7101
|
+
}
|
|
7102
|
+
|
|
7103
|
+
const previousPendingApproval = { ...approval };
|
|
7104
|
+
const previousStatus = state.detail.session?.status || "waiting_input";
|
|
7105
|
+
const previousLiveBusy = Boolean(state.detail.session?.liveBusy);
|
|
7106
|
+
retryButton.disabled = true;
|
|
7107
|
+
banner.setAttribute("aria-busy", "true");
|
|
7108
|
+
state.detail.pendingApproval = null;
|
|
7109
|
+
if (state.detail.session?.sessionId === sessionId) {
|
|
7110
|
+
state.detail.session.status = "running";
|
|
7111
|
+
state.detail.session.liveBusy = true;
|
|
7112
|
+
}
|
|
7113
|
+
scheduleSessionDetailRender({ immediate: true });
|
|
7114
|
+
|
|
7115
|
+
try {
|
|
7116
|
+
const codex = buildCodexLaunchPayload(
|
|
7117
|
+
state.detail.codexLaunch,
|
|
7118
|
+
state.detail.codexUiOptions,
|
|
7119
|
+
);
|
|
7120
|
+
const payload = codex ? { codex } : {};
|
|
7121
|
+
await retrySessionApproval(sessionId, requestId, payload);
|
|
7122
|
+
await resumeActiveSessionDetail("approval-retry");
|
|
7123
|
+
} catch (error) {
|
|
7124
|
+
state.detail.pendingApproval = previousPendingApproval;
|
|
7125
|
+
if (state.detail.session?.sessionId === sessionId) {
|
|
7126
|
+
state.detail.session.status = previousStatus;
|
|
7127
|
+
state.detail.session.liveBusy = previousLiveBusy;
|
|
7128
|
+
}
|
|
7129
|
+
scheduleSessionDetailRender({ immediate: true });
|
|
7130
|
+
showToast(messageOf(error));
|
|
7131
|
+
}
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
if (banner.getAttribute("data-approval-resumable") === "false") {
|
|
7087
7136
|
return;
|
|
7088
7137
|
}
|
|
7089
7138
|
|
package/web/i18n/locales/en.js
CHANGED
|
@@ -198,6 +198,7 @@ export default {
|
|
|
198
198
|
"approval.restore": "Restart required",
|
|
199
199
|
"approval.continueHint": "This step needs your approval before execution can continue.",
|
|
200
200
|
"approval.restoreHint": "This approval request was restored from history and the runtime is no longer active. Send the task again to request approval one more time.",
|
|
201
|
+
"approval.retryAction": "Request again",
|
|
201
202
|
"approval.deny": "Deny",
|
|
202
203
|
"approval.allowOnce": "Allow once",
|
|
203
204
|
"approval.allowForTurn": "Allow for turn",
|
|
@@ -198,6 +198,7 @@ export default {
|
|
|
198
198
|
"approval.restore": "需重新发起",
|
|
199
199
|
"approval.continueHint": "这一步需要你的确认后才能继续执行。",
|
|
200
200
|
"approval.restoreHint": "这条授权请求是从历史事件恢复的,当前运行已经结束。请重新发送这轮任务以再次发起授权。",
|
|
201
|
+
"approval.retryAction": "重新发起授权",
|
|
201
202
|
"approval.deny": "拒绝",
|
|
202
203
|
"approval.allowOnce": "允许一次",
|
|
203
204
|
"approval.allowForTurn": "本轮允许",
|