opencode-buddy 0.2.0 → 0.2.1

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": "opencode-buddy",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A virtual ASCII pet companion for opencode. Works as a tmux sidecar or an in-TUI /buddy slash command.",
5
5
  "type": "module",
6
6
  "main": "src/plugin.js",
@@ -58,6 +58,7 @@ export function feed(state) {
58
58
  hunger: CLAMP(state.hunger + 25),
59
59
  energy: CLAMP(state.energy - 5),
60
60
  lastFed: Date.now(),
61
+ lastTick: Date.now(),
61
62
  };
62
63
  }
63
64
 
@@ -68,6 +69,7 @@ export function play(state) {
68
69
  energy: CLAMP(state.energy - 10),
69
70
  hunger: CLAMP(state.hunger - 5),
70
71
  lastPlayed: Date.now(),
72
+ lastTick: Date.now(),
71
73
  xp: state.xp + 5,
72
74
  };
73
75
  }
@@ -76,15 +78,20 @@ export function rest(state) {
76
78
  return {
77
79
  ...state,
78
80
  energy: CLAMP(state.energy + 30),
81
+ lastTick: Date.now(),
79
82
  };
80
83
  }
81
84
 
82
85
  export function rename(state, name) {
83
- return { ...state, name: String(name).slice(0, 20) };
86
+ return {
87
+ ...state,
88
+ name: String(name).slice(0, 20),
89
+ lastTick: Date.now(),
90
+ };
84
91
  }
85
92
 
86
93
  export function switchSpecies(state, species) {
87
- return { ...state, species };
94
+ return { ...state, species, lastTick: Date.now() };
88
95
  }
89
96
 
90
97
  export function celebrate(state, durationMs = 4000) {
package/src/index.js CHANGED
@@ -55,10 +55,14 @@ export async function runInteractive({ autoSplit = true } = {}) {
55
55
  let state = await loadOrInit();
56
56
  let buddyPane = null;
57
57
  if (autoSplit) {
58
- buddyPane = splitRight(
58
+ // The `start` subcommand: split a side pane that runs `render`,
59
+ // then exit so this main-pane process goes back to the shell prompt.
60
+ // The render process owns the TUI; we just kick it off.
61
+ splitRight(
59
62
  `cd ${process.cwd()} && exec ${process.execPath} ${process.argv[1]} render`,
60
63
  28,
61
64
  );
65
+ return;
62
66
  }
63
67
 
64
68
  const tui = createTui({
@@ -134,12 +138,37 @@ export async function runInteractive({ autoSplit = true } = {}) {
134
138
  let lastActivity = "unknown";
135
139
  let lastPoll = 0;
136
140
  let lastSave = 0;
141
+ let lastReload = 0;
142
+ let lastKnownMtime = 0;
137
143
  let running = true;
138
144
 
139
145
  async function loop() {
140
146
  while (running) {
141
147
  const now = Date.now();
142
148
 
149
+ // 0. Reload from disk every 1.5s so the side pane picks up
150
+ // changes made by other processes (opencode plugin, headless
151
+ // CLI invocations from the main pane, etc.). Use mtime to
152
+ // detect external writes — lastTick is not a reliable
153
+ // signal because tick() rewrites it every loop.
154
+ if (now - lastReload > 1500) {
155
+ lastReload = now;
156
+ const mt = await persistence.mtime();
157
+ if (mt > 0 && mt !== lastKnownMtime) {
158
+ lastKnownMtime = mt;
159
+ const onDisk = await persistence.load();
160
+ if (onDisk) {
161
+ // Preserve in-memory transient state overrides
162
+ const transient = {
163
+ state: state.state,
164
+ stateUntil: state.stateUntil,
165
+ stateReason: state.stateReason,
166
+ };
167
+ state = { ...onDisk, ...transient };
168
+ }
169
+ }
170
+ }
171
+
143
172
  // 1. tick attributes for time decay
144
173
  state = tick(state, now);
145
174
 
@@ -170,6 +199,7 @@ export async function runInteractive({ autoSplit = true } = {}) {
170
199
  if (now - lastSave > SAVE_EVERY_MS) {
171
200
  lastSave = now;
172
201
  await persistence.save(state).catch(() => {});
202
+ lastKnownMtime = await persistence.mtime();
173
203
  }
174
204
 
175
205
  await new Promise((r) => setTimeout(r, TICK_MS));
@@ -16,6 +16,16 @@ export async function load() {
16
16
  }
17
17
  }
18
18
 
19
+ export async function mtime() {
20
+ try {
21
+ const st = await fs.stat(FILE);
22
+ return st.mtimeMs;
23
+ } catch (err) {
24
+ if (err.code === "ENOENT") return 0;
25
+ throw err;
26
+ }
27
+ }
28
+
19
29
  export async function save(state) {
20
30
  await fs.mkdir(DIR, { recursive: true });
21
31
  const tmp = FILE + ".tmp";