@imisbahk/hive 0.1.3 → 0.1.6
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/.gitattributes +7 -0
- package/.rocket/README.md +22 -22
- package/dist/agent/agent.d.ts +7 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +139 -27
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/hive-ctx.d.ts +23 -0
- package/dist/agent/hive-ctx.d.ts.map +1 -0
- package/dist/agent/hive-ctx.js +68 -0
- package/dist/agent/hive-ctx.js.map +1 -0
- package/dist/agent/prompts.d.ts +15 -0
- package/dist/agent/prompts.d.ts.map +1 -0
- package/dist/agent/prompts.js +153 -0
- package/dist/agent/prompts.js.map +1 -0
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +788 -29
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +93 -27
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/memory.d.ts +3 -0
- package/dist/cli/commands/memory.d.ts.map +1 -0
- package/dist/cli/commands/memory.js +104 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/api-key.js +1 -1
- package/dist/providers/api-key.js.map +1 -1
- package/dist/providers/base.d.ts +1 -0
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/base.js +1 -1
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/groq.d.ts.map +1 -1
- package/dist/providers/groq.js +1 -0
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +41 -8
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +1 -0
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts +2 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +5 -3
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/resilience.d.ts +4 -0
- package/dist/providers/resilience.d.ts.map +1 -0
- package/dist/providers/resilience.js +38 -0
- package/dist/providers/resilience.js.map +1 -0
- package/dist/storage/db.d.ts +31 -2
- package/dist/storage/db.d.ts.map +1 -1
- package/dist/storage/db.js +165 -3
- package/dist/storage/db.js.map +1 -1
- package/dist/storage/schema.d.ts +12 -1
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/schema.js +29 -1
- package/dist/storage/schema.js.map +1 -1
- package/master +0 -0
- package/package.json +9 -2
- package/.github/workflows/publish.yml +0 -31
- package/.rocket/ARCHITECTURE.md +0 -7
- package/.rocket/SYMBOLS.md +0 -425
- package/001-local-first-storage.md +0 -43
- package/003-memory-architechture.md +0 -71
- package/CONTRIBUTING.md +0 -150
- package/FEATURES.md +0 -63
- package/index.md +0 -16
- package/prompts/Behaviour.md +0 -23
- package/prompts/Browser.md +0 -13
- package/prompts/Code.md +0 -12
- package/prompts/Debugging.md +0 -15
- package/prompts/Execution.md +0 -13
- package/prompts/Memory.md +0 -11
- package/prompts/Planning.md +0 -13
- package/prompts/Product.md +0 -14
- package/prompts/Review.md +0 -15
- package/prompts/Safety.md +0 -12
- package/prompts/Search.md +0 -14
- package/prompts/System.md +0 -6
- package/prompts/Tools.md +0 -14
- package/prompts/Writing.md +0 -13
- package/releases/v1/v0.1/RELEASE-NOTES.md +0 -101
- package/src/agent/agent.ts +0 -595
- package/src/agent/index.ts +0 -2
- package/src/browser/browser.ts +0 -410
- package/src/cli/commands/chat.ts +0 -864
- package/src/cli/commands/config.ts +0 -610
- package/src/cli/commands/doctor.ts +0 -655
- package/src/cli/commands/init.ts +0 -288
- package/src/cli/commands/nuke.ts +0 -64
- package/src/cli/commands/status.ts +0 -170
- package/src/cli/helpers/providerPrompts.ts +0 -192
- package/src/cli/index.ts +0 -68
- package/src/cli/theme.ts +0 -88
- package/src/cli/ui.ts +0 -127
- package/src/providers/anthropic.ts +0 -146
- package/src/providers/api-key.ts +0 -23
- package/src/providers/base.ts +0 -409
- package/src/providers/google.ts +0 -21
- package/src/providers/groq.ts +0 -21
- package/src/providers/index.ts +0 -65
- package/src/providers/mistral.ts +0 -21
- package/src/providers/ollama.ts +0 -22
- package/src/providers/openai-compatible.ts +0 -82
- package/src/providers/openai.ts +0 -21
- package/src/providers/openrouter.ts +0 -21
- package/src/providers/together.ts +0 -21
- package/src/storage/db.ts +0 -476
- package/src/storage/schema.ts +0 -116
- package/tsconfig.json +0 -51
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { stdin, stdout } from "node:process";
|
|
2
4
|
import * as readline from "node:readline";
|
|
3
5
|
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
4
7
|
import chalk from "chalk";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import fetch from "node-fetch";
|
|
9
|
+
import { RUNTIME_SYSTEM_GUARDRAILS, buildBrowserAugmentedPrompt, HiveAgent, } from "../../agent/agent.js";
|
|
10
|
+
import { initializeHiveCtxSession, } from "../../agent/hive-ctx.js";
|
|
11
|
+
import { closeHiveDatabase, deleteKnowledge, findClosestKnowledge, getPrimaryAgent, getHiveHomeDir, insertKnowledge, listKnowledge, listConversationMessages, listRecentConversations, openHiveDatabase, updateConversationTitle, clearEpisodes, } from "../../storage/db.js";
|
|
12
|
+
import { createProvider, pingProvider } from "../../providers/index.js";
|
|
13
|
+
import { renderError, renderHiveHeader, renderInfo, renderSeparator, renderSuccess, } from "../ui.js";
|
|
14
|
+
import { openPage } from "../../browser/browser.js";
|
|
9
15
|
import { runConfigKeyCommandWithOptions, runConfigModelCommandWithOptions, runConfigProviderCommandWithOptions, runConfigShowCommandWithOptions, runConfigThemeCommandWithOptions, } from "./config.js";
|
|
10
16
|
import { runStatusCommandWithOptions } from "./status.js";
|
|
11
17
|
import { getTheme } from "../theme.js";
|
|
@@ -18,6 +24,24 @@ const COMMAND_HELP_TEXT = [
|
|
|
18
24
|
"Commands:",
|
|
19
25
|
" /help show commands",
|
|
20
26
|
" /new start a new conversation",
|
|
27
|
+
" /remember <fact> save a fact",
|
|
28
|
+
" /forget <thing> delete closest fact",
|
|
29
|
+
" /pin <fact> pin fact into context",
|
|
30
|
+
" /summarize <url> summarize a web page",
|
|
31
|
+
" /tldr summarize this conversation",
|
|
32
|
+
" /recap summarize persona + knowledge",
|
|
33
|
+
" /mode <name> switch response mode",
|
|
34
|
+
" /status show mode/provider/model",
|
|
35
|
+
" /export export conversation markdown",
|
|
36
|
+
" /save <title> name this conversation",
|
|
37
|
+
" /history list recent conversations",
|
|
38
|
+
" /clear clear the screen",
|
|
39
|
+
" /think <question>think step by step",
|
|
40
|
+
" /retry resend last message",
|
|
41
|
+
" /copy copy last reply",
|
|
42
|
+
" /hive memory list show knowledge items",
|
|
43
|
+
" /hive memory clear clear episodic memory",
|
|
44
|
+
" /hive memory show show current persona",
|
|
21
45
|
" /browse <url> read a webpage",
|
|
22
46
|
" browse <url> same as /browse",
|
|
23
47
|
" /search <query> search the web",
|
|
@@ -36,6 +60,9 @@ const HIVE_SHORTCUT_HELP_TEXT = [
|
|
|
36
60
|
" /hive help list shortcuts",
|
|
37
61
|
" /hive status run hive status",
|
|
38
62
|
" /hive config show run hive config show",
|
|
63
|
+
" /hive memory list list knowledge",
|
|
64
|
+
" /hive memory clear clear episodes",
|
|
65
|
+
" /hive memory show show persona",
|
|
39
66
|
"",
|
|
40
67
|
"Interactive config commands (in chat):",
|
|
41
68
|
" /hive config provider",
|
|
@@ -53,6 +80,13 @@ const PREVIEW_AGENT_NAME = "jarvis";
|
|
|
53
80
|
const PREVIEW_PROVIDER = "google";
|
|
54
81
|
const PREVIEW_MODEL = "gemini-2.0-flash";
|
|
55
82
|
const PREVIEW_NEW_MESSAGE = "Started a new preview conversation context.";
|
|
83
|
+
const MODE_PROMPTS = {
|
|
84
|
+
default: null,
|
|
85
|
+
research: "Every answer must be grounded in current web evidence. Perform web search as needed and cite sources inline.",
|
|
86
|
+
code: "Think and respond like a focused software engineer. Prioritize concise technical answers and code.",
|
|
87
|
+
brainstorm: "Be creative and opinionated. Offer bold suggestions and push back on weak ideas when helpful.",
|
|
88
|
+
brief: "Keep every response to a maximum of 3 sentences while preserving key details.",
|
|
89
|
+
};
|
|
56
90
|
const COMMAND_SUGGESTIONS = [
|
|
57
91
|
{
|
|
58
92
|
label: "/help",
|
|
@@ -64,6 +98,81 @@ const COMMAND_SUGGESTIONS = [
|
|
|
64
98
|
insertText: "/new",
|
|
65
99
|
description: "start a new conversation",
|
|
66
100
|
},
|
|
101
|
+
{
|
|
102
|
+
label: "/remember <fact>",
|
|
103
|
+
insertText: "/remember ",
|
|
104
|
+
description: "save to knowledge graph",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: "/pin <fact>",
|
|
108
|
+
insertText: "/pin ",
|
|
109
|
+
description: "pin fact into context",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
label: "/forget <thing>",
|
|
113
|
+
insertText: "/forget ",
|
|
114
|
+
description: "delete closest fact",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: "/summarize <url>",
|
|
118
|
+
insertText: "/summarize ",
|
|
119
|
+
description: "summarize a web page",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
label: "/tldr",
|
|
123
|
+
insertText: "/tldr",
|
|
124
|
+
description: "summarize this conversation",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: "/recap",
|
|
128
|
+
insertText: "/recap",
|
|
129
|
+
description: "summarize persona & knowledge",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: "/mode <name>",
|
|
133
|
+
insertText: "/mode ",
|
|
134
|
+
description: "switch response style",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: "/export",
|
|
138
|
+
insertText: "/export",
|
|
139
|
+
description: "export conversation markdown",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: "/save <title>",
|
|
143
|
+
insertText: "/save ",
|
|
144
|
+
description: "set conversation title",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
label: "/history",
|
|
148
|
+
insertText: "/history",
|
|
149
|
+
description: "list recent conversations",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: "/status",
|
|
153
|
+
insertText: "/status",
|
|
154
|
+
description: "show session status",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
label: "/clear",
|
|
158
|
+
insertText: "/clear",
|
|
159
|
+
description: "clear the screen",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: "/think <question>",
|
|
163
|
+
insertText: "/think ",
|
|
164
|
+
description: "think step by step",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: "/retry",
|
|
168
|
+
insertText: "/retry",
|
|
169
|
+
description: "resend last message",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: "/copy",
|
|
173
|
+
insertText: "/copy",
|
|
174
|
+
description: "copy last reply",
|
|
175
|
+
},
|
|
67
176
|
{
|
|
68
177
|
label: "/browse <url>",
|
|
69
178
|
insertText: "/browse ",
|
|
@@ -94,6 +203,21 @@ const COMMAND_SUGGESTIONS = [
|
|
|
94
203
|
insertText: "/hive config show",
|
|
95
204
|
description: "run hive config show",
|
|
96
205
|
},
|
|
206
|
+
{
|
|
207
|
+
label: "/hive memory list",
|
|
208
|
+
insertText: "/hive memory list",
|
|
209
|
+
description: "list knowledge entries",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
label: "/hive memory clear",
|
|
213
|
+
insertText: "/hive memory clear",
|
|
214
|
+
description: "clear episodic memory",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
label: "/hive memory show",
|
|
218
|
+
insertText: "/hive memory show",
|
|
219
|
+
description: "show current persona",
|
|
220
|
+
},
|
|
97
221
|
{
|
|
98
222
|
label: "/hive init",
|
|
99
223
|
insertText: "/hive init",
|
|
@@ -142,6 +266,7 @@ export function registerChatCommand(program) {
|
|
|
142
266
|
export async function runChatCommand(options, context = {}) {
|
|
143
267
|
console.clear();
|
|
144
268
|
renderHiveHeader("Chat");
|
|
269
|
+
void checkForUpdates();
|
|
145
270
|
const entrypoint = context.entrypoint ?? "chat-command";
|
|
146
271
|
if (entrypoint === "chat-command") {
|
|
147
272
|
renderInfo("`hive chat` is deprecated. Run `hive`.");
|
|
@@ -159,16 +284,36 @@ export async function runChatCommand(options, context = {}) {
|
|
|
159
284
|
return;
|
|
160
285
|
}
|
|
161
286
|
let activeProfile = profile;
|
|
287
|
+
const model = options.model ?? activeProfile.model;
|
|
288
|
+
const ctxStoragePath = join(getHiveHomeDir(), "ctx");
|
|
289
|
+
mkdirSync(ctxStoragePath, { recursive: true });
|
|
290
|
+
let hiveCtx = await initializeHiveCtxSession({
|
|
291
|
+
storagePath: ctxStoragePath,
|
|
292
|
+
profile: activeProfile,
|
|
293
|
+
model,
|
|
294
|
+
});
|
|
295
|
+
if (hiveCtx.warning) {
|
|
296
|
+
console.log(chalk.dim(hiveCtx.warning));
|
|
297
|
+
}
|
|
162
298
|
let provider = await createProvider(activeProfile.provider);
|
|
163
299
|
let agent = new HiveAgent(db, provider, activeProfile);
|
|
164
300
|
let agentName = resolveAgentName(activeProfile.agent_name);
|
|
165
|
-
|
|
301
|
+
let currentMode = "default";
|
|
302
|
+
try {
|
|
303
|
+
await pingProvider(provider, model);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
renderError("✗ Provider unreachable. Run `hive doctor` to diagnose.");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
166
309
|
let conversationId = options.conversation;
|
|
167
310
|
const runOptions = {
|
|
168
311
|
model,
|
|
169
312
|
title: options.title,
|
|
170
313
|
temperature,
|
|
171
314
|
};
|
|
315
|
+
const lastUserPromptRef = { value: null };
|
|
316
|
+
const lastAssistantRef = { value: "" };
|
|
172
317
|
renderChatPreamble({
|
|
173
318
|
agentName,
|
|
174
319
|
provider: profile.provider,
|
|
@@ -176,9 +321,22 @@ export async function runChatCommand(options, context = {}) {
|
|
|
176
321
|
});
|
|
177
322
|
if (options.message) {
|
|
178
323
|
const augmentedMessage = await buildBrowserAugmentedPrompt(options.message, {
|
|
179
|
-
locationHint:
|
|
324
|
+
locationHint: activeProfile.location ?? undefined,
|
|
180
325
|
});
|
|
181
|
-
|
|
326
|
+
const systemAddition = getModeSystemPrompt(currentMode);
|
|
327
|
+
lastUserPromptRef.value = options.message;
|
|
328
|
+
const streamResult = await streamReply({
|
|
329
|
+
agent,
|
|
330
|
+
prompt: augmentedMessage,
|
|
331
|
+
rawPrompt: options.message,
|
|
332
|
+
conversationId,
|
|
333
|
+
options: runOptions,
|
|
334
|
+
agentName,
|
|
335
|
+
systemAddition,
|
|
336
|
+
hiveCtx: hiveCtx.session,
|
|
337
|
+
});
|
|
338
|
+
conversationId = streamResult.conversationId;
|
|
339
|
+
lastAssistantRef.value = streamResult.assistantText;
|
|
182
340
|
renderInfo(`conversation: ${conversationId}`);
|
|
183
341
|
return;
|
|
184
342
|
}
|
|
@@ -187,25 +345,54 @@ export async function runChatCommand(options, context = {}) {
|
|
|
187
345
|
if (prompt.length === 0) {
|
|
188
346
|
continue;
|
|
189
347
|
}
|
|
348
|
+
const normalizedPrompt = prompt.trim().toLowerCase();
|
|
190
349
|
if (prompt === "/") {
|
|
191
350
|
printChatHelp();
|
|
192
351
|
continue;
|
|
193
352
|
}
|
|
194
|
-
if (
|
|
353
|
+
if (normalizedPrompt === "/help") {
|
|
195
354
|
printChatHelp();
|
|
196
355
|
continue;
|
|
197
356
|
}
|
|
198
|
-
if (
|
|
357
|
+
if (normalizedPrompt === "/exit" || normalizedPrompt === "/quit") {
|
|
199
358
|
break;
|
|
200
359
|
}
|
|
201
|
-
if (
|
|
360
|
+
if (normalizedPrompt === "/new") {
|
|
202
361
|
conversationId = undefined;
|
|
362
|
+
currentMode = "default";
|
|
363
|
+
lastUserPromptRef.value = null;
|
|
364
|
+
lastAssistantRef.value = "";
|
|
203
365
|
renderInfo("Started a new conversation context.");
|
|
204
366
|
continue;
|
|
205
367
|
}
|
|
206
368
|
try {
|
|
369
|
+
const handled = await handleChatSlashCommand({
|
|
370
|
+
prompt,
|
|
371
|
+
db,
|
|
372
|
+
agent,
|
|
373
|
+
provider,
|
|
374
|
+
agentName,
|
|
375
|
+
conversationId,
|
|
376
|
+
activeProfilePersona: activeProfile.persona,
|
|
377
|
+
mode: currentMode,
|
|
378
|
+
providerName: activeProfile.provider,
|
|
379
|
+
modelName: runOptions.model ?? activeProfile.model,
|
|
380
|
+
setConversationId: (id) => {
|
|
381
|
+
conversationId = id;
|
|
382
|
+
},
|
|
383
|
+
setMode: (mode) => {
|
|
384
|
+
currentMode = mode;
|
|
385
|
+
},
|
|
386
|
+
lastUserPromptRef,
|
|
387
|
+
lastAssistantRef,
|
|
388
|
+
hiveCtx: hiveCtx.session,
|
|
389
|
+
});
|
|
390
|
+
if (handled) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
207
393
|
const shortcutResult = await handleHiveShortcut(prompt, {
|
|
208
394
|
allowInteractiveConfig: true,
|
|
395
|
+
db,
|
|
209
396
|
});
|
|
210
397
|
if (shortcutResult === "handled") {
|
|
211
398
|
continue;
|
|
@@ -217,29 +404,57 @@ export async function runChatCommand(options, context = {}) {
|
|
|
217
404
|
continue;
|
|
218
405
|
}
|
|
219
406
|
activeProfile = latestProfile;
|
|
407
|
+
const resolvedModel = options.model ?? activeProfile.model;
|
|
408
|
+
hiveCtx = await initializeHiveCtxSession({
|
|
409
|
+
storagePath: ctxStoragePath,
|
|
410
|
+
profile: activeProfile,
|
|
411
|
+
model: resolvedModel,
|
|
412
|
+
});
|
|
413
|
+
if (hiveCtx.warning) {
|
|
414
|
+
console.log(chalk.dim(hiveCtx.warning));
|
|
415
|
+
}
|
|
220
416
|
provider = await createProvider(activeProfile.provider);
|
|
221
417
|
agent = new HiveAgent(db, provider, activeProfile);
|
|
222
418
|
agentName = resolveAgentName(activeProfile.agent_name);
|
|
223
419
|
if (!options.model) {
|
|
224
420
|
runOptions.model = activeProfile.model;
|
|
225
421
|
}
|
|
422
|
+
try {
|
|
423
|
+
await pingProvider(provider, runOptions.model ?? activeProfile.model);
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
renderError("✗ Provider unreachable. Run `hive doctor` to diagnose.");
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
226
429
|
conversationId = undefined;
|
|
227
430
|
renderInfo(`Switched to ${activeProfile.provider} · ${runOptions.model ?? activeProfile.model}.`);
|
|
228
431
|
renderInfo("Started a new conversation context.");
|
|
229
432
|
continue;
|
|
230
433
|
}
|
|
231
434
|
if (isUnknownSlashCommand(prompt)) {
|
|
232
|
-
renderError(
|
|
233
|
-
renderInfo("Run `/help` to view supported commands.");
|
|
435
|
+
renderError("✗ Unknown command. Type /help for available commands.");
|
|
234
436
|
continue;
|
|
235
437
|
}
|
|
236
438
|
const augmentedPrompt = await buildBrowserAugmentedPrompt(prompt, {
|
|
237
|
-
locationHint:
|
|
439
|
+
locationHint: activeProfile.location ?? undefined,
|
|
440
|
+
});
|
|
441
|
+
lastUserPromptRef.value = prompt;
|
|
442
|
+
const systemAddition = getModeSystemPrompt(currentMode);
|
|
443
|
+
const streamResult = await streamReply({
|
|
444
|
+
agent,
|
|
445
|
+
prompt: augmentedPrompt,
|
|
446
|
+
rawPrompt: prompt,
|
|
447
|
+
conversationId,
|
|
448
|
+
options: runOptions,
|
|
449
|
+
agentName,
|
|
450
|
+
systemAddition,
|
|
451
|
+
hiveCtx: hiveCtx.session,
|
|
238
452
|
});
|
|
239
|
-
conversationId =
|
|
453
|
+
conversationId = streamResult.conversationId;
|
|
454
|
+
lastAssistantRef.value = streamResult.assistantText;
|
|
240
455
|
}
|
|
241
456
|
catch (error) {
|
|
242
|
-
|
|
457
|
+
renderAmberError(formatError(error));
|
|
243
458
|
}
|
|
244
459
|
}
|
|
245
460
|
}
|
|
@@ -247,28 +462,84 @@ export async function runChatCommand(options, context = {}) {
|
|
|
247
462
|
closeHiveDatabase(db);
|
|
248
463
|
}
|
|
249
464
|
}
|
|
250
|
-
async function streamReply(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
465
|
+
async function streamReply(input) {
|
|
466
|
+
const theme = getTheme();
|
|
467
|
+
process.stdout.write(theme.accent(`${input.agentName}${PROMPT_SYMBOL} `));
|
|
468
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
469
|
+
let frameIndex = 0;
|
|
470
|
+
let firstToken = false;
|
|
471
|
+
const spinner = setInterval(() => {
|
|
472
|
+
if (firstToken) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
readline.cursorTo(stdout, 0);
|
|
476
|
+
readline.clearLine(stdout, 0);
|
|
477
|
+
stdout.write(`${theme.accent(`${input.agentName}${PROMPT_SYMBOL} `)}${chalk.dim(theme.accent(frames[frameIndex]))} thinking...`);
|
|
478
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
479
|
+
}, 120);
|
|
480
|
+
let activeConversationId = input.conversationId;
|
|
481
|
+
let assistantText = "";
|
|
482
|
+
let ctxSystemPrompt;
|
|
483
|
+
let ctxTokenCount;
|
|
484
|
+
if (input.hiveCtx) {
|
|
485
|
+
const context = await input.hiveCtx.build(input.rawPrompt);
|
|
486
|
+
ctxSystemPrompt = context.system;
|
|
487
|
+
ctxTokenCount = context.tokens;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
for await (const event of input.agent.chat(input.prompt, {
|
|
491
|
+
conversationId: activeConversationId,
|
|
492
|
+
model: input.options.model,
|
|
493
|
+
temperature: input.options.temperature,
|
|
494
|
+
title: input.options.title,
|
|
495
|
+
systemAddition: input.systemAddition,
|
|
496
|
+
contextSystemPrompt: ctxSystemPrompt,
|
|
497
|
+
disableLegacyEpisodeStore: Boolean(input.hiveCtx),
|
|
498
|
+
})) {
|
|
499
|
+
if (event.type === "token") {
|
|
500
|
+
if (!firstToken) {
|
|
501
|
+
firstToken = true;
|
|
502
|
+
clearInterval(spinner);
|
|
503
|
+
readline.cursorTo(stdout, 0);
|
|
504
|
+
readline.clearLine(stdout, 0);
|
|
505
|
+
stdout.write(theme.accent(`${input.agentName}${PROMPT_SYMBOL} `));
|
|
506
|
+
}
|
|
507
|
+
process.stdout.write(event.token);
|
|
508
|
+
activeConversationId = event.conversationId;
|
|
509
|
+
assistantText += event.token;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
261
512
|
activeConversationId = event.conversationId;
|
|
262
|
-
continue;
|
|
263
513
|
}
|
|
264
|
-
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
if (!firstToken) {
|
|
517
|
+
clearInterval(spinner);
|
|
518
|
+
readline.cursorTo(stdout, 0);
|
|
519
|
+
readline.clearLine(stdout, 0);
|
|
520
|
+
stdout.write(theme.accent(`${input.agentName}${PROMPT_SYMBOL} `));
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
clearInterval(spinner);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (!firstToken) {
|
|
527
|
+
stdout.write("✗ No response received.\n");
|
|
528
|
+
renderSeparator(EXCHANGE_SEPARATOR);
|
|
529
|
+
return { conversationId: activeConversationId ?? "", assistantText };
|
|
265
530
|
}
|
|
266
531
|
process.stdout.write("\n");
|
|
267
532
|
renderSeparator(EXCHANGE_SEPARATOR);
|
|
533
|
+
if (input.hiveCtx) {
|
|
534
|
+
await Promise.resolve(input.hiveCtx.episode(input.rawPrompt, assistantText)).catch(() => { });
|
|
535
|
+
}
|
|
536
|
+
if (ctxTokenCount !== undefined) {
|
|
537
|
+
console.log(chalk.dim(`· ~${ctxTokenCount} ctx tokens`));
|
|
538
|
+
}
|
|
268
539
|
if (!activeConversationId) {
|
|
269
540
|
throw new Error("Conversation state was not returned by the agent.");
|
|
270
541
|
}
|
|
271
|
-
return activeConversationId;
|
|
542
|
+
return { conversationId: activeConversationId, assistantText };
|
|
272
543
|
}
|
|
273
544
|
function parseTemperature(raw) {
|
|
274
545
|
if (raw === undefined) {
|
|
@@ -286,6 +557,10 @@ function formatError(error) {
|
|
|
286
557
|
}
|
|
287
558
|
return String(error);
|
|
288
559
|
}
|
|
560
|
+
function renderAmberError(message) {
|
|
561
|
+
const accent = getTheme().accent;
|
|
562
|
+
console.error(accent(`✗ ${message}`));
|
|
563
|
+
}
|
|
289
564
|
function resolveAgentName(agentName) {
|
|
290
565
|
const normalized = agentName?.trim();
|
|
291
566
|
if (normalized && normalized.length > 0) {
|
|
@@ -355,6 +630,16 @@ function isHiveShortcut(prompt) {
|
|
|
355
630
|
const normalized = prompt.trim().toLowerCase();
|
|
356
631
|
return normalized === HIVE_SHORTCUT_PREFIX || normalized.startsWith(`${HIVE_SHORTCUT_PREFIX} `);
|
|
357
632
|
}
|
|
633
|
+
function getModeSystemPrompt(mode) {
|
|
634
|
+
return MODE_PROMPTS[mode] ?? undefined;
|
|
635
|
+
}
|
|
636
|
+
function combineSystemAdditions(parts) {
|
|
637
|
+
const merged = parts
|
|
638
|
+
.map((part) => part?.trim() ?? "")
|
|
639
|
+
.filter((part) => part.length > 0)
|
|
640
|
+
.join("\n\n");
|
|
641
|
+
return merged.length > 0 ? merged : undefined;
|
|
642
|
+
}
|
|
358
643
|
function isUnknownSlashCommand(prompt) {
|
|
359
644
|
const normalized = prompt.trim().toLowerCase();
|
|
360
645
|
if (!normalized.startsWith("/")) {
|
|
@@ -364,6 +649,26 @@ function isUnknownSlashCommand(prompt) {
|
|
|
364
649
|
normalized === "/new" ||
|
|
365
650
|
normalized === "/exit" ||
|
|
366
651
|
normalized === "/quit" ||
|
|
652
|
+
normalized === "/remember" ||
|
|
653
|
+
normalized.startsWith("/remember ") ||
|
|
654
|
+
normalized === "/forget" ||
|
|
655
|
+
normalized.startsWith("/forget ") ||
|
|
656
|
+
normalized === "/summarize" ||
|
|
657
|
+
normalized.startsWith("/summarize ") ||
|
|
658
|
+
normalized === "/tldr" ||
|
|
659
|
+
normalized === "/recap" ||
|
|
660
|
+
normalized === "/mode" ||
|
|
661
|
+
normalized.startsWith("/mode ") ||
|
|
662
|
+
normalized === "/export" ||
|
|
663
|
+
normalized === "/history" ||
|
|
664
|
+
normalized === "/clear" ||
|
|
665
|
+
normalized === "/think" ||
|
|
666
|
+
normalized.startsWith("/think ") ||
|
|
667
|
+
normalized.startsWith("/save") ||
|
|
668
|
+
normalized.startsWith("/pin") ||
|
|
669
|
+
normalized === "/status" ||
|
|
670
|
+
normalized === "/retry" ||
|
|
671
|
+
normalized === "/copy" ||
|
|
367
672
|
normalized === "/browse" ||
|
|
368
673
|
normalized.startsWith("/browse ") ||
|
|
369
674
|
normalized === "/search" ||
|
|
@@ -400,6 +705,52 @@ async function handleHiveShortcut(prompt, options = {}) {
|
|
|
400
705
|
restoreChatInputAfterInteractiveCommand();
|
|
401
706
|
return "handled";
|
|
402
707
|
}
|
|
708
|
+
if (subcommand === "memory list") {
|
|
709
|
+
const db = options.db;
|
|
710
|
+
if (!db) {
|
|
711
|
+
renderError("Memory commands unavailable: database not open.");
|
|
712
|
+
return "handled";
|
|
713
|
+
}
|
|
714
|
+
const rows = listKnowledge(db, { limit: 1000 });
|
|
715
|
+
if (rows.length === 0) {
|
|
716
|
+
renderInfo("No knowledge stored.");
|
|
717
|
+
return "handled";
|
|
718
|
+
}
|
|
719
|
+
rows.forEach((row, index) => {
|
|
720
|
+
const pinnedLabel = row.pinned ? " (pinned)" : "";
|
|
721
|
+
renderInfo(`${index + 1}. ${row.content}${pinnedLabel}`);
|
|
722
|
+
});
|
|
723
|
+
return "handled";
|
|
724
|
+
}
|
|
725
|
+
if (subcommand === "memory clear") {
|
|
726
|
+
const db = options.db;
|
|
727
|
+
if (!db) {
|
|
728
|
+
renderError("Memory commands unavailable: database not open.");
|
|
729
|
+
return "handled";
|
|
730
|
+
}
|
|
731
|
+
const confirmed = await promptYesNo("This will delete all episodic memories. Continue? (y/n) ");
|
|
732
|
+
if (!confirmed) {
|
|
733
|
+
renderInfo("Cancelled.");
|
|
734
|
+
return "handled";
|
|
735
|
+
}
|
|
736
|
+
clearEpisodes(db);
|
|
737
|
+
renderSuccess("Episodes cleared.");
|
|
738
|
+
return "handled";
|
|
739
|
+
}
|
|
740
|
+
if (subcommand === "memory show") {
|
|
741
|
+
const db = options.db;
|
|
742
|
+
if (!db) {
|
|
743
|
+
renderError("Memory commands unavailable: database not open.");
|
|
744
|
+
return "handled";
|
|
745
|
+
}
|
|
746
|
+
const agent = getPrimaryAgent(db);
|
|
747
|
+
if (!agent) {
|
|
748
|
+
renderError("Hive is not initialized. Run `hive init` first.");
|
|
749
|
+
return "handled";
|
|
750
|
+
}
|
|
751
|
+
renderInfo(agent.persona);
|
|
752
|
+
return "handled";
|
|
753
|
+
}
|
|
403
754
|
if (subcommand === "config provider") {
|
|
404
755
|
if (!options.allowInteractiveConfig) {
|
|
405
756
|
renderInfo("Interactive config commands are unavailable here.");
|
|
@@ -445,6 +796,335 @@ async function handleHiveShortcut(prompt, options = {}) {
|
|
|
445
796
|
renderInfo("Use `/hive help` to list available shortcuts.");
|
|
446
797
|
return "handled";
|
|
447
798
|
}
|
|
799
|
+
async function handleChatSlashCommand(input) {
|
|
800
|
+
const normalized = input.prompt.trim();
|
|
801
|
+
const lower = normalized.toLowerCase();
|
|
802
|
+
if (!lower.startsWith("/")) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
if (lower === "/clear") {
|
|
806
|
+
console.clear();
|
|
807
|
+
renderHiveHeader("Chat");
|
|
808
|
+
renderChatPreamble({
|
|
809
|
+
agentName: input.agentName,
|
|
810
|
+
provider: input.providerName,
|
|
811
|
+
model: input.modelName,
|
|
812
|
+
});
|
|
813
|
+
input.lastUserPromptRef.value = null;
|
|
814
|
+
input.lastAssistantRef.value = "";
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
if (lower.startsWith("/remember")) {
|
|
818
|
+
const fact = normalized.slice("/remember".length).trim();
|
|
819
|
+
if (fact.length === 0) {
|
|
820
|
+
renderError("Usage: /remember <fact>");
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
if (input.hiveCtx) {
|
|
824
|
+
await input.hiveCtx.remember(fact);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
insertKnowledge(input.db, { content: fact });
|
|
828
|
+
}
|
|
829
|
+
renderSuccess("✓ Remembered.");
|
|
830
|
+
input.lastUserPromptRef.value = null;
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
if (lower.startsWith("/forget")) {
|
|
834
|
+
const query = normalized.slice("/forget".length).trim();
|
|
835
|
+
if (query.length === 0) {
|
|
836
|
+
renderError("Usage: /forget <thing>");
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
const match = findClosestKnowledge(input.db, query);
|
|
840
|
+
if (!match) {
|
|
841
|
+
renderError("No similar knowledge found.");
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
const confirmed = await promptYesNo(`Forget "${match.content}"? (y/n) `);
|
|
845
|
+
if (!confirmed) {
|
|
846
|
+
renderInfo("Kept.");
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
deleteKnowledge(input.db, match.id);
|
|
850
|
+
renderSuccess("✓ Forgotten.");
|
|
851
|
+
input.lastUserPromptRef.value = null;
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
if (lower.startsWith("/mode")) {
|
|
855
|
+
const modeName = normalized.slice("/mode".length).trim().toLowerCase();
|
|
856
|
+
if (!modeName || !Object.hasOwn(MODE_PROMPTS, modeName)) {
|
|
857
|
+
renderError("Usage: /mode <default|research|code|brainstorm|brief>");
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
input.setMode(modeName);
|
|
861
|
+
renderSuccess(`✓ Mode set to ${modeName}.`);
|
|
862
|
+
input.lastUserPromptRef.value = null;
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
if (lower.startsWith("/pin")) {
|
|
866
|
+
const fact = normalized.slice("/pin".length).trim();
|
|
867
|
+
if (fact.length === 0) {
|
|
868
|
+
renderError("Usage: /pin <fact>");
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
if (input.hiveCtx) {
|
|
872
|
+
await input.hiveCtx.remember(fact, { pinned: true });
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
insertKnowledge(input.db, { content: fact, pinned: true });
|
|
876
|
+
}
|
|
877
|
+
renderSuccess("✓ Pinned.");
|
|
878
|
+
input.lastUserPromptRef.value = null;
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
if (lower === "/export") {
|
|
882
|
+
if (!input.conversationId) {
|
|
883
|
+
renderError("No conversation to export. Start chatting first.");
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
const messages = listConversationMessages(input.db, input.conversationId);
|
|
887
|
+
const exportDir = join(getHiveHomeDir(), "exports");
|
|
888
|
+
mkdirSync(exportDir, { recursive: true });
|
|
889
|
+
const exportPath = join(exportDir, `${input.conversationId}.md`);
|
|
890
|
+
writeFileSync(exportPath, formatConversationMarkdown(messages, input.conversationId));
|
|
891
|
+
renderSuccess(`✓ Exported to ${exportPath}`);
|
|
892
|
+
input.lastUserPromptRef.value = null;
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
if (lower === "/history") {
|
|
896
|
+
const rows = listRecentConversations(input.db, 10);
|
|
897
|
+
if (rows.length === 0) {
|
|
898
|
+
renderInfo("No past conversations found.");
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
901
|
+
rows.forEach((row, index) => {
|
|
902
|
+
const title = row.title?.trim().length ? row.title : "(untitled)";
|
|
903
|
+
renderInfo(`${index + 1}. ${title} · ${row.updated_at} · ${row.message_count} messages`);
|
|
904
|
+
});
|
|
905
|
+
const answer = await promptLine("Pick a conversation number (or blank to cancel): ");
|
|
906
|
+
if (!answer) {
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
const choice = Number.parseInt(answer, 10);
|
|
910
|
+
if (Number.isNaN(choice) || choice < 1 || choice > rows.length) {
|
|
911
|
+
renderError("Invalid selection.");
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
const selected = rows[choice - 1];
|
|
915
|
+
input.setConversationId(selected.id);
|
|
916
|
+
renderInfo(`Continuing conversation ${selected.title ?? selected.id}.`);
|
|
917
|
+
input.lastUserPromptRef.value = null;
|
|
918
|
+
input.lastAssistantRef.value = "";
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
if (lower === "/tldr") {
|
|
922
|
+
if (!input.conversationId) {
|
|
923
|
+
renderError("No conversation yet. Say something first.");
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
const history = listConversationMessages(input.db, input.conversationId);
|
|
927
|
+
if (history.length === 0) {
|
|
928
|
+
renderError("Nothing to summarize yet.");
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
await streamEphemeral({
|
|
932
|
+
provider: input.provider,
|
|
933
|
+
agentName: input.agentName,
|
|
934
|
+
model: input.modelName,
|
|
935
|
+
messages: buildEphemeralMessages({
|
|
936
|
+
persona: input.activeProfilePersona,
|
|
937
|
+
mode: input.mode,
|
|
938
|
+
systemInstruction: "Summarize this conversation in 3-5 bullet points.",
|
|
939
|
+
history,
|
|
940
|
+
}),
|
|
941
|
+
});
|
|
942
|
+
input.lastUserPromptRef.value = null;
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
if (lower === "/recap") {
|
|
946
|
+
const knowledge = listKnowledge(input.db, { limit: 500 });
|
|
947
|
+
await streamEphemeral({
|
|
948
|
+
provider: input.provider,
|
|
949
|
+
agentName: input.agentName,
|
|
950
|
+
model: input.modelName,
|
|
951
|
+
messages: buildRecapMessages({
|
|
952
|
+
persona: input.activeProfilePersona,
|
|
953
|
+
knowledge,
|
|
954
|
+
mode: input.mode,
|
|
955
|
+
}),
|
|
956
|
+
});
|
|
957
|
+
input.lastUserPromptRef.value = null;
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
if (lower.startsWith("/summarize")) {
|
|
961
|
+
const url = normalized.slice("/summarize".length).trim();
|
|
962
|
+
if (url.length === 0) {
|
|
963
|
+
renderError("Usage: /summarize <url>");
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
966
|
+
try {
|
|
967
|
+
const content = await openPage(url);
|
|
968
|
+
await streamEphemeral({
|
|
969
|
+
provider: input.provider,
|
|
970
|
+
agentName: input.agentName,
|
|
971
|
+
model: input.modelName,
|
|
972
|
+
messages: buildEphemeralMessages({
|
|
973
|
+
persona: input.activeProfilePersona,
|
|
974
|
+
mode: input.mode,
|
|
975
|
+
userMessage: `Summarize this page concisely:\n\n${content}`,
|
|
976
|
+
}),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
catch (error) {
|
|
980
|
+
renderError(`Unable to summarize page: ${formatError(error)}`);
|
|
981
|
+
}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
if (lower.startsWith("/save")) {
|
|
985
|
+
const title = normalized.slice("/save".length).trim();
|
|
986
|
+
if (!input.conversationId) {
|
|
987
|
+
renderError("No active conversation to title.");
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
if (title.length === 0) {
|
|
991
|
+
renderError("Usage: /save <title>");
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
updateConversationTitle(input.db, input.conversationId, title);
|
|
995
|
+
renderSuccess(`✓ Saved title "${title}".`);
|
|
996
|
+
input.lastUserPromptRef.value = null;
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
if (lower.startsWith("/think")) {
|
|
1000
|
+
const question = normalized.slice("/think".length).trim();
|
|
1001
|
+
if (question.length === 0) {
|
|
1002
|
+
renderError("Usage: /think <question>");
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
await streamEphemeral({
|
|
1006
|
+
provider: input.provider,
|
|
1007
|
+
agentName: input.agentName,
|
|
1008
|
+
model: input.modelName,
|
|
1009
|
+
messages: buildEphemeralMessages({
|
|
1010
|
+
persona: input.activeProfilePersona,
|
|
1011
|
+
mode: input.mode,
|
|
1012
|
+
userMessage: `Think through this step by step, show your reasoning:\n\n${question}`,
|
|
1013
|
+
}),
|
|
1014
|
+
});
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
if (lower === "/status") {
|
|
1018
|
+
const info = [
|
|
1019
|
+
`mode=${input.mode}`,
|
|
1020
|
+
`provider=${input.providerName}`,
|
|
1021
|
+
`model=${input.modelName}`,
|
|
1022
|
+
`conversation=${input.conversationId ?? "none"}`,
|
|
1023
|
+
].join(" · ");
|
|
1024
|
+
renderInfo(info);
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
if (lower === "/retry") {
|
|
1028
|
+
const userPrompt = input.lastUserPromptRef.value;
|
|
1029
|
+
if (!userPrompt) {
|
|
1030
|
+
renderError("Nothing to retry yet.");
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
const systemAddition = getModeSystemPrompt(input.mode);
|
|
1034
|
+
const retryResult = await streamReply({
|
|
1035
|
+
agent: input.agent,
|
|
1036
|
+
prompt: userPrompt,
|
|
1037
|
+
rawPrompt: userPrompt,
|
|
1038
|
+
conversationId: input.conversationId,
|
|
1039
|
+
options: { model: input.modelName },
|
|
1040
|
+
agentName: input.agentName,
|
|
1041
|
+
systemAddition,
|
|
1042
|
+
hiveCtx: input.hiveCtx,
|
|
1043
|
+
});
|
|
1044
|
+
input.setConversationId(retryResult.conversationId);
|
|
1045
|
+
input.lastAssistantRef.value = retryResult.assistantText;
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
if (lower === "/copy") {
|
|
1049
|
+
if (!input.lastAssistantRef.value) {
|
|
1050
|
+
renderError("Nothing to copy yet.");
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
const copied = copyToClipboard(input.lastAssistantRef.value);
|
|
1054
|
+
if (copied) {
|
|
1055
|
+
renderSuccess("✓ Copied last reply to clipboard.");
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
renderError("Clipboard tool not available.");
|
|
1059
|
+
}
|
|
1060
|
+
return true;
|
|
1061
|
+
}
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
function copyToClipboard(text) {
|
|
1065
|
+
const platform = process.platform;
|
|
1066
|
+
const buffer = Buffer.from(text, "utf8");
|
|
1067
|
+
if (platform === "darwin") {
|
|
1068
|
+
const result = spawnSync("pbcopy", [], { input: buffer });
|
|
1069
|
+
return result.status === 0;
|
|
1070
|
+
}
|
|
1071
|
+
const result = spawnSync("xclip", ["-selection", "clipboard"], { input: buffer });
|
|
1072
|
+
return result.status === 0;
|
|
1073
|
+
}
|
|
1074
|
+
let cachedLocalVersion = null;
|
|
1075
|
+
async function checkForUpdates() {
|
|
1076
|
+
try {
|
|
1077
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3000));
|
|
1078
|
+
const latest = (await Promise.race([
|
|
1079
|
+
fetch("https://registry.npmjs.org/@imisbahk/hive/latest").then((response) => response.json()),
|
|
1080
|
+
timeout,
|
|
1081
|
+
]));
|
|
1082
|
+
if (!latest?.version || typeof latest.version !== "string") {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const localVersion = getLocalVersion();
|
|
1086
|
+
if (isVersionNewer(latest.version, localVersion)) {
|
|
1087
|
+
const amber = chalk.hex("#ffbf00");
|
|
1088
|
+
console.log(amber.dim(`✦ Update available v${localVersion} → npm update -g @imisbahk/hive`));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
catch {
|
|
1092
|
+
// Silently ignore update check failures.
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function getLocalVersion() {
|
|
1096
|
+
if (cachedLocalVersion) {
|
|
1097
|
+
return cachedLocalVersion;
|
|
1098
|
+
}
|
|
1099
|
+
try {
|
|
1100
|
+
const raw = readFileSync(new URL("../../../package.json", import.meta.url), "utf8");
|
|
1101
|
+
const parsed = JSON.parse(raw);
|
|
1102
|
+
if (parsed.version) {
|
|
1103
|
+
cachedLocalVersion = parsed.version;
|
|
1104
|
+
return cachedLocalVersion;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
catch {
|
|
1108
|
+
// ignore
|
|
1109
|
+
}
|
|
1110
|
+
cachedLocalVersion = "0.0.0";
|
|
1111
|
+
return cachedLocalVersion;
|
|
1112
|
+
}
|
|
1113
|
+
function isVersionNewer(remote, local) {
|
|
1114
|
+
const toNumbers = (value) => value.split(".").map((part) => Number.parseInt(part, 10));
|
|
1115
|
+
const r = toNumbers(remote);
|
|
1116
|
+
const l = toNumbers(local);
|
|
1117
|
+
const length = Math.max(r.length, l.length);
|
|
1118
|
+
for (let index = 0; index < length; index += 1) {
|
|
1119
|
+
const rv = r[index] ?? 0;
|
|
1120
|
+
const lv = l[index] ?? 0;
|
|
1121
|
+
if (rv > lv)
|
|
1122
|
+
return true;
|
|
1123
|
+
if (rv < lv)
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
448
1128
|
function getCommandSuggestions(input) {
|
|
449
1129
|
const normalized = input.trimStart().toLowerCase();
|
|
450
1130
|
if (!normalized.startsWith("/")) {
|
|
@@ -456,6 +1136,85 @@ function getCommandSuggestions(input) {
|
|
|
456
1136
|
suggestion.label.toLowerCase().includes(normalized.slice(1)));
|
|
457
1137
|
return [...prefixMatches, ...fallbackMatches];
|
|
458
1138
|
}
|
|
1139
|
+
async function promptLine(question) {
|
|
1140
|
+
const rl = createInterface({
|
|
1141
|
+
input: stdin,
|
|
1142
|
+
output: stdout,
|
|
1143
|
+
terminal: true,
|
|
1144
|
+
});
|
|
1145
|
+
try {
|
|
1146
|
+
return (await rl.question(question)).trim();
|
|
1147
|
+
}
|
|
1148
|
+
finally {
|
|
1149
|
+
rl.close();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
async function promptYesNo(question) {
|
|
1153
|
+
const answer = (await promptLine(question)).toLowerCase();
|
|
1154
|
+
return answer === "y" || answer === "yes";
|
|
1155
|
+
}
|
|
1156
|
+
async function streamEphemeral(input) {
|
|
1157
|
+
process.stdout.write(getTheme().accent(`${input.agentName}${PROMPT_SYMBOL} `));
|
|
1158
|
+
let hadOutput = false;
|
|
1159
|
+
for await (const token of input.provider.streamChat({
|
|
1160
|
+
model: input.model ?? input.provider.defaultModel,
|
|
1161
|
+
messages: input.messages,
|
|
1162
|
+
})) {
|
|
1163
|
+
hadOutput = true;
|
|
1164
|
+
process.stdout.write(token);
|
|
1165
|
+
}
|
|
1166
|
+
if (!hadOutput) {
|
|
1167
|
+
process.stdout.write("(no response)");
|
|
1168
|
+
}
|
|
1169
|
+
process.stdout.write("\n");
|
|
1170
|
+
renderSeparator(EXCHANGE_SEPARATOR);
|
|
1171
|
+
}
|
|
1172
|
+
function buildEphemeralMessages(input) {
|
|
1173
|
+
const messages = [
|
|
1174
|
+
{ role: "system", content: RUNTIME_SYSTEM_GUARDRAILS },
|
|
1175
|
+
{ role: "system", content: input.persona },
|
|
1176
|
+
];
|
|
1177
|
+
const modePrompt = getModeSystemPrompt(input.mode);
|
|
1178
|
+
if (modePrompt) {
|
|
1179
|
+
messages.push({ role: "system", content: modePrompt });
|
|
1180
|
+
}
|
|
1181
|
+
if (input.systemInstruction) {
|
|
1182
|
+
messages.push({ role: "system", content: input.systemInstruction });
|
|
1183
|
+
}
|
|
1184
|
+
if (input.history) {
|
|
1185
|
+
messages.push(...input.history.map((message) => ({
|
|
1186
|
+
role: message.role,
|
|
1187
|
+
content: message.content,
|
|
1188
|
+
})));
|
|
1189
|
+
}
|
|
1190
|
+
if (input.userMessage) {
|
|
1191
|
+
messages.push({ role: "user", content: input.userMessage });
|
|
1192
|
+
}
|
|
1193
|
+
return messages;
|
|
1194
|
+
}
|
|
1195
|
+
function buildRecapMessages(input) {
|
|
1196
|
+
const knowledgeLines = input.knowledge.length > 0
|
|
1197
|
+
? input.knowledge.map((row) => `- ${row.content}`).join("\n")
|
|
1198
|
+
: "No knowledge stored yet.";
|
|
1199
|
+
const userMessage = `Summarize everything you know about the user based on persona and knowledge facts below. Be concise.\n\nPersona:\n${input.persona}\n\nKnowledge facts:\n${knowledgeLines}`;
|
|
1200
|
+
return buildEphemeralMessages({
|
|
1201
|
+
persona: input.persona,
|
|
1202
|
+
mode: input.mode,
|
|
1203
|
+
userMessage,
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
function formatConversationMarkdown(messages, conversationId) {
|
|
1207
|
+
const lines = [`# Conversation ${conversationId}`, ""];
|
|
1208
|
+
for (const message of messages) {
|
|
1209
|
+
const speaker = message.role === "user"
|
|
1210
|
+
? "User"
|
|
1211
|
+
: message.role === "assistant"
|
|
1212
|
+
? "Hive"
|
|
1213
|
+
: "System";
|
|
1214
|
+
lines.push(`**${speaker}:**`, message.content, "");
|
|
1215
|
+
}
|
|
1216
|
+
return lines.join("\n");
|
|
1217
|
+
}
|
|
459
1218
|
async function readPromptWithSuggestions() {
|
|
460
1219
|
const accent = getTheme().accent;
|
|
461
1220
|
const promptPrefix = accent(USER_PROMPT);
|