appback-remoteagent 0.17.0 → 0.18.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/README.md CHANGED
@@ -103,6 +103,8 @@ Current command surface implemented in `src/bot.ts`:
103
103
  | `/new` | Creates and binds a new session using the saved default mode in a new managed workspace |
104
104
  | `/switch <session>` | Rebinds this chat to an existing RemoteAgent session |
105
105
  | `/status` | Shows current session, workspace, provider, and sandbox state |
106
+ | `/model [name]` | Lists selectable provider models or changes the current session model |
107
+ | `/sandbox [codex <mode>]` | Lists Codex sandbox choices or changes the current session sandbox |
106
108
  | `/option retry <count>` | Sets the automatic continuation turn limit and persists it to `~/.remoteagent/.env` |
107
109
  | `/option timeout <seconds>` | Sets the provider execution timeout and persists it to `~/.remoteagent/.env` |
108
110
  | `/option intent <count>` | Sets retries for untagged intent-only provider replies and persists it to `~/.remoteagent/.env` |
@@ -110,6 +112,7 @@ Current command surface implemented in `src/bot.ts`:
110
112
  | `/state clear` | Clears the current session ledger without deleting the session |
111
113
  | `/state note <text>` | Adds an operator note to the session ledger |
112
114
  | `/bots` | Lists the currently configured Telegram bots |
115
+ | `/macro [alias\|number]` | Lists stored macros or runs one against the current session |
113
116
  | `/bot add <token>` | Adds a conversation bot, restarts the runtime, and confirms the result after restart |
114
117
  | `/bot doctor` | Checks configured Telegram bots and removes bots that Telegram reports as permanently dead |
115
118
  | `/bot remove <username\|id>` | Removes a configured Telegram bot, restarts the runtime, and confirms the result after restart |
@@ -127,6 +130,8 @@ Current command surface implemented in `src/bot.ts`:
127
130
  | `/queue remove <id>` | Removes one waiting instruction by its `Q001`-style id |
128
131
  | `/queue del` | Removes the most recently queued instruction |
129
132
 
133
+ Selection-oriented replies include Telegram inline buttons for sessions, models, macros, runtime option details, sandbox modes, queued instructions, and configured bot links. Text commands remain the canonical compatible interface. Callback actions use short-lived opaque IDs bound to the originating chat and user; they do not expose secrets or rely on visible list positions as internal identity. Selecting `danger-full-access` through a button requires a second confirmation.
134
+
130
135
  Multi-bot polling is tiered by recent activity and active provider work. See [docs/BOT_POLLING_POLICY.md](./docs/BOT_POLLING_POLICY.md).
131
136
 
132
137
  ### 2. Terminal control
@@ -18,7 +18,7 @@ export class CodexAdapter {
18
18
  const args = request.sessionId
19
19
  ? this.buildResumeArgs(request, outputPath, sandboxMode)
20
20
  : this.buildExecArgs(request, outputPath, sandboxMode);
21
- const { stdout, stderr, code, timedOut } = await this.runCodex(args, request.cwd, request.remoteSessionId, request.publicSessionId, request.message);
21
+ const { stdout, stderr, code, timedOut } = await this.runCodex(args, request.cwd, request.remoteSessionId, request.publicSessionId, request.message, request.onProgress);
22
22
  try {
23
23
  const sessionId = this.extractThreadId(stdout) ?? request.sessionId;
24
24
  const output = await this.readOutput(outputPath) || this.extractAgentMessage(stdout);
@@ -102,12 +102,49 @@ export class CodexAdapter {
102
102
  args.push("--dangerously-bypass-approvals-and-sandbox");
103
103
  }
104
104
  }
105
- runCodex(args, cwd, remoteSessionId, publicSessionId, input) {
105
+ runCodex(args, cwd, remoteSessionId, publicSessionId, input, onProgress) {
106
106
  return spawnWithPlatformShell(this.codexBin, args, cwd, this.currentTimeoutMs(), input, remoteSessionId, {
107
107
  REMOTEAGENT_SESSION_ID: remoteSessionId,
108
108
  REMOTEAGENT_PUBLIC_SESSION_ID: publicSessionId ?? "",
109
109
  REMOTEAGENT_WORKSPACE: cwd,
110
- });
110
+ }, (line) => this.handleProgressLine(line, onProgress));
111
+ }
112
+ async handleProgressLine(line, onProgress) {
113
+ if (!onProgress || !line.startsWith("{")) {
114
+ return;
115
+ }
116
+ let text;
117
+ try {
118
+ text = this.extractEventAgentText(JSON.parse(line));
119
+ }
120
+ catch {
121
+ // Ignore non-JSON stdout and let the normal final-response parser handle it.
122
+ return;
123
+ }
124
+ if (text && /^REPORT:progress(?:\r?\n|$)/i.test(text)) {
125
+ await onProgress(text);
126
+ }
127
+ }
128
+ extractEventAgentText(event) {
129
+ if (!event || typeof event !== "object") {
130
+ return undefined;
131
+ }
132
+ const record = event;
133
+ if (record.type === "item.completed" && record.item?.type === "agent_message") {
134
+ return record.item.text?.trim();
135
+ }
136
+ if (record.type === "event_msg" && record.payload?.type === "agent_message") {
137
+ return (record.payload.message ?? record.payload.text)?.trim();
138
+ }
139
+ if (record.type === "response_item" && record.payload?.type === "message") {
140
+ const text = record.payload.content
141
+ ?.filter((item) => item.type === "output_text" && typeof item.text === "string")
142
+ .map((item) => item.text)
143
+ .join("\n")
144
+ .trim();
145
+ return text || record.payload.text?.trim();
146
+ }
147
+ return undefined;
111
148
  }
