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 CHANGED
@@ -1,14 +1,15 @@
1
1
  # 🚀 RemCodex
2
2
 
3
- > Control your AI coding agent from anywhere.
4
- > Built for Codex. Ready for more.
3
+ > Remote control for Codex.
4
+ > From your browser and phone.
5
5
 
6
- Run Codex once on your computer.
7
- Watch, approve, and control it from any device.
6
+ Run Codex on one machine.
7
+ Monitor, approve, and control the same session from another.
8
8
 
9
- > Not a wrapper. Not a proxy.
10
- > A real-time control layer for AI execution.
11
- > Turns AI execution into a controllable system.
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
  ![RemCodex hero cover](docs/assets/hero-cover.png)
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 a **local-first control layer for Codex**.
20
+ RemCodex is **remote control for Codex**.
20
21
 
21
- It turns AI execution into a **controllable system**
22
- visible, interruptible, and continuous.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remcodex",
3
- "version": "0.1.0-beta.5",
3
+ "version": "0.1.0-beta.6",
4
4
  "description": "Control Codex from anywhere. Even on your phone.",
5
5
  "license": "MIT",
6
6
  "bin": {
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: true,
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
- <button type="button" class="secondary-button" data-approval-decision="decline" ${canResolve ? "" : "disabled"}>${escapeHtml(t("approval.deny"))}</button>
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 || banner.getAttribute("data-approval-resumable") === "false") {
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
 
@@ -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": "本轮允许",