agent-office-cli 0.1.6 → 0.1.7
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 +1 -1
- package/src/index.js +3 -0
- package/src/runtime/index.js +4 -0
- package/src/runtime/tunnel-log.js +56 -0
- package/src/runtime/tunnel-log.test.js +43 -0
- package/src/tunnel.js +14 -9
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -202,6 +202,9 @@ async function main() {
|
|
|
202
202
|
});
|
|
203
203
|
console.log(`AgentOffice tunnel connecting to relay: ${hosted.relayUrl}`);
|
|
204
204
|
console.log(`- hosted auth: key from ${hosted.keySource}, relay from ${hosted.relaySource}`);
|
|
205
|
+
if (tunnel.logPath) {
|
|
206
|
+
console.log(`- tunnel log: ${tunnel.logPath}`);
|
|
207
|
+
}
|
|
205
208
|
tunnel.sendStatusSummary(store.listSessionSummaries());
|
|
206
209
|
|
|
207
210
|
let statusDebounceTimer = null;
|
package/src/runtime/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
} = require("./session-registry");
|
|
20
20
|
const { ensureNodePtySpawnHelper } = require("./ensure-node-pty");
|
|
21
21
|
const { startSleepInhibitor } = require("./sleep-inhibitor");
|
|
22
|
+
const { createTunnelLogger, TUNNEL_LOG_PATH, describeWebSocketClose } = require("./tunnel-log");
|
|
22
23
|
const {
|
|
23
24
|
applyClaudeHookConfig,
|
|
24
25
|
claudeSettingsPath,
|
|
@@ -47,6 +48,9 @@ module.exports = {
|
|
|
47
48
|
removeSessionRecord,
|
|
48
49
|
ensureNodePtySpawnHelper,
|
|
49
50
|
startSleepInhibitor,
|
|
51
|
+
createTunnelLogger,
|
|
52
|
+
TUNNEL_LOG_PATH,
|
|
53
|
+
describeWebSocketClose,
|
|
50
54
|
applyClaudeHookConfig,
|
|
51
55
|
claudeSettingsPath,
|
|
52
56
|
commandExists,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const os = require("node:os");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const TUNNEL_LOG_PATH = path.join(os.homedir(), ".agentoffice", "logs", "tunnel.log");
|
|
6
|
+
|
|
7
|
+
function describeWebSocketClose({ code, reason }) {
|
|
8
|
+
const parts = [];
|
|
9
|
+
|
|
10
|
+
if (typeof code === "number") {
|
|
11
|
+
parts.push(`code=${code}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (reason) {
|
|
15
|
+
parts.push(`reason=${reason}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return parts.length > 0 ? parts.join(" ") : "no close details";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createTunnelLogger({
|
|
22
|
+
logPath = TUNNEL_LOG_PATH,
|
|
23
|
+
now = () => new Date().toISOString(),
|
|
24
|
+
mkdirSync = fs.mkdirSync,
|
|
25
|
+
appendFileSync = fs.appendFileSync,
|
|
26
|
+
consoleObj = console,
|
|
27
|
+
} = {}) {
|
|
28
|
+
function write(level, message) {
|
|
29
|
+
const line = `[${now()}] [${level}] ${message}`;
|
|
30
|
+
const print = level === "error" ? consoleObj.error : consoleObj.log;
|
|
31
|
+
print.call(consoleObj, line);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
35
|
+
appendFileSync(logPath, `${line}\n`, "utf8");
|
|
36
|
+
} catch {
|
|
37
|
+
// Logging should never crash the tunnel client.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
logPath,
|
|
43
|
+
info(message) {
|
|
44
|
+
write("info", message);
|
|
45
|
+
},
|
|
46
|
+
error(message) {
|
|
47
|
+
write("error", message);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
TUNNEL_LOG_PATH,
|
|
54
|
+
createTunnelLogger,
|
|
55
|
+
describeWebSocketClose,
|
|
56
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const test = require("node:test");
|
|
2
|
+
const assert = require("node:assert/strict");
|
|
3
|
+
|
|
4
|
+
const { createTunnelLogger, describeWebSocketClose } = require("./tunnel-log");
|
|
5
|
+
|
|
6
|
+
test("describeWebSocketClose includes code and reason when present", () => {
|
|
7
|
+
assert.equal(
|
|
8
|
+
describeWebSocketClose({ code: 1006, reason: "network_reset" }),
|
|
9
|
+
"code=1006 reason=network_reset"
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("describeWebSocketClose falls back when no details are available", () => {
|
|
14
|
+
assert.equal(describeWebSocketClose({}), "no close details");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("createTunnelLogger mirrors lines to console and appends a local tunnel log", () => {
|
|
18
|
+
const writes = [];
|
|
19
|
+
const consoleLines = [];
|
|
20
|
+
|
|
21
|
+
const logger = createTunnelLogger({
|
|
22
|
+
logPath: "/tmp/agentoffice-tunnel.log",
|
|
23
|
+
now: () => "2026-03-20T08:12:00.000Z",
|
|
24
|
+
mkdirSync: () => {},
|
|
25
|
+
appendFileSync: (_path, content) => writes.push(content),
|
|
26
|
+
consoleObj: {
|
|
27
|
+
log: (line) => consoleLines.push(["log", line]),
|
|
28
|
+
error: (line) => consoleLines.push(["error", line]),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
logger.info("connected to relay");
|
|
33
|
+
logger.error("ws error: socket hang up");
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(consoleLines, [
|
|
36
|
+
["log", "[2026-03-20T08:12:00.000Z] [info] connected to relay"],
|
|
37
|
+
["error", "[2026-03-20T08:12:00.000Z] [error] ws error: socket hang up"],
|
|
38
|
+
]);
|
|
39
|
+
assert.deepEqual(writes, [
|
|
40
|
+
"[2026-03-20T08:12:00.000Z] [info] connected to relay\n",
|
|
41
|
+
"[2026-03-20T08:12:00.000Z] [error] ws error: socket hang up\n",
|
|
42
|
+
]);
|
|
43
|
+
});
|
package/src/tunnel.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { WebSocket } = require("ws");
|
|
2
2
|
const { toSessionSummary } = require("./core");
|
|
3
|
+
const { createTunnelLogger, describeWebSocketClose } = require("./runtime/tunnel-log");
|
|
3
4
|
|
|
4
5
|
const RECONNECT_BASE_MS = 1000;
|
|
5
6
|
const RECONNECT_MAX_MS = 30000;
|
|
@@ -40,7 +41,7 @@ function buildLocalRequestHeaders(headers, localServerUrl) {
|
|
|
40
41
|
return nextHeaders;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function createTunnelClient({ key, relayUrl, localServerUrl }) {
|
|
44
|
+
function createTunnelClient({ key, relayUrl, localServerUrl, logger = createTunnelLogger() }) {
|
|
44
45
|
let ws = null;
|
|
45
46
|
let reconnectDelay = RECONNECT_BASE_MS;
|
|
46
47
|
let stopped = false;
|
|
@@ -68,7 +69,7 @@ function createTunnelClient({ key, relayUrl, localServerUrl }) {
|
|
|
68
69
|
|
|
69
70
|
ws.on("open", () => {
|
|
70
71
|
reconnectDelay = RECONNECT_BASE_MS;
|
|
71
|
-
|
|
72
|
+
logger.info("[tunnel] connected to relay, authenticating...");
|
|
72
73
|
ws.send(JSON.stringify({ type: "auth", key }));
|
|
73
74
|
});
|
|
74
75
|
|
|
@@ -91,12 +92,12 @@ function createTunnelClient({ key, relayUrl, localServerUrl }) {
|
|
|
91
92
|
|
|
92
93
|
if (msg.type === "auth:ok") {
|
|
93
94
|
authenticated = true;
|
|
94
|
-
|
|
95
|
+
logger.info(`[tunnel] authenticated with relay: ${relayUrl} (userId=${msg.userId})`);
|
|
95
96
|
flushStatusSummary();
|
|
96
97
|
return;
|
|
97
98
|
}
|
|
98
99
|
if (msg.type === "auth:error") {
|
|
99
|
-
|
|
100
|
+
logger.error(`[tunnel] authentication failed: ${msg.error || "invalid key"}`);
|
|
100
101
|
stopped = true;
|
|
101
102
|
ws.close();
|
|
102
103
|
return;
|
|
@@ -108,20 +109,23 @@ function createTunnelClient({ key, relayUrl, localServerUrl }) {
|
|
|
108
109
|
|
|
109
110
|
await handleRelayMessage(msg);
|
|
110
111
|
} catch (err) {
|
|
111
|
-
|
|
112
|
+
logger.error(`[tunnel] message error: ${err.message}`);
|
|
112
113
|
}
|
|
113
114
|
});
|
|
114
115
|
|
|
115
|
-
ws.on("close", (code) => {
|
|
116
|
+
ws.on("close", (code, reasonBuffer) => {
|
|
117
|
+
const reason = Buffer.isBuffer(reasonBuffer) ? reasonBuffer.toString("utf8") : String(reasonBuffer || "");
|
|
118
|
+
const closeDetails = describeWebSocketClose({ code, reason });
|
|
116
119
|
if (stopped) {
|
|
120
|
+
logger.info(`[tunnel] stopped with close ${closeDetails}`);
|
|
117
121
|
return;
|
|
118
122
|
}
|
|
119
123
|
if (code === 4401) {
|
|
120
|
-
|
|
124
|
+
logger.error(`[tunnel] authentication rejected by relay (${closeDetails}). Not reconnecting.`);
|
|
121
125
|
stopped = true;
|
|
122
126
|
return;
|
|
123
127
|
}
|
|
124
|
-
|
|
128
|
+
logger.info(`[tunnel] disconnected (${closeDetails}). Reconnecting in ${reconnectDelay}ms...`);
|
|
125
129
|
setTimeout(() => {
|
|
126
130
|
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
127
131
|
connect();
|
|
@@ -129,7 +133,7 @@ function createTunnelClient({ key, relayUrl, localServerUrl }) {
|
|
|
129
133
|
});
|
|
130
134
|
|
|
131
135
|
ws.on("error", (err) => {
|
|
132
|
-
|
|
136
|
+
logger.error(`[tunnel] ws error: ${err.message}`);
|
|
133
137
|
});
|
|
134
138
|
}
|
|
135
139
|
|
|
@@ -250,6 +254,7 @@ function createTunnelClient({ key, relayUrl, localServerUrl }) {
|
|
|
250
254
|
connect();
|
|
251
255
|
|
|
252
256
|
return {
|
|
257
|
+
logPath: logger.logPath,
|
|
253
258
|
sendStatusSummary,
|
|
254
259
|
stop
|
|
255
260
|
};
|