appback-remoteagent 0.23.3 → 0.23.4

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
@@ -106,6 +106,7 @@ Current command surface implemented in `src/bot.ts`:
106
106
  | `/switch <session>` | Rebinds this chat to an existing RemoteAgent session |
107
107
  | `/status` | Shows current session, workspace, provider, and sandbox state |
108
108
  | `/model [name]` | Lists selectable provider models or changes the current session model. New Codex sessions default to `gpt-6-astra`. Use `/model gpt-6-astra` for an existing session. |
109
+ | `/model restore` | Clears temporary Codex fallback after a usage reset or credit purchase. Server-wide; next execution uses each session's configured model. Running work is preserved. Also available through the model menu's restore button. |
109
110
  | `/option reasoning [low\|medium\|high\|xhigh\|max]` | Shows or changes Astra reasoning for all bots on this server. Defaults to `medium`; persisted as `CODEX_REASONING_EFFORT`. Applies on the next execution without restarting. Running replies retain their actual execution effort in the header. |
110
111
  | `/sandbox [codex <mode>]` | Lists Codex sandbox choices or changes the current session sandbox |
111
112
  | `/option retry <count>` | Sets the automatic continuation turn limit and persists it to `~/.remoteagent/.env` |
package/dist/bot.js CHANGED
@@ -27,7 +27,7 @@ const HELP_TEXT = [
27
27
  "/batch start|send|cancel|status",
28
28
  "/attach codex <thread_id>",
29
29
  "/attach claude <session_id>",
30
- "/model [name]",
30
+ "/model [name|restore]",
31
31
  "/queue [remove <id>|del]",
32
32
  "/stop",
33
33
  "/sandbox codex <read-only|workspace-write|danger-full-access>",
@@ -547,16 +547,22 @@ ${bridge.formatStatus(mapping)}`);
547
547
  const { args, rest } = parseCommand(ctx.message?.text, 1);
548
548
  const model = args[0]?.trim();
549
549
  if (rest?.trim()) {
550
- await reply(ctx, "Usage: `/model` or `/model <name|number>`", {
550
+ await reply(ctx, "Usage: `/model`, `/model <name|number>`, or `/model restore`", {
551
551
  parse_mode: "Markdown",
552
552
  });
553
553
  return;
554
554
  }
555
+ if (model?.toLowerCase() === "restore") {
556
+ await ensureOwnerControlAccess(ctx);
557
+ await reply(ctx, await bridge.restoreCodexModel());
558
+ return;
559
+ }
555
560
  if (!model) {
556
561
  const selection = await bridge.getModelSelection(botId, chatId);
557
562
  const rows = selection.presets.map((preset) => [
558
563
  actionButton(ctx, `${preset === selection.currentModel ? "✓ " : ""}${preset}`, { kind: "model.set", model: preset }),
559
564
  ]);
565
+ rows.push([actionButton(ctx, "원래 모델 복귀", { kind: "model.restore" })]);
560
566
  await reply(ctx, await bridge.formatModelSelection(botId, chatId), {
561
567
  parse_mode: "Markdown",
562
568
  ...(keyboardOptions(rows) ?? {}),
@@ -1151,6 +1157,11 @@ ${bridge.formatStatus(mapping)}`);
1151
1157
  await reply(ctx, await setChatModel(ctx, action.model));
1152
1158
  return;
1153
1159
  }
