hive-am 1.2.0 → 1.2.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/package.json +1 -1
  3. package/server/dist/history/opencode.js +74 -4
  4. package/server/dist/http/routes/agents.js +21 -0
  5. package/server/dist/providers/opencode-server.js +45 -0
  6. package/server/dist/providers/opencode.js +50 -4
  7. package/server/dist/terminals.js +10 -2
  8. package/web/.next/BUILD_ID +1 -1
  9. package/web/.next/app-build-manifest.json +52 -52
  10. package/web/.next/app-path-routes-manifest.json +7 -7
  11. package/web/.next/build-manifest.json +2 -2
  12. package/web/.next/prerender-manifest.json +50 -50
  13. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/web/.next/server/app/_not-found.html +1 -1
  15. package/web/.next/server/app/_not-found.rsc +3 -3
  16. package/web/.next/server/app/agents/[id]/page.js +1 -1
  17. package/web/.next/server/app/agents/[id]/page_client-reference-manifest.js +1 -1
  18. package/web/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  19. package/web/.next/server/app/agents.html +1 -1
  20. package/web/.next/server/app/agents.rsc +4 -4
  21. package/web/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  22. package/web/.next/server/app/connections.html +1 -1
  23. package/web/.next/server/app/connections.rsc +4 -4
  24. package/web/.next/server/app/index.html +1 -1
  25. package/web/.next/server/app/index.rsc +4 -4
  26. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/relations/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/relations.html +1 -1
  29. package/web/.next/server/app/relations.rsc +4 -4
  30. package/web/.next/server/app/resources/[id]/page_client-reference-manifest.js +1 -1
  31. package/web/.next/server/app/resources/page_client-reference-manifest.js +1 -1
  32. package/web/.next/server/app/resources.html +1 -1
  33. package/web/.next/server/app/resources.rsc +4 -4
  34. package/web/.next/server/app/schedules/page_client-reference-manifest.js +1 -1
  35. package/web/.next/server/app/schedules.html +1 -1
  36. package/web/.next/server/app/schedules.rsc +4 -4
  37. package/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  38. package/web/.next/server/app/sessions.html +1 -1
  39. package/web/.next/server/app/sessions.rsc +4 -4
  40. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  41. package/web/.next/server/app/skills.html +1 -1
  42. package/web/.next/server/app/skills.rsc +4 -4
  43. package/web/.next/server/app/types/page_client-reference-manifest.js +1 -1
  44. package/web/.next/server/app/types.html +1 -1
  45. package/web/.next/server/app/types.rsc +4 -4
  46. package/web/.next/server/app-paths-manifest.json +7 -7
  47. package/web/.next/server/chunks/274.js +1 -1
  48. package/web/.next/server/chunks/612.js +1 -1
  49. package/web/.next/server/pages/404.html +1 -1
  50. package/web/.next/server/pages/500.html +1 -1
  51. package/web/.next/server/server-reference-manifest.json +1 -1
  52. package/web/.next/static/chunks/705-c7e75806f77b880c.js +1 -0
  53. package/web/.next/static/chunks/839-4aefe6e71be6e1ef.js +1 -0
  54. package/web/.next/static/chunks/app/agents/[id]/{page-4af6c246c4ca7a97.js → page-3faf40800981a736.js} +1 -1
  55. package/web/.next/static/chunks/app/layout-99620746d028dda8.js +1 -0
  56. package/web/.next/static/css/{dd4e32c285f75776.css → b7a03c62468f4263.css} +1 -1
  57. package/web/.next/trace +29 -29
  58. package/web/.next/static/chunks/705-a16e1fe09be1ac7f.js +0 -1
  59. package/web/.next/static/chunks/839-23c5508622fae942.js +0 -1
  60. package/web/.next/static/chunks/app/layout-87ebb70bd9cb8443.js +0 -1
  61. /package/web/.next/static/{p4S3i9kvO359GW91DQiHu → 7BI__-ayGDTlOrSboD_bT}/_buildManifest.js +0 -0
  62. /package/web/.next/static/{p4S3i9kvO359GW91DQiHu → 7BI__-ayGDTlOrSboD_bT}/_ssgManifest.js +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.2.2] - 2026-10-13
10
+
11
+ ### Fixed
12
+
13
+ - OpenCode 1.x: the conversation is now read from its `message` and `part` tables, so the history no longer empties after sending a message.
14
+
15
+ ## [1.2.1] - 2026-10-13
16
+
17
+ ### Added
18
+
19
+ - OpenCode 2.x: when a turn fails (for example a full conversation) the error offers **Compact conversation**. Other versions only suggest starting a new conversation.
20
+ - Terminals: selecting text copies it, and Ctrl+Shift+C copies too (Ctrl+C only copies while text is selected).
21
+
22
+ ### Fixed
23
+
24
+ - Minimizing the terminal panel no longer breaks the scroll of a full screen program (OpenCode, Claude Code): the terminals stay mounted, and the server remembers the screen and mouse modes a program switched on so a reload restores them.
25
+ - A turn that failed (conversation full, provider blocked, timeout) no longer looks like it is still running: the timer stops, "Stop" goes away and the composer sends normally. The error now says what to do.
26
+ - OpenCode failures now show their real reason ("Go usage limit exceeded", insufficient funds, blocked response) instead of "opencode exited with code 1".
27
+ - OpenCode 1.x works again: the flags `opencode run` accepts are read from its help and unsupported ones (`--standalone`, `--server`) are not passed.
28
+ - A new conversation no longer shows "Show earlier messages" with the count of the previous one.
29
+ - The tokens per turn chart in the stats panel was drawn downwards.
30
+
9
31
  ## [1.2.0] - 2026-10-13
