agent-office-cli 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-office-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Run and manage AI agent sessions locally, with optional relay to agentoffice.top",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -3,11 +3,13 @@ const DEFAULT_LAN_HOST = "0.0.0.0";
3
3
  const DEFAULT_PORT = 8765;
4
4
  const DEFAULT_SERVER_URL = `http://${DEFAULT_HOST}:${DEFAULT_PORT}`;
5
5
  const LOG_LIMIT = 1000;
6
+ const TERMINAL_REPLAY_LIMIT = 128 * 1024;
6
7
 
7
8
  module.exports = {
8
9
  DEFAULT_HOST,
9
10
  DEFAULT_LAN_HOST,
10
11
  DEFAULT_PORT,
11
12
  DEFAULT_SERVER_URL,
12
- LOG_LIMIT
13
+ LOG_LIMIT,
14
+ TERMINAL_REPLAY_LIMIT
13
15
  };
@@ -1,7 +1,7 @@
1
1
  const { EventEmitter } = require("node:events");
2
2
  const os = require("node:os");
3
3
  const crypto = require("node:crypto");
4
- const { LOG_LIMIT } = require("../config");
4
+ const { LOG_LIMIT, TERMINAL_REPLAY_LIMIT } = require("../config");
5
5
  const { displayZoneFor } = require("../state");
6
6
  const { toPublicSession, toSessionSummary } = require("../session-contract");
7
7
 
@@ -53,7 +53,8 @@ function createSessionStore() {
53
53
  host: payload.host || os.hostname(),
54
54
  meta: { ...(payload.meta || {}) },
55
55
  logs: [...(payload.logs || [])],
56
- events: [...(payload.events || [])]
56
+ events: [...(payload.events || [])],
57
+ terminalReplay: String(payload.terminalReplay || "")
57
58
  };
58
59
  }
59
60
 
@@ -151,6 +152,10 @@ function createSessionStore() {
151
152
  const lines = String(chunk).replace(/\r/g, "").split("\n").filter(Boolean);
152
153
  session.logs.push(...lines);
153
154
  session.logs = session.logs.slice(-LOG_LIMIT);
155
+ session.terminalReplay = `${session.terminalReplay || ""}${String(chunk)}`;
156
+ if (session.terminalReplay.length > TERMINAL_REPLAY_LIMIT) {
157
+ session.terminalReplay = session.terminalReplay.slice(-TERMINAL_REPLAY_LIMIT);
158
+ }
154
159
  session.lastOutputAt = isoNow();
155
160
  session.updatedAt = session.lastOutputAt;
156
161
  // Terminal output does not change session state — skip session:update broadcast.
@@ -189,6 +194,11 @@ function createSessionStore() {
189
194
  return session ? toSessionSummary(session) : null;
190
195
  }
191
196
 
197
+ function getTerminalReplay(sessionId) {
198
+ const session = sessions.get(sessionId);
199
+ return session ? session.terminalReplay || "" : "";
200
+ }
201
+
192
202
  function listSessions() {
193
203
  return [...sessions.values()]
194
204
  .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
@@ -224,6 +234,7 @@ function createSessionStore() {
224
234
  markExit,
225
235
  getSession,
226
236
  getSessionSummary,
237
+ getTerminalReplay,
227
238
  listSessions,
228
239
  listSessionSummaries,
229
240
  removeSession
@@ -48,3 +48,21 @@ test("setSessionState derives displayZone from displayState override when zone i
48
48
  assert.equal(next.displayState, "attention");
49
49
  assert.equal(next.displayZone, "attention-zone");
50
50
  });
51
+
52
+ test("appendOutput keeps a raw terminal replay buffer for fast reconnects", () => {
53
+ const store = createSessionStore();
54
+ store.upsertSession({
55
+ sessionId: "sess_3",
56
+ provider: "generic",
57
+ title: "Shell",
58
+ command: "bash",
59
+ cwd: process.cwd(),
60
+ state: "working",
61
+ status: "running"
62
+ });
63
+
64
+ store.appendOutput("sess_3", "line one\r\n");
65
+ store.appendOutput("sess_3", "line two\n");
66
+
67
+ assert.equal(store.getTerminalReplay("sess_3"), "line one\r\nline two\n");
68
+ });
@@ -503,18 +503,25 @@ function createPtyManager({ store }) {
503
503
 
504
504
  let attachedClient = null;
505
505
  let tmuxStreamStarted = false;
506
+ let pendingCols = 120;
507
+ let pendingRows = 32;
506
508
 
507
509
  async function startTmuxStream(cols, rows) {
508
510
  if (tmuxStreamStarted || !entry || entry.transport !== "tmux") {
509
511
  return;
510
512
  }
511
513
  tmuxStreamStarted = true;
514
+ pendingCols = cols;
515
+ pendingRows = rows;
512
516
  try {
513
517
  const snapshot = await capturePane(entry.tmuxSession);
514
518
  if (snapshot && ws.readyState === 1) {
515
519
  ws.send(JSON.stringify({ type: "terminal:data", data: `${snapshot}\r\n` }));
516
520
  }
517
521
  attachedClient = attachClient(entry.tmuxSession, { cwd: entry.cwd, cols, rows });
522
+ if (pendingCols !== cols || pendingRows !== rows) {
523
+ attachedClient.resize(pendingCols, pendingRows);
524
+ }
518
525
  attachedClient.onData((chunk) => {
519
526
  if (ws.readyState === 1) {
520
527
  ws.send(JSON.stringify({ type: "terminal:data", data: chunk }));
@@ -532,7 +539,12 @@ function createPtyManager({ store }) {
532
539
 
533
540
  // For non-tmux transports that had immediate setup, keep original behavior
534
541
  if (entry && entry.transport === "pty") {
535
- // PTY sessions stream via broadcastTerminal, no per-client attach needed
542
+ const replay = store.getTerminalReplay(sessionId);
543
+ if (replay) {
544
+ ws.send(JSON.stringify({ type: "terminal:data", data: replay }));
545
+ }
546
+ } else if (entry && entry.transport === "tmux") {
547
+ void startTmuxStream(120, 32);
536
548
  }
537
549
 
538
550
  ws.on("message", async (raw) => {
@@ -553,6 +565,8 @@ function createPtyManager({ store }) {
553
565
  if (message.type === "resize") {
554
566
  const cols = Number(message.cols || 120);
555
567
  const rows = Number(message.rows || 32);
568
+ pendingCols = cols;
569
+ pendingRows = rows;
556
570
  if (runtime.transport === "pty") {
557
571
  runtime.pty.resize(cols, rows);
558
572
  } else if (!tmuxStreamStarted) {