@agentmeshhq/agent 0.1.16 → 0.1.17

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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * OpenCode Session ID Utilities
3
+ *
4
+ * Parses OpenCode log files to extract the active session ID (ses_XXXXX).
5
+ * Used for native session resumption via `opencode --session <id> --continue`.
6
+ */
7
+
8
+ import fs from "node:fs";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+
12
+ const SESSION_ID_PATTERN = /service=session\s+id=(ses_[A-Za-z0-9]+)/;
13
+
14
+ function getLogDir(agentName: string): string {
15
+ return path.join(os.homedir(), ".agentmesh", "opencode-data", agentName, "opencode", "log");
16
+ }
17
+
18
+ /**
19
+ * Gets the latest OpenCode session ID from agent log files.
20
+ *
21
+ * Parses log files in ~/.agentmesh/opencode-data/<agentName>/opencode/log/
22
+ * for the pattern: `service=session id=ses_XXXXX ... created`
23
+ *
24
+ * @returns The session ID string (e.g. "ses_365c3ec5bffeAV7qWirhNr1sU9") or null
25
+ */
26
+ export function getLatestSessionId(agentName: string): string | null {
27
+ const logDir = getLogDir(agentName);
28
+
29
+ if (!fs.existsSync(logDir)) {
30
+ return null;
31
+ }
32
+
33
+ try {
34
+ // List log files sorted by name (they're timestamped, so newest last)
35
+ const logFiles = fs
36
+ .readdirSync(logDir)
37
+ .filter((f) => f.endsWith(".log"))
38
+ .sort();
39
+
40
+ if (logFiles.length === 0) {
41
+ return null;
42
+ }
43
+
44
+ // Search from newest log file backwards
45
+ for (let i = logFiles.length - 1; i >= 0; i--) {
46
+ const logPath = path.join(logDir, logFiles[i]);
47
+ const content = fs.readFileSync(logPath, "utf-8");
48
+
49
+ // Find all session creation lines and take the last one
50
+ const lines = content.split("\n");
51
+ let lastSessionId: string | null = null;
52
+
53
+ for (const line of lines) {
54
+ if (line.includes("service=session") && line.includes("created")) {
55
+ const match = line.match(SESSION_ID_PATTERN);
56
+ if (match) {
57
+ lastSessionId = match[1];
58
+ }
59
+ }
60
+ }
61
+
62
+ if (lastSessionId) {
63
+ return lastSessionId;
64
+ }
65
+ }
66
+
67
+ return null;
68
+ } catch (error) {
69
+ console.error(`[SESSION-ID] Failed to parse logs for ${agentName}:`, error);
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Waits for OpenCode to write a NEW session ID to logs (different from `previousId`).
76
+ * Polls log files up to `maxWaitMs` with `intervalMs` between checks.
77
+ *
78
+ * This solves the race condition where we read logs before OpenCode has started
79
+ * and see the old session ID, falsely concluding resume succeeded.
80
+ *
81
+ * @returns The new session ID, or null if timeout or no new session appeared
82
+ */
83
+ export async function waitForNewSessionId(
84
+ agentName: string,
85
+ previousId: string | null,
86
+ maxWaitMs = 15000,
87
+ intervalMs = 1000,
88
+ ): Promise<string | null> {
89
+ const deadline = Date.now() + maxWaitMs;
90
+
91
+ while (Date.now() < deadline) {
92
+ const currentId = getLatestSessionId(agentName);
93
+
94
+ // If we see a different session ID than before, OpenCode has started
95
+ if (currentId && currentId !== previousId) {
96
+ return currentId;
97
+ }
98
+
99
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ /**
106
+ * Snapshots the latest session ID BEFORE starting OpenCode.
107
+ * Used to detect whether OpenCode created a new session or resumed the requested one.
108
+ */
109
+ export function snapshotSessionId(agentName: string): string | null {
110
+ return getLatestSessionId(agentName);
111
+ }
package/src/core/tmux.ts CHANGED
@@ -31,6 +31,7 @@ export function createSession(
31
31
  command: string,
32
32
  workdir?: string,
33
33
  env?: SessionEnv,
34
+ opencodeSessionId?: string,
34
35
  ): boolean {
35
36
  const sessionName = getSessionName(agentName);
36
37
 
@@ -92,6 +93,17 @@ export function createSession(
92
93
  }
93
94
  }
94
95
 
96
+ // Append --session --continue flags for native session resume
97
+ if (
98
+ opencodeSessionId &&
99
+ (finalCommand === "opencode" || finalCommand.startsWith("opencode ")) &&
100
+ !finalCommand.includes("--session") &&
101
+ !finalCommand.includes("--continue")
102
+ ) {
103
+ finalCommand = `${finalCommand} --session ${opencodeSessionId} --continue`;
104
+ console.log(`[TMUX] Resuming OpenCode session: ${opencodeSessionId}`);
105
+ }
106
+
95
107
  const fullCommand = `${envPrefix}${finalCommand}`;
96
108
 
97
109
  // Set reasonable terminal size for TUI applications
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import { execSync, spawnSync } from "node:child_process";
9
- import fs from "node:fs";
10
9
  import { captureSessionOutput } from "./tmux.js";
11
10
 
12
11
  export type WatchdogStatus = "active" | "idle" | "stuck" | "permission_blocked";
@@ -139,12 +138,8 @@ export function getLastActivityTime(agentName: string, containerName?: string):
139
138
  }
140
139
  logLine = result.stdout.trim();
141
140
  } else {
142
- // Non-sandbox mode: read from agent-isolated log directory
143
- // Each agent has its own XDG_DATA_HOME at ~/.agentmesh/opencode-data/<agent>/
144
- const agentLogDir = `${process.env.HOME}/.agentmesh/opencode-data/${agentName}/opencode/log`;
145
- const sharedLogDir = `${process.env.HOME}/.local/share/opencode/log`;
146
- // Prefer agent-specific logs, fall back to shared dir for backwards compatibility
147
- const logDir = fs.existsSync(agentLogDir) ? agentLogDir : sharedLogDir;
141
+ // Non-sandbox mode: read from local logs
142
+ const logDir = `${process.env.HOME}/.local/share/opencode/log`;
148
143
  const result = spawnSync(
149
144
  "sh",
150
145
  ["-c", `ls -t ${logDir}/*.log 2>/dev/null | head -1 | xargs tail -1 2>/dev/null`],