10
32
 
11
33
  ### Added
@@ -133,7 +155,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
133
155
  - Complete documentation in English and Spanish.
134
156
  - npm package with the `hive-am` command.
135
157
 
136
- [Unreleased]: https://github.com/hughcbscrt/hive-am/compare/v1.2.0...HEAD
158
+ [Unreleased]: https://github.com/hughcbscrt/hive-am/compare/v1.2.2...HEAD
159
+ [1.2.2]: https://github.com/hughcbscrt/hive-am/compare/v1.2.1...v1.2.2
160
+ [1.2.1]: https://github.com/hughcbscrt/hive-am/compare/v1.2.0...v1.2.1
137
161
  [1.2.0]: https://github.com/hughcbscrt/hive-am/compare/v1.1.0...v1.2.0
138
162
  [1.1.0]: https://github.com/hughcbscrt/hive-am/compare/v1.0.0...v1.1.0
139
163
  [1.0.0]: https://github.com/hughcbscrt/hive-am/releases/tag/v1.0.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hive-am",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "A colony of coding agents: orchestrators and subagents over Claude Code, OpenCode and Kiro, with real session resume, a browser UI and Telegram.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -6,13 +6,30 @@ import { estimateCost } from '../pricing.js';
6
6
  import { stripInstructions as stripPreamble } from '../providers/preamble.js';
7
7
  const DB = join(homedir(), '.local', 'share', 'opencode', 'opencode.db');
8
8
  /** Changes when the conversation changes (used to cache what is computed from it). */