112
149
  extractThreadId(stdout) {
113
150
  for (const line of stdout.split(/\r?\n/)) {
@@ -2,7 +2,7 @@ import process from "node:process";
2
2
  import { execFile, spawn } from "node:child_process";
3
3
  import { buildProviderEnv } from "./runtime-env.js";
4
4
  const activeCommands = new Map();
5
- export function spawnWithPlatformShell(bin, args, cwd, timeoutMs, input, executionKey, extraEnv) {
5
+ export function spawnWithPlatformShell(bin, args, cwd, timeoutMs, input, executionKey, extraEnv, onStdoutLine) {
6
6
  return new Promise((resolve, reject) => {
7
7
  const command = process.platform === "win32"
8
8
  ? spawn("cmd.exe", ["/d", "/c", "call", bin, ...args], {
@@ -19,13 +19,29 @@ export function spawnWithPlatformShell(bin, args, cwd, timeoutMs, input, executi
19
19
  }
20
20
  let stdout = "";
21
21
  let stderr = "";
22
+ let stdoutRemainder = "";
23
+ let stdoutCallbackTail = Promise.resolve();
22
24
  let timedOut = false;
23
25
  const timer = setTimeout(() => {
24
26
  timedOut = true;
25
27
  terminateProcessTree(command);
26
28
  }, timeoutMs);
27
29
  command.stdout.on("data", (chunk) => {
28
- stdout += chunk.toString();
30
+ const text = chunk.toString();
31
+ stdout += text;
32
+ if (!onStdoutLine) {
33
+ return;
34
+ }
35
+ stdoutRemainder += text;
36
+ const lines = stdoutRemainder.split(/\r?\n/);
37
+ stdoutRemainder = lines.pop() ?? "";
38
+ for (const line of lines) {
39
+ stdoutCallbackTail = stdoutCallbackTail
40
+ .then(() => onStdoutLine(line))
41
+ .catch((error) => {
42
+ console.warn(`[provider-stream] stdout callback failed: ${error instanceof Error ? error.message : String(error)}`);
43
+ });
44
+ }
29
45
  });
30
46
  command.stderr.on("data", (chunk) => {
31
47
  stderr += chunk.toString();
@@ -41,11 +57,19 @@ export function spawnWithPlatformShell(bin, args, cwd, timeoutMs, input, executi
41
57
  command.stdin.write(input);
42
58
  }
43
59
  command.stdin.end();
44
- command.on("close", (code) => {
60
+ command.on("close", async (code) => {
45
61
  clearTimeout(timer);
46
62
  if (executionKey && activeCommands.get(executionKey) === command) {
47
63
  activeCommands.delete(executionKey);
48
64
  }
65
+ if (onStdoutLine && stdoutRemainder) {
66
+ stdoutCallbackTail = stdoutCallbackTail
67
+ .then(() => onStdoutLine(stdoutRemainder))
68
+ .catch((error) => {
69
+ console.warn(`[provider-stream] stdout callback failed: ${error instanceof Error ? error.message : String(error)}`);
70
+ });
71
+ }
72
+ await stdoutCallbackTail;
49
73
  resolve({ stdout, stderr, code, timedOut });
50
74
  });
51
75
  });