1160
+ if (action.kind === "model.restore") {
1161
+ await ensureOwnerControlAccess(ctx);
1162
+ await reply(ctx, await bridge.restoreCodexModel());
1163
+ return;
1164
+ }
1154
1165
  if (action.kind === "macro.run") {
1155
1166
  const result = await runMacro(ctx, action.alias);
1156
1167
  if (result) {
@@ -195,12 +195,17 @@ export class BridgeService {
195
195
  ...presets.map((item, index) => ` ${index + 1}. ${item}`),
196
196
  "",
197
197
  "Use `/model <name>` or `/model <number>` to change it.",
198
+ "Use `/model restore` to clear temporary Codex fallback for all bots on this server. The next execution uses each session's configured model.",
198
199
  ];
199
200
  if (presets.length === 0) {
200
201
  lines.splice(3, 1, "availablePresets: none");
201
202
  }
202
203
  return lines.join("\n");
203
204
  }
205
+ async restoreCodexModel() {
206
+ await this.codexUsageFallback.clear();
207
+ return "이 서버의 Codex 임시 전환 상태를 해제했습니다. 다음 실행부터 각 세션에 설정된 원래 모델을 사용합니다.\n진행 중인 작업과 세션의 모델 설정은 유지됩니다. 실제 사용 한도가 남아 있으면 다시 임시 전환될 수 있습니다.";
208
+ }
204
209
  async getModelSelection(botId, chatId) {
205
210
  const chatSession = await this.requireChat(botId, chatId);
206
211
  const provider = chatSession.session.mode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appback-remoteagent",
3
- "version": "0.23.3",
3
+ "version": "0.23.4",
4
4
  "description": "Personal installable session server for continuing local AI work across PC and Telegram",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -121,6 +121,14 @@ try {
121
121
  );
122
122
  assert.deepEqual(calls.map((call) => call.model), [primaryModel, CODEX_USAGE_FALLBACK_MODEL]);
123
123
 
124
+ await bridge.restoreCodexModel();
125
+ await assert.rejects(fs.stat(fallbackStatePath), { code: "ENOENT" });
126
+ failPrimaryWithUsageLimit = false;
127
+ calls.length = 0;
128
+ await bridge.routeMessage("test-bot", "test-chat", "after coupon reset");
129
+ assert.deepEqual(calls.map(call => call.model), [primaryModel]);
130
+ assert.equal((await store.getChatSession("test-bot", "test-chat"))?.session.sessionId, originalSessionId);
131
+
124
132
  console.log(JSON.stringify({
125
133
  ok: true,
126
134
  detectedExactUsageError: true,
@@ -130,6 +138,7 @@ try {
130
138
  fallbackSharedAcrossSessions: true,
131
139
  primaryRestoredAfterSuccessfulProbe: true,
132
140
  fallbackAttemptLimit: 1,
141
+ manualRestoreUsesPrimary: true,
133
142
  }, null, 2));
134
143
  } finally {
135
144
  await fs.rm(root, { recursive: true, force: true });
@@ -531,6 +531,20 @@ await waitForTelegramCall((call) => call.text.includes("Switched this chat to se
531
531
 
532
532
  await send("/model");
533
533
  const modelListCall = await waitForTelegramCall((call) => call.text.includes("availablePresets:"));
534
+ const restoreButton = findInlineButton(modelListCall, "원래 모델 복귀");
535
+ if (!restoreButton?.callback_data) throw new Error("Model restore button missing");
536
+ const fallbackPath = path.join(dataDir, "codex-usage-fallback.json");
537
+ const fallbackFixture = JSON.stringify({fallbackModel: "gpt-5.3-codex-spark", activatedAt: new Date().toISOString(), resetAt: "2099-01-01T00:00:00Z"});
538
+ const beforeRestore = await fs.readFile(path.join(dataDir, "state.json"), "utf8");
539
+ await fs.writeFile(fallbackPath, fallbackFixture);
540
+ await click(restoreButton.callback_data);
541
+ if (await pathExists(fallbackPath)) throw new Error("Restore button did not clear fallback");
542
+ await fs.writeFile(fallbackPath, fallbackFixture);
543
+ await send("/model restore");
544
+ await send("/model restore");
545
+ if (await pathExists(fallbackPath)) throw new Error("Restore command did not clear fallback");
546
+ const afterRestore = await fs.readFile(path.join(dataDir, "state.json"), "utf8");
547
+ if (JSON.stringify(JSON.parse(beforeRestore).sessions) !== JSON.stringify(JSON.parse(afterRestore).sessions)) throw new Error("Restore changed sessions");
534
548
  const modelButton = findInlineButton(modelListCall, "gpt-5.6-terra");
535
549
  if (!modelButton?.callback_data) {
536
550
  throw new Error(`Model selection button is missing: ${modelListCall.reply_markup}`);