9
+ /** A query that may name tables this OpenCode version does not have. */
10
+ function tryGet(db, sql, ...args) { try {
11
+ return db.prepare(sql).get(...args);
12
+ }
13
+ catch {
14
+ return undefined;
15
+ } }
16
+ function tryAll(db, sql, ...args) { try {
17
+ return db.prepare(sql).all(...args);
18
+ }
19
+ catch {
20
+ return [];
21
+ } }
9
22
  export function opencodeSig(sessionId) {
10
23
  if (!existsSync(DB))
11
24
  return null;
12
25
  const db = new Database(DB, { readonly: true, fileMustExist: true });
13
26
  try {
14
- const r = db.prepare('SELECT COUNT(*) n, COALESCE(MAX(time_created), 0) t FROM session_message WHERE session_id=?').get(sessionId);
15
- return `${r.n}:${r.t}`;
27
+ const r = tryGet(db, 'SELECT COUNT(*) n, COALESCE(MAX(time_created), 0) t FROM session_message WHERE session_id=?', sessionId);
28
+ if (r && r.n > 0)
29
+ return `${r.n}:${r.t}`;
30
+ // 1.x keeps messages and their parts apart; a part changes while a message is being written
31
+ const v = tryGet(db, 'SELECT COUNT(*) n, COALESCE(MAX(time_updated), 0) t FROM part WHERE session_id=?', sessionId);
32
+ return v ? `v1:${v.n}:${v.t}` : null;
16
33
  }
17
34
  catch {
18
35
  return null;
@@ -26,7 +43,9 @@ export function readOpencode(sessionId) {
26
43
  return [];
27
44
  const db = new Database(DB, { readonly: true, fileMustExist: true });
28
45
  try {
29
- const rows = db.prepare(`SELECT id, type, time_created, data FROM session_message WHERE session_id=? AND type IN ('user','assistant') ORDER BY seq`).all(sessionId);
46
+ const rows = tryAll(db, `SELECT id, type, time_created, data FROM session_message WHERE session_id=? AND type IN ('user','assistant') ORDER BY seq`, sessionId);
47
+ if (!rows.length)
48
+ return readOpencodeV1(db, sessionId);
30
49
  const out = [];
31
50
  for (const r of rows) {
32
51
  let d;
@@ -67,6 +86,57 @@ export function readOpencode(sessionId) {
67
86
  db.close();
68
87
  }
69
88
  }
89
+ /** OpenCode 1.x: a `message` row per turn side and its `part` rows (text, reasoning, tool...). */
90
+ function readOpencodeV1(db, sessionId) {
91
+ const msgs = tryAll(db, 'SELECT id, time_created, data FROM message WHERE session_id=? ORDER BY time_created, id', sessionId);
92
+ const parts = new Map();
93
+ for (const r of tryAll(db, 'SELECT message_id, data FROM part WHERE session_id=? ORDER BY id', sessionId)) {
94
+ let d;
95
+ try {
96
+ d = JSON.parse(r.data);
97
+ }
98
+ catch {
99
+ continue;
100
+ }
101
+ (parts.get(r.message_id) ?? parts.set(r.message_id, []).get(r.message_id)).push(d);
102
+ }
103
+ const out = [];
104
+ for (const r of msgs) {
105
+ let m;
106
+ try {
107
+ m = JSON.parse(r.data);
108
+ }
109
+ catch {
110
+ continue;
111
+ }
112
+ const ps = parts.get(r.id) ?? [];
113
+ if (m.role === 'user') {
114
+ const text = ps.filter((p) => p.type === 'text' && !p.synthetic).map((p) => p.text ?? '').join('\n');
115
+ if (text.trim())
116
+ out.push({ id: r.id, role: 'user', ts: r.time_created, blocks: [{ type: 'text', text: stripInstructions(text) }] });
117
+ continue;
118
+ }
119
+ const blocks = [];
120
+ for (const p of ps) {
121
+ if (p.type === 'text' && p.text)
122
+ blocks.push({ type: 'text', text: p.text });
123
+ else if (p.type === 'reasoning' && p.text)
124
+ blocks.push({ type: 'thinking', text: p.text });
125
+ else if (p.type === 'tool') {
126
+ const st = p.state ?? {};
127
+ blocks.push({ type: 'tool', id: p.callID ?? p.id, name: p.tool ?? 'tool', input: st.input, output: typeof st.output === 'string' ? st.output : st.error ? String(st.error) : '', error: st.status === 'error', durationMs: st.time?.end && st.time?.start ? st.time.end - st.time.start : undefined });
128
+ }
129
+ }
130
+ const tk = m.tokens;
131
+ const usage = tk ? { input: tk.input ?? 0, output: tk.output ?? 0, cacheRead: tk.cache?.read ?? 0, cacheWrite: tk.cache?.write ?? 0, reasoning: tk.reasoning ?? 0 } : undefined;
132
+ const model = m.providerID && m.modelID ? `${m.providerID}/${m.modelID}` : undefined;
133
+ const reported = typeof m.cost === 'number' && m.cost > 0 ? m.cost : undefined;
134
+ const meta = { model, usage, cost: reported ?? estimateCost(model, usage), costEstimated: reported === undefined, endTs: m.time?.completed };
135
+ if (blocks.length)
136
+ out.push({ id: r.id, role: 'assistant', ts: r.time_created, blocks, meta });
137
+ }
138
+ return out;
139
+ }
70
140
  /**
71
141
  * What the user actually typed. OpenCode stores the message wrapped in literal quotes, and the first turn
72
142
  * of a session carries the hive-am instruction preamble; neither belongs in the transcript.
@@ -83,7 +153,7 @@ export function opencodeSessionDir(sessionId) {
83
153
  return null;
84
154
  const db = new Database(DB, { readonly: true, fileMustExist: true });
85
155
  try {
86
- const r = db.prepare('SELECT directory FROM session_v2 WHERE id=?').get(sessionId);
156
+ const r = tryGet(db, 'SELECT directory FROM session_v2 WHERE id=?', sessionId) ?? tryGet(db, 'SELECT directory FROM session WHERE id=?', sessionId);
87
157
  return r?.directory ?? null;
88
158
  }
89
159
  catch {
@@ -3,6 +3,8 @@ import { basename, extname, sep } from 'node:path';
3
3
  import { agents, resolved } from '../../db.js';
4
4
  import { inboxDir, saveUploads } from '../../connections/files.js';
5
5
  import { uploadsBlock } from '../../connections/prompt.js';
6
+ import { compactOpencodeSession } from '../../providers/opencode-server.js';
7
+ import { opencodeCanCompact } from '../../providers/opencode.js';
6
8
  import { readHistory } from '../../history/index.js';
7
9
  import { liveTurn, notifyAgentsChanged, queueDepth, sendTurn, stopAgent } from '../../runtime.js';
8
10
  import { sessionStats } from '../../stats.js';
@@ -61,6 +63,25 @@ route('GET', '/api/agents/:id/inbox', ({ params, url, res }) => {
61
63
  });
62
64
  createReadStream(abs).pipe(res);
63
65
  });
66
+ // Compacting a conversation: only OpenCode 2.x can. `GET` says whether this agent can, `POST` does it and answers when it is done.
67
+ route('GET', '/api/agents/:id/compact', async ({ params }) => {
68
+ const a = agentOrFail(params[0]);
69
+ return { supported: a.provider === 'opencode' && !!a.session_id && await opencodeCanCompact() };
70
+ });
71
+ route('POST', '/api/agents/:id/compact', async ({ params }) => {
72
+ const a = agentOrFail(params[0]);
73
+ if (a.provider !== 'opencode' || !a.session_id || !(await opencodeCanCompact()))
74
+ throw bad('This conversation cannot be compacted');
75
+ if (liveTurn(a.id))
76
+ throw bad('Wait for the agent to finish before compacting');
77
+ try {
78
+ await compactOpencodeSession(resolved(a).cwd, a.session_id, new AbortController().signal);
79
+ }
80
+ catch (e) {
81
+ throw bad(e instanceof Error ? e.message : 'The compaction failed');
82
+ }
83
+ return { ok: true };
84
+ });
64
85
  route('POST', '/api/agents/:id/stop', ({ params }) => ({ stopped: stopAgent(params[0]) }));
65
86
  route('POST', '/api/agents/:id/new-session', ({ params }) => {
66
87
  const a = agentOrFail(params[0]);
@@ -162,3 +162,48 @@ export async function opencodeServer(agentId, cwd, config, signal) {
162
162
  }
163
163
  return { url: s.url, password: s.password, release };
164
164
  }
165
+ /**
166
+ * Compacts a session (OpenCode 2.x): the model summarizes the conversation so it fits again. OpenCode only admits the request and the
167
+ * work happens inside a running server, so a short-lived one stays up until the compaction finishes. Throws `Error` with OpenCode's own message when it fails.
168
+ */
169
+ export async function compactOpencodeSession(cwd, sessionId, signal) {
170
+ const password = randomBytes(18).toString('base64url');
171
+ const proc = spawn('opencode', ['serve', '--hostname', '127.0.0.1', '--port', '0'], { cwd, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env, PWD: cwd, OPENCODE_SERVER_PASSWORD: password } });
172
+ try {
173
+ const url = await new Promise((resolve, reject) => {
174
+ let out = '';
175
+ const timer = setTimeout(() => reject(new Error('opencode serve did not start in time')), START_TIMEOUT_MS);
176
+ const onData = (d) => { out = (out + d).slice(-2000); const m = /listening on (http:\/\/\S+)/i.exec(out); if (m) {
177
+ clearTimeout(timer);
178
+ resolve(m[1].replace(/\/$/, ''));
179
+ } };
180
+ proc.stdout.on('data', onData);
181
+ proc.stderr.on('data', onData);
182
+ proc.on('error', (e) => { clearTimeout(timer); reject(e); });
183
+ proc.on('exit', (c) => { clearTimeout(timer); reject(new Error(`opencode serve exited with code ${c}`)); });
184
+ });
185
+ const base = `${url}/api/session/${encodeURIComponent(sessionId)}`;
186
+ const headers = { ...auth(password), 'content-type': 'application/json' };
187
+ const admit = await fetch(`${base}/compact`, { method: 'POST', headers, body: '{}', signal });
188
+ if (!admit.ok)
189
+ throw new Error(`OpenCode refused to compact the conversation (${admit.status})`);
190
+ const id = (await admit.json())?.data?.id;
191
+ const until = Date.now() + 10 * 60_000;
192
+ while (Date.now() < until && !signal.aborted) {
193
+ await new Promise((r) => setTimeout(r, 1000));
194
+ const r = await fetch(`${base}/message/${id}`, { headers, signal });
195
+ if (!r.ok)
196
+ continue;
197
+ const m = (await r.json())?.data;
198
+ if (m?.status === 'completed')
199
+ return;
200
+ if (m?.status === 'failed')
201
+ throw new Error(String(m.error?.message ?? 'The compaction failed'));
202
+ }
203
+ throw new Error(signal.aborted ? 'Compaction cancelled' : 'The compaction took too long');
204
+ }
205
+ finally {
206
+ if (!proc.killed)
207
+ proc.kill('SIGTERM');
208
+ }
209
+ }
@@ -1,5 +1,6 @@
1
1
  import { DATA_DIR } from '../db.js';
2
2
  import { join } from 'node:path';
3
+ import { execFile } from 'node:child_process';
3
4
  const INBOX_ROOT = join(DATA_DIR, 'inbox');
4
5
  import { spawnJson } from './spawn.js';
5
6
  import { dispatchMcpConfigOpencode } from '../mcp-config.js';
@@ -26,10 +27,37 @@ export function opencodePermissions(level) {
26
27
  external_directory: { '*': 'deny', [`${INBOX_ROOT}/**`]: 'allow' },
27
28
  };
28
29
  }
30
+ /**
31
+ * OpenCode 1.x and 2.x take different flags (1.x has no `--standalone`, `--server` or maybe `--auto`; an unknown flag makes it print its help and exit 1).
32
+ * The flags `opencode run` knows are read once from its help, and only those are passed.
33
+ */
34
+ let runFlags = null;
35
+ export function opencodeRunFlags() {
36
+ runFlags ??= new Promise((resolve) => {
37
+ execFile('opencode', ['run', '--help'], { timeout: 15_000, maxBuffer: 1 << 20 }, (_err, out, errOut) => {
38
+ const found = new Set(`${out}\n${errOut}`.match(/--[a-z][a-z-]*/g) ?? []);
39
+ if (!found.has('--format'))
40
+ runFlags = null; // no readable help: try again next turn, and pass the current flags meanwhile
41
+ resolve(found.has('--format') ? found : null);
42
+ });
43
+ });
44
+ return runFlags;
45
+ }
46
+ /** The text of an `error` event: OpenCode's message, with the provider's own when it only wraps one. */
47
+ function opencodeErrorText(err) {
48
+ if (!err)
49
+ return '';
50
+ if (typeof err === 'string')
51
+ return err;
52
+ const msg = String(err.message ?? err.data?.message ?? err.name ?? '').trim();
53
+ return msg || (typeof err.type === 'string' ? err.type : '');
54
+ }
29
55
  /** OpenCode: `opencode run --format json`; parts arrive whole, so deltas are derived from per-part length. */
30
56
  export async function* runOpencode(o) {
31
57
  const { agent } = o;
32
- const args = ['run', '--format', 'json', '--thinking', '--auto'];
58
+ const flags = await opencodeRunFlags();
59
+ const has = (f) => !flags || flags.has(f);
60
+ const args = ['run', '--format', 'json', ...['--thinking', '--auto'].filter(has)];
33
61
  // Sessions created while the folder was wrong (older hive-am builds) would keep running there; start fresh instead.
34
62
  const recorded = agent.session_id ? opencodeSessionDir(agent.session_id) : null;
35
63
  const resume = agent.session_id && (!recorded || recorded === agent.cwd) ? agent.session_id : null;
@@ -48,7 +76,7 @@ export async function* runOpencode(o) {
48
76
  // started with it and keeps hive connected between turns. If that server cannot start, a private one-off server is used instead.
49
77
  let access = null;
50
78
  // Only agents answering an external chat gain enough from it (somebody is waiting for a reply and the first `channel_reply` would fail); every other agent keeps the one-off server and costs nothing between turns.
51
- if (o.mcpCaps.includes('channel') && !process.env.HIVE_AM_OPENCODE_STANDALONE) {
79
+ if (o.mcpCaps.includes('channel') && !process.env.HIVE_AM_OPENCODE_STANDALONE && has('--server')) {
52
80
  try {
53
81
  access = await opencodeServer(agent.id, agent.cwd, config, o.signal);
54
82
  }
@@ -60,8 +88,8 @@ export async function* runOpencode(o) {
60
88
  args.splice(1, 0, '--server', access.url);
61
89
  env.OPENCODE_SERVER_PASSWORD = access.password;
62
90
  }
63
- else
64
- args.splice(1, 0, '--standalone');
91
+ else if (has('--standalone'))
92
+ args.splice(1, 0, '--standalone'); // 1.x always runs its own server
65
93
  args.push(prompt);
66
94
  // Stopping a turn must really stop the model: a client that is killed would leave the server working on, so the server goes down with it.
67
95
  if (access)
@@ -69,12 +97,18 @@ export async function* runOpencode(o) {
69
97
  const seen = new Map();
70
98
  const toolStarted = new Set();
71
99
  let sawSession = false;
100
+ // With `--format json` a failure (quota, usage limit, blocked response) is an `error` event on stdout and the exit code is just 1, so the reason is kept here.
101
+ let failure = '';
72
102
  try {
73
103
  for await (const ev of spawnJson({ cmd: 'opencode', args, cwd: agent.cwd, env, signal: o.signal })) {
74
104
  if (ev.sessionID && !sawSession) {
75
105
  sawSession = true;
76
106
  yield { t: 'session', sessionId: ev.sessionID };
77
107
  }
108
+ if (ev.type === 'error') {
109
+ failure = opencodeErrorText(ev.error) || failure;
110
+ continue;
111
+ }
78
112
  const p = ev.part;
79
113
  if (!p)
80
114
  continue;
@@ -103,9 +137,21 @@ export async function* runOpencode(o) {
103
137
  }
104
138
  }
105
139
  }
140
+ if (failure)
141
+ throw new Error(failure);
142
+ }
143
+ catch (e) {
144
+ if (failure && !o.signal.aborted)
145
+ throw new Error(failure); // OpenCode's own reason says more than "exited with code 1"
146
+ throw e;
106
147
  }
107
148
  finally {
108
149
  access?.release();
109
150
  }
110
151
  yield { t: 'done', ok: true };
111
152
  }
153
+ /** Compacting a conversation needs OpenCode 2.x (the version that has `--standalone`). */
154
+ export async function opencodeCanCompact() {
155
+ const flags = await opencodeRunFlags();
156
+ return !flags || flags.has('--standalone');
157
+ }
@@ -14,6 +14,7 @@ import { containerName } from './objects/docker.js';
14
14
  * shows the same terminals with what they printed last. They end when the server stops.
15
15
  */
16
16
  const MAX_TERMINALS = 12, BUFFER = 256 * 1024;
17
+ const MODE = /\x1b\[\?([0-9;]+)([hl])/g;
17
18
  export const terminalsEnabled = () => process.env.HIVE_AM_TERMINALS !== '0';
18
19
  let lib;
19
20
  async function ptyLib() {
@@ -92,11 +93,18 @@ export async function createTerminal(spec) {
92
93
  const { cwd, file, args, title } = plan(spec);
93
94
  const env = Object.fromEntries(Object.entries(process.env).filter(([k, v]) => v !== undefined && !k.startsWith('HIVE_AM_') && k !== 'HIVE_AGENT_ID' && k !== 'HIVE_CAPS'));
94
95
  const pty = p.spawn(file, args, { name: 'xterm-256color', cols: 100, rows: 28, cwd, env: { ...env, TERM: 'xterm-256color', COLORTERM: 'truecolor' } });
95
- const t = { id: randomUUID(), title, cwd, command: [file, ...args].join(' '), created: Date.now(), cols: 100, rows: 28, pty, chunks: [], size: 0, clients: new Set() };
96
+ const t = { id: randomUUID(), title, cwd, command: [file, ...args].join(' '), created: Date.now(), cols: 100, rows: 28, pty, chunks: [], size: 0, modes: new Set(), clients: new Set() };
96
97
  terms.set(t.id, t);
97
98
  pty.onData((d) => {
98
99
  t.chunks.push(d);
99
100
  t.size += d.length;
101
+ for (const m of d.matchAll(MODE))
102
+ for (const n of m[1].split(';')) {
103
+ if (m[2] === 'h')
104
+ t.modes.add(n);
105
+ else
106
+ t.modes.delete(n);
107
+ }
100
108
  while (t.size > BUFFER && t.chunks.length > 1)
101
109
  t.size -= t.chunks.shift().length; // the oldest output goes first
102
110
  const msg = JSON.stringify({ t: 'out', d });
@@ -142,7 +150,7 @@ export function attachTerminal(ws, id) {
142
150
  t.clients.add(ws);
143
151
  ws.send(JSON.stringify({ t: 'init', title: t.title, cols: t.cols, rows: t.rows, alive: !t.exit }));
144
152
  if (t.chunks.length)
145
- ws.send(JSON.stringify({ t: 'out', d: t.chunks.join('') }));
153
+ ws.send(JSON.stringify({ t: 'out', d: [...t.modes].map((n) => `\x1b[?${n}h`).join('') + t.chunks.join('') }));
146
154
  if (t.exit)
147
155
  ws.send(JSON.stringify({ t: 'exit', code: t.exit.code }));
148
156
  ws.on('message', (raw) => {
@@ -1 +1 @@
1
- p4S3i9kvO359GW91DQiHu
1
+ 7BI__-ayGDTlOrSboD_bT
@@ -13,10 +13,19 @@
13
13
  "static/chunks/89-b194fcffe5120c44.js",
14
14
  "static/chunks/main-app-6086068e5fb5eacd.js",
15
15
  "static/css/945795da867f5250.css",
16
- "static/css/dd4e32c285f75776.css",
16
+ "static/css/b7a03c62468f4263.css",
17
17
  "static/chunks/586-f7017d585a84738d.js",
18
- "static/chunks/839-23c5508622fae942.js",
19
- "static/chunks/app/layout-87ebb70bd9cb8443.js"
18
+ "static/chunks/839-4aefe6e71be6e1ef.js",
19
+ "static/chunks/app/layout-99620746d028dda8.js"
20
+ ],
21
+ "/connections/page": [
22
+ "static/chunks/webpack-ef515e41a83a16ac.js",
23
+ "static/chunks/0937d497-114a2a3de515565e.js",
24
+ "static/chunks/89-b194fcffe5120c44.js",
25
+ "static/chunks/main-app-6086068e5fb5eacd.js",
26
+ "static/chunks/586-f7017d585a84738d.js",
27
+ "static/chunks/839-4aefe6e71be6e1ef.js",
28
+ "static/chunks/app/connections/page-0936beaf02d863c6.js"
20
29
  ],
21
30
  "/agents/[id]/page": [
22
31
  "static/chunks/webpack-ef515e41a83a16ac.js",
@@ -28,25 +37,13 @@
28
37
  "static/chunks/837-33c7480524f48577.js",
29
38
  "static/chunks/486-13e08542f3a1cbac.js",
30
39
  "static/chunks/316-26da9cc233ba7557.js",
31
- "static/chunks/839-23c5508622fae942.js",
40
+ "static/chunks/839-4aefe6e71be6e1ef.js",
32
41
  "static/chunks/517-ccaf8cc5c733cbd8.js",
33
42
  "static/chunks/350-56599dc3bf06066d.js",
34
43
  "static/chunks/291-efae53ea5f7ee92d.js",
35
44
  "static/chunks/30-9adcbe92b15a6815.js",
36
- "static/chunks/705-a16e1fe09be1ac7f.js",
37
- "static/chunks/app/agents/[id]/page-4af6c246c4ca7a97.js"
38
- ],
39
- "/relations/page": [
40
- "static/chunks/webpack-ef515e41a83a16ac.js",
41
- "static/chunks/0937d497-114a2a3de515565e.js",
42
- "static/chunks/89-b194fcffe5120c44.js",
43
- "static/chunks/main-app-6086068e5fb5eacd.js",
44
- "static/css/c4a545148b5320ec.css",
45
- "static/chunks/2b9ff8ee-969e65cc354b2150.js",
46
- "static/chunks/586-f7017d585a84738d.js",
47
- "static/chunks/562-7c2e33d838e869bf.js",
48
- "static/chunks/839-23c5508622fae942.js",
49
- "static/chunks/app/relations/page-fba25393c8688714.js"
45
+ "static/chunks/705-c7e75806f77b880c.js",
46
+ "static/chunks/app/agents/[id]/page-3faf40800981a736.js"
50
47
  ],
51
48
  "/resources/[id]/page": [
52
49
  "static/chunks/webpack-ef515e41a83a16ac.js",
@@ -58,7 +55,7 @@
58
55
  "static/chunks/837-33c7480524f48577.js",
59
56
  "static/chunks/486-13e08542f3a1cbac.js",
60
57
  "static/chunks/581-3ad5c4343dad3f50.js",
61
- "static/chunks/839-23c5508622fae942.js",
58
+ "static/chunks/839-4aefe6e71be6e1ef.js",
62
59
  "static/chunks/517-ccaf8cc5c733cbd8.js",
63
60
  "static/chunks/350-56599dc3bf06066d.js",
64
61
  "static/chunks/291-efae53ea5f7ee92d.js",
@@ -67,14 +64,14 @@
67
64
  "static/chunks/491-6b4e6201f5110dff.js",
68
65
  "static/chunks/app/resources/[id]/page-d110d89bb8b5fca7.js"
69
66
  ],
70
- "/connections/page": [
67
+ "/schedules/page": [
71
68
  "static/chunks/webpack-ef515e41a83a16ac.js",
72
69
  "static/chunks/0937d497-114a2a3de515565e.js",
73
70
  "static/chunks/89-b194fcffe5120c44.js",
74
71
  "static/chunks/main-app-6086068e5fb5eacd.js",
75
72
  "static/chunks/586-f7017d585a84738d.js",
76
- "static/chunks/839-23c5508622fae942.js",
77
- "static/chunks/app/connections/page-0936beaf02d863c6.js"
73
+ "static/chunks/839-4aefe6e71be6e1ef.js",
74
+ "static/chunks/app/schedules/page-a1124397fc760413.js"
78
75
  ],
79
76
  "/resources/page": [
80
77
  "static/chunks/webpack-ef515e41a83a16ac.js",
@@ -86,7 +83,7 @@
86
83
  "static/chunks/837-33c7480524f48577.js",
87
84
  "static/chunks/486-13e08542f3a1cbac.js",
88
85
  "static/chunks/581-3ad5c4343dad3f50.js",
89
- "static/chunks/839-23c5508622fae942.js",
86
+ "static/chunks/839-4aefe6e71be6e1ef.js",
90
87
  "static/chunks/517-ccaf8cc5c733cbd8.js",
91
88
  "static/chunks/350-56599dc3bf06066d.js",
92
89
  "static/chunks/291-efae53ea5f7ee92d.js",
@@ -95,74 +92,77 @@
95
92
  "static/chunks/491-6b4e6201f5110dff.js",
96
93
  "static/chunks/app/resources/page-bbd0b450d3f3131d.js"
97
94
  ],
98
- "/agents/page": [
95
+ "/sessions/page": [
99
96
  "static/chunks/webpack-ef515e41a83a16ac.js",
100
97
  "static/chunks/0937d497-114a2a3de515565e.js",
101
98
  "static/chunks/89-b194fcffe5120c44.js",
102
99
  "static/chunks/main-app-6086068e5fb5eacd.js",
103
100
  "static/chunks/586-f7017d585a84738d.js",
104
101
  "static/chunks/706-fa2be0f10ffebe6a.js",
105
- "static/chunks/839-23c5508622fae942.js",
106
- "static/chunks/517-ccaf8cc5c733cbd8.js",
107
- "static/chunks/app/agents/page-f4c4c80f955f3967.js"
102
+ "static/chunks/176-aeaf907a6ea5627d.js",
103
+ "static/chunks/839-4aefe6e71be6e1ef.js",
104
+ "static/chunks/350-56599dc3bf06066d.js",
105
+ "static/chunks/705-c7e75806f77b880c.js",
106
+ "static/chunks/app/sessions/page-0e322811b0cde724.js"
108
107
  ],
109
- "/page": [
108
+ "/skills/page": [
110
109
  "static/chunks/webpack-ef515e41a83a16ac.js",
111
110
  "static/chunks/0937d497-114a2a3de515565e.js",
112
111
  "static/chunks/89-b194fcffe5120c44.js",
113
112
  "static/chunks/main-app-6086068e5fb5eacd.js",
114
113
  "static/chunks/586-f7017d585a84738d.js",
115
114
  "static/chunks/706-fa2be0f10ffebe6a.js",
116
- "static/chunks/486-13e08542f3a1cbac.js",
117
- "static/chunks/839-23c5508622fae942.js",
118
- "static/chunks/517-ccaf8cc5c733cbd8.js",
119
- "static/chunks/350-56599dc3bf06066d.js",
120
- "static/chunks/291-efae53ea5f7ee92d.js",
121
- "static/chunks/844-fe55292e0701f436.js",
122
- "static/chunks/app/page-a7f292117b7d76bf.js"
115
+ "static/chunks/839-4aefe6e71be6e1ef.js",
116
+ "static/chunks/app/skills/page-22ea0ef89d9f7a13.js"
123
117
  ],
124
- "/schedules/page": [
118
+ "/types/page": [
125
119
  "static/chunks/webpack-ef515e41a83a16ac.js",
126
120
  "static/chunks/0937d497-114a2a3de515565e.js",
127
121
  "static/chunks/89-b194fcffe5120c44.js",
128
122
  "static/chunks/main-app-6086068e5fb5eacd.js",
129
123
  "static/chunks/586-f7017d585a84738d.js",
130
- "static/chunks/839-23c5508622fae942.js",
131
- "static/chunks/app/schedules/page-a1124397fc760413.js"
124
+ "static/chunks/706-fa2be0f10ffebe6a.js",
125
+ "static/chunks/839-4aefe6e71be6e1ef.js",
126
+ "static/chunks/517-ccaf8cc5c733cbd8.js",
127
+ "static/chunks/app/types/page-18c4e1db2724d41d.js"
132
128
  ],
133
- "/sessions/page": [
129
+ "/agents/page": [
134
130
  "static/chunks/webpack-ef515e41a83a16ac.js",
135
131
  "static/chunks/0937d497-114a2a3de515565e.js",
136
132
  "static/chunks/89-b194fcffe5120c44.js",
137
133
  "static/chunks/main-app-6086068e5fb5eacd.js",
138
134
  "static/chunks/586-f7017d585a84738d.js",
139
135
  "static/chunks/706-fa2be0f10ffebe6a.js",
140
- "static/chunks/176-aeaf907a6ea5627d.js",
141
- "static/chunks/839-23c5508622fae942.js",
142
- "static/chunks/350-56599dc3bf06066d.js",
143
- "static/chunks/705-a16e1fe09be1ac7f.js",
144
- "static/chunks/app/sessions/page-0e322811b0cde724.js"
136
+ "static/chunks/839-4aefe6e71be6e1ef.js",
137
+ "static/chunks/517-ccaf8cc5c733cbd8.js",
138
+ "static/chunks/app/agents/page-f4c4c80f955f3967.js"
145
139
  ],
146
- "/skills/page": [
140
+ "/page": [
147
141
  "static/chunks/webpack-ef515e41a83a16ac.js",
148
142
  "static/chunks/0937d497-114a2a3de515565e.js",
149
143
  "static/chunks/89-b194fcffe5120c44.js",
150
144
  "static/chunks/main-app-6086068e5fb5eacd.js",
151
145
  "static/chunks/586-f7017d585a84738d.js",
152
146
  "static/chunks/706-fa2be0f10ffebe6a.js",
153
- "static/chunks/839-23c5508622fae942.js",
154
- "static/chunks/app/skills/page-22ea0ef89d9f7a13.js"
147
+ "static/chunks/486-13e08542f3a1cbac.js",
148
+ "static/chunks/839-4aefe6e71be6e1ef.js",
149
+ "static/chunks/517-ccaf8cc5c733cbd8.js",
150
+ "static/chunks/350-56599dc3bf06066d.js",
151
+ "static/chunks/291-efae53ea5f7ee92d.js",
152
+ "static/chunks/844-fe55292e0701f436.js",
153
+ "static/chunks/app/page-a7f292117b7d76bf.js"
155
154
  ],
156
- "/types/page": [
155
+ "/relations/page": [
157
156
  "static/chunks/webpack-ef515e41a83a16ac.js",
158
157
  "static/chunks/0937d497-114a2a3de515565e.js",
159
158
  "static/chunks/89-b194fcffe5120c44.js",
160
159
  "static/chunks/main-app-6086068e5fb5eacd.js",
160
+ "static/css/c4a545148b5320ec.css",
161
+ "static/chunks/2b9ff8ee-969e65cc354b2150.js",
161
162
  "static/chunks/586-f7017d585a84738d.js",
162
- "static/chunks/706-fa2be0f10ffebe6a.js",
163
- "static/chunks/839-23c5508622fae942.js",
164
- "static/chunks/517-ccaf8cc5c733cbd8.js",
165
- "static/chunks/app/types/page-18c4e1db2724d41d.js"
163
+ "static/chunks/562-7c2e33d838e869bf.js",
164
+ "static/chunks/839-4aefe6e71be6e1ef.js",
165
+ "static/chunks/app/relations/page-fba25393c8688714.js"
166
166
  ]
167
167
  }
168
168
  }
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "/_not-found/page": "/_not-found",
3
- "/favicon.ico/route": "/favicon.ico",
4
3
  "/apple-icon.png/route": "/apple-icon.png",
4
+ "/favicon.ico/route": "/favicon.ico",
5
5
  "/icon.png/route": "/icon.png",
6
+ "/connections/page": "/connections",
6
7
  "/agents/[id]/page": "/agents/[id]",
7
- "/relations/page": "/relations",
8
8
  "/resources/[id]/page": "/resources/[id]",
9
- "/connections/page": "/connections",
10
- "/resources/page": "/resources",
11
- "/agents/page": "/agents",
12
- "/page": "/",
13
9
  "/schedules/page": "/schedules",
10
+ "/resources/page": "/resources",
14
11
  "/sessions/page": "/sessions",
15
12
  "/skills/page": "/skills",
16
- "/types/page": "/types"
13
+ "/types/page": "/types",
14
+ "/agents/page": "/agents",
15
+ "/page": "/",
16
+ "/relations/page": "/relations"
17
17
  }
@@ -5,8 +5,8 @@
5
5
  "devFiles": [],
6
6
  "ampDevFiles": [],
7
7
  "lowPriorityFiles": [
8
- "static/p4S3i9kvO359GW91DQiHu/_buildManifest.js",
9
- "static/p4S3i9kvO359GW91DQiHu/_ssgManifest.js"
8
+ "static/7BI__-ayGDTlOrSboD_bT/_buildManifest.js",
9
+ "static/7BI__-ayGDTlOrSboD_bT/_ssgManifest.js"
10
10
  ],
11
11
  "rootMainFiles": [
12
12
  "static/chunks/webpack-ef515e41a83a16ac.js",