@hung319/opencode-hive 1.4.0 → 1.4.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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Smart Session Title Plugin
3
+ *
4
+ * Auto-generates meaningful session titles based on conversation content.
5
+ * Based on: https://github.com/Tarquinen/opencode-smart-title
6
+ *
7
+ * Note: This is a simplified version that uses heuristics.
8
+ * Full AI-based title generation can be added later.
9
+ */
10
+ import type { Event } from '@opencode-ai/sdk';
11
+ export interface SmartTitleConfig {
12
+ enabled?: boolean;
13
+ updateThreshold?: number;
14
+ }
15
+ export declare function createSmartTitleHandler(config: SmartTitleConfig): (input: {
16
+ event: Event;
17
+ }) => Promise<void>;
18
+ export declare function clearSessionData(sessionID: string): void;
package/dist/index.js CHANGED
@@ -16266,6 +16266,267 @@ async function buildMemoryInjection(projectRoot) {
16266
16266
  return sections.join(`
16267
16267
  `);
16268
16268
  }
16269
+ function getTypedMemoryDir() {
16270
+ return path2.join(os.homedir(), ".config", "opencode", "hive", "typed-memory");
16271
+ }
16272
+ function getDeletionsFile() {
16273
+ return path2.join(getTypedMemoryDir(), "deletions.logfmt");
16274
+ }
16275
+ function parseTypedMemoryLine(line) {
16276
+ const tsMatch = line.match(/ts=([^\s]+)/);
16277
+ const typeMatch = line.match(/type=([^\s]+)/);
16278
+ const scopeMatch = line.match(/scope=([^\s]+)/);
16279
+ const contentMatch = line.match(/content="([^"]*(?:\\"[^"]*)*)"/);
16280
+ const issueMatch = line.match(/issue=([^\s]+)/);
16281
+ const tagsMatch = line.match(/tags=([^\s]+)/);
16282
+ if (!tsMatch?.[1] || !typeMatch?.[1] || !scopeMatch?.[1])
16283
+ return null;
16284
+ return {
16285
+ ts: tsMatch[1],
16286
+ type: typeMatch[1],
16287
+ scope: scopeMatch[1],
16288
+ content: contentMatch?.[1]?.replace(/\\"/g, '"') || "",
16289
+ issue: issueMatch?.[1],
16290
+ tags: tagsMatch?.[1]?.split(",")
16291
+ };
16292
+ }
16293
+ function formatTypedMemory(m) {
16294
+ const date5 = m.ts.split("T")[0];
16295
+ const tags = m.tags?.length ? ` [${m.tags.join(", ")}]` : "";
16296
+ const issue2 = m.issue ? ` (${m.issue})` : "";
16297
+ return `[${date5}] ${m.type}/${m.scope}: ${m.content}${issue2}${tags}`;
16298
+ }
16299
+ function scoreTypedMemoryMatch(memory, words) {
16300
+ const searchable = `${memory.type} ${memory.scope} ${memory.content} ${memory.tags?.join(" ") || ""}`.toLowerCase();
16301
+ let score = 0;
16302
+ for (const word of words) {
16303
+ if (searchable.includes(word))
16304
+ score++;
16305
+ if (memory.scope.toLowerCase() === word)
16306
+ score += 2;
16307
+ if (memory.type.toLowerCase() === word)
16308
+ score += 2;
16309
+ }
16310
+ return score;
16311
+ }
16312
+ async function ensureTypedMemoryDir() {
16313
+ const dir = getTypedMemoryDir();
16314
+ if (!fs2.existsSync(dir)) {
16315
+ fs2.mkdirSync(dir, { recursive: true });
16316
+ }
16317
+ }
16318
+ async function getAllTypedMemories() {
16319
+ const dir = getTypedMemoryDir();
16320
+ if (!fs2.existsSync(dir))
16321
+ return [];
16322
+ const files = fs2.readdirSync(dir).filter((f) => f.endsWith(".logfmt"));
16323
+ if (!files.length)
16324
+ return [];
16325
+ const lines = [];
16326
+ for (const filename of files) {
16327
+ if (filename === "deletions.logfmt")
16328
+ continue;
16329
+ const filepath = path2.join(dir, filename);
16330
+ const text = fs2.readFileSync(filepath, "utf-8");
16331
+ lines.push(...text.trim().split(`
16332
+ `).filter(Boolean));
16333
+ }
16334
+ return lines.map(parseTypedMemoryLine).filter((m) => m !== null);
16335
+ }
16336
+ async function findTypedMemories(scope, type, query) {
16337
+ const dir = getTypedMemoryDir();
16338
+ if (!fs2.existsSync(dir))
16339
+ return [];
16340
+ const files = fs2.readdirSync(dir).filter((f) => f.endsWith(".logfmt"));
16341
+ const matches = [];
16342
+ for (const filename of files) {
16343
+ if (filename === "deletions.logfmt")
16344
+ continue;
16345
+ const filepath = path2.join(dir, filename);
16346
+ const text = fs2.readFileSync(filepath, "utf-8");
16347
+ const lines = text.split(`
16348
+ `);
16349
+ lines.forEach((line, lineIndex) => {
16350
+ const memory = parseTypedMemoryLine(line);
16351
+ if (!memory)
16352
+ return;
16353
+ if (scope && memory.scope !== scope)
16354
+ return;
16355
+ if (type && memory.type !== type)
16356
+ return;
16357
+ if (query) {
16358
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
16359
+ if (scoreTypedMemoryMatch(memory, words) === 0)
16360
+ return;
16361
+ }
16362
+ matches.push({ filepath, lineIndex });
16363
+ });
16364
+ }
16365
+ return matches;
16366
+ }
16367
+ async function logTypedMemoryDeletion(memory, reason) {
16368
+ await ensureTypedMemoryDir();
16369
+ const ts = new Date().toISOString();
16370
+ const content = memory.content.replace(/"/g, "\\\"");
16371
+ const escapedReason = reason.replace(/"/g, "\\\"");
16372
+ const issue2 = memory.issue ? ` issue=${memory.issue}` : "";
16373
+ const tags = memory.tags?.length ? ` tags=${memory.tags.join(",")}` : "";
16374
+ const line = `ts=${ts} action=deleted original_ts=${memory.ts} type=${memory.type} scope=${memory.scope} content="${content}" reason="${escapedReason}"${issue2}${tags}
16375
+ `;
16376
+ const file2 = getDeletionsFile();
16377
+ const existing = fs2.existsSync(file2) ? fs2.readFileSync(file2, "utf-8") : "";
16378
+ fs2.writeFileSync(file2, existing + line);
16379
+ }
16380
+ var hiveMemoryRecallTool = tool({
16381
+ description: "Search typed memories by scope, type, or query. Retrieve learnings, decisions, preferences, and patterns.",
16382
+ args: {
16383
+ scope: tool.schema.string().optional().describe("Filter by scope (e.g., project, user, auth, api)"),
16384
+ type: tool.schema.enum(["decision", "learning", "preference", "blocker", "context", "pattern"]).optional().describe("Filter by memory type"),
16385
+ query: tool.schema.string().optional().describe("Search terms (matches type, scope, content)"),
16386
+ limit: tool.schema.number().optional().describe("Maximum results (default: 20)")
16387
+ },
16388
+ async execute({ scope, type, query, limit = 20 }) {
16389
+ await ensureTypedMemoryDir();
16390
+ const allMemories = await getAllTypedMemories();
16391
+ if (!allMemories.length) {
16392
+ return JSON.stringify({
16393
+ message: "No typed memories found. Use hive_memory_set to create one.",
16394
+ total: 0,
16395
+ results: []
16396
+ }, null, 2);
16397
+ }
16398
+ let results = allMemories;
16399
+ if (scope) {
16400
+ results = results.filter((m) => m.scope === scope || m.scope.includes(scope));
16401
+ }
16402
+ if (type) {
16403
+ results = results.filter((m) => m.type === type);
16404
+ }
16405
+ if (query) {
16406
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
16407
+ const scored = results.map((m) => ({ memory: m, score: scoreTypedMemoryMatch(m, words) })).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
16408
+ results = scored.map((x) => x.memory);
16409
+ }
16410
+ const totalCount = allMemories.length;
16411
+ const filteredCount = results.length;
16412
+ const limited = results.slice(0, limit);
16413
+ if (!limited.length) {
16414
+ return JSON.stringify({
16415
+ message: "No matching memories found.",
16416
+ total: totalCount,
16417
+ results: []
16418
+ }, null, 2);
16419
+ }
16420
+ return JSON.stringify({
16421
+ total: totalCount,
16422
+ filtered: filteredCount,
16423
+ returned: limited.length,
16424
+ results: limited.map((m) => ({
16425
+ type: m.type,
16426
+ scope: m.scope,
16427
+ content: m.content,
16428
+ created: m.ts,
16429
+ issue: m.issue,
16430
+ tags: m.tags,
16431
+ formatted: formatTypedMemory(m)
16432
+ }))
16433
+ }, null, 2);
16434
+ }
16435
+ });
16436
+ var hiveMemoryUpdateTool = tool({
16437
+ description: "Update a typed memory entry. Finds by scope and type, updates content.",
16438
+ args: {
16439
+ scope: tool.schema.string().describe("Scope of memory to update"),
16440
+ type: tool.schema.enum(["decision", "learning", "preference", "blocker", "context", "pattern"]).describe("Type of memory"),
16441
+ content: tool.schema.string().describe("New content"),
16442
+ query: tool.schema.string().optional().describe("If multiple matches, filter by query")
16443
+ },
16444
+ async execute({ scope, type, content, query }) {
16445
+ const matches = await findTypedMemories(scope, type);
16446
+ if (matches.length === 0) {
16447
+ return JSON.stringify({
16448
+ success: false,
16449
+ error: `No memories found for ${type} in ${scope}`
16450
+ }, null, 2);
16451
+ }
16452
+ let target = matches[0];
16453
+ if (matches.length > 1 && query) {
16454
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
16455
+ const scored = matches.map((m) => {
16456
+ const memory = parseTypedMemoryLine(fs2.readFileSync(m.filepath, "utf-8").split(`
16457
+ `)[m.lineIndex]);
16458
+ return { match: m, score: memory ? scoreTypedMemoryMatch(memory, words) : 0 };
16459
+ }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
16460
+ if (scored.length === 0) {
16461
+ return JSON.stringify({
16462
+ success: false,
16463
+ error: `Found ${matches.length} memories for ${type}/${scope}, but none matched query "${query}"`
16464
+ }, null, 2);
16465
+ }
16466
+ target = scored[0].match;
16467
+ }
16468
+ const oldLine = fs2.readFileSync(target.filepath, "utf-8").split(`
16469
+ `)[target.lineIndex];
16470
+ const oldMemory = parseTypedMemoryLine(oldLine);
16471
+ if (oldMemory) {
16472
+ await logTypedMemoryDeletion(oldMemory, `Updated to: ${content}`);
16473
+ }
16474
+ const ts = new Date().toISOString();
16475
+ const lines = fs2.readFileSync(target.filepath, "utf-8").split(`
16476
+ `);
16477
+ lines[target.lineIndex] = `ts=${ts} type=${type} scope=${scope} content="${content.replace(/"/g, "\\\"")}"`;
16478
+ fs2.writeFileSync(target.filepath, lines.join(`
16479
+ `));
16480
+ return JSON.stringify({
16481
+ success: true,
16482
+ scope,
16483
+ type,
16484
+ content,
16485
+ message: `Updated ${type} in ${scope}`
16486
+ }, null, 2);
16487
+ }
16488
+ });
16489
+ var hiveMemoryForgetTool = tool({
16490
+ description: "Delete a typed memory. Logs deletion for audit purposes.",
16491
+ args: {
16492
+ scope: tool.schema.string().describe("Scope of memory to delete"),
16493
+ type: tool.schema.enum(["decision", "learning", "preference", "blocker", "context", "pattern"]).describe("Type of memory"),
16494
+ reason: tool.schema.string().describe("Why this memory is being deleted")
16495
+ },
16496
+ async execute({ scope, type, reason }) {
16497
+ const matches = await findTypedMemories(scope, type);
16498
+ if (matches.length === 0) {
16499
+ return JSON.stringify({
16500
+ success: false,
16501
+ error: `No memories found for ${type} in ${scope}`
16502
+ }, null, 2);
16503
+ }
16504
+ let deleted = 0;
16505
+ const deletedMemories = [];
16506
+ for (const match of matches) {
16507
+ const text = fs2.readFileSync(match.filepath, "utf-8");
16508
+ const lines = text.split(`
16509
+ `);
16510
+ const memory = parseTypedMemoryLine(lines[match.lineIndex]);
16511
+ if (memory) {
16512
+ await logTypedMemoryDeletion(memory, reason);
16513
+ deletedMemories.push(memory);
16514
+ deleted++;
16515
+ }
16516
+ lines.splice(match.lineIndex, 1);
16517
+ fs2.writeFileSync(match.filepath, lines.join(`
16518
+ `));
16519
+ }
16520
+ return JSON.stringify({
16521
+ success: true,
16522
+ deleted,
16523
+ scope,
16524
+ type,
16525
+ reason,
16526
+ message: `Deleted ${deleted} ${type} memory(ies) from ${scope}. Deletion logged.`
16527
+ }, null, 2);
16528
+ }
16529
+ });
16269
16530
 
16270
16531
  // src/agents/hive.ts
16271
16532
  var QUEEN_BEE_PROMPT = `# Zetta (Hybrid)
@@ -18471,6 +18732,14 @@ var DEFAULT_HIVE_CONFIG = {
18471
18732
  includeActiveFeature: true,
18472
18733
  includePendingTasks: true,
18473
18734
  includeModifiedFiles: false
18735
+ },
18736
+ snip: {
18737
+ enabled: false,
18738
+ command: "snip"
18739
+ },
18740
+ smartTitle: {
18741
+ enabled: false,
18742
+ updateThreshold: 1
18474
18743
  }
18475
18744
  };
18476
18745
  var HIVE_DIR = ".hive";
@@ -25165,6 +25434,18 @@ No Hive skills available.` : base + formatSkillsXml(filteredSkills);
25165
25434
  }
25166
25435
  });
25167
25436
  }
25437
+ var ENV_VAR_RE = /^([A-Za-z_][A-Za-z0-9_]*=[^\s]* +)*/;
25438
+ function prefixWithSnip(command, snipCommand = "snip") {
25439
+ if (command.startsWith(`${snipCommand} `)) {
25440
+ return command;
25441
+ }
25442
+ const splitMatch = command.match(/^(.*?)( &&| [|]| ;|;)(.*)$/);
25443
+ const firstPart = splitMatch ? splitMatch[1] : command;
25444
+ const rest = splitMatch ? splitMatch[2] + splitMatch[3] : "";
25445
+ const envPrefix = (firstPart.match(ENV_VAR_RE) ?? [""])[0];
25446
+ const bareCmd = firstPart.slice(envPrefix.length).trim();
25447
+ return `${envPrefix}${snipCommand} ${bareCmd}${rest}`;
25448
+ }
25168
25449
  var plugin = async (ctx) => {
25169
25450
  const { directory, client } = ctx;
25170
25451
  const featureService = new FeatureService(directory);
@@ -25753,9 +26034,6 @@ ${snapshot}
25753
26034
  }
25754
26035
  if (input.tool !== "bash")
25755
26036
  return;
25756
- const sandboxConfig = configService.getSandboxConfig();
25757
- if (sandboxConfig.mode === "none")
25758
- return;
25759
26037
  const command = output.args?.command?.trim();
25760
26038
  if (!command)
25761
26039
  return;
@@ -25765,15 +26043,27 @@ ${snapshot}
25765
26043
  output.args.command = strippedCommand;
25766
26044
  return;
25767
26045
  }
25768
- const workdir = output.args?.workdir;
25769
- if (!workdir)
25770
- return;
25771
- const hiveWorktreeBase = path8.join(directory, ".hive", ".worktrees");
25772
- if (!workdir.startsWith(hiveWorktreeBase))
25773
- return;
25774
- const wrapped = DockerSandboxService.wrapCommand(workdir, command, sandboxConfig);
25775
- output.args.command = wrapped;
25776
- output.args.workdir = undefined;
26046
+ const snipConfig = configService.get().snip;
26047
+ let finalCommand = command;
26048
+ if (snipConfig?.enabled) {
26049
+ finalCommand = prefixWithSnip(command, snipConfig.command ?? "snip");
26050
+ }
26051
+ const sandboxConfig = configService.getSandboxConfig();
26052
+ if (sandboxConfig.mode !== "none") {
26053
+ const workdir = output.args?.workdir;
26054
+ if (workdir) {
26055
+ const hiveWorktreeBase = path8.join(directory, ".hive", ".worktrees");
26056
+ if (workdir.startsWith(hiveWorktreeBase)) {
26057
+ const wrapped = DockerSandboxService.wrapCommand(workdir, finalCommand, sandboxConfig);
26058
+ output.args.command = wrapped;
26059
+ output.args.workdir = undefined;
26060
+ return;
26061
+ }
26062
+ }
26063
+ }
26064
+ if (snipConfig?.enabled && finalCommand !== command) {
26065
+ output.args.command = finalCommand;
26066
+ }
25777
26067
  },
25778
26068
  "tool.execute.after": async (input, output) => {
25779
26069
  const truncationConfig = configService.get().tokenTruncation;
@@ -25818,6 +26108,9 @@ ${snapshot}
25818
26108
  hive_memory_list: hiveMemoryListTool,
25819
26109
  hive_memory_set: hiveMemorySetTool,
25820
26110
  hive_memory_replace: hiveMemoryReplaceTool,
26111
+ hive_memory_recall: hiveMemoryRecallTool,
26112
+ hive_memory_update: hiveMemoryUpdateTool,
26113
+ hive_memory_forget: hiveMemoryForgetTool,
25821
26114
  hive_journal_write: hiveJournalWriteTool,
25822
26115
  hive_journal_search: hiveJournalSearchTool,
25823
26116
  hive_skill: createHiveSkillTool(filteredSkills),
@@ -26722,12 +27015,24 @@ ${Object.entries(customAgentConfigs).sort(([left], [right]) => left.localeCompar
26722
27015
  allAgents["scout-researcher"] = builtInAgentConfigs["scout-researcher"];
26723
27016
  allAgents["forager-worker"] = builtInAgentConfigs["forager-worker"];
26724
27017
  allAgents["hygienic-reviewer"] = builtInAgentConfigs["hygienic-reviewer"];
27018
+ allAgents["codebase-locator"] = builtInAgentConfigs["codebase-locator"];
27019
+ allAgents["codebase-analyzer"] = builtInAgentConfigs["codebase-analyzer"];
27020
+ allAgents["pattern-finder"] = builtInAgentConfigs["pattern-finder"];
27021
+ allAgents["project-initializer"] = builtInAgentConfigs["project-initializer"];
27022
+ allAgents["code-reviewer"] = builtInAgentConfigs["code-reviewer"];
27023
+ allAgents["code-simplifier"] = builtInAgentConfigs["code-simplifier"];
26725
27024
  } else {
26726
27025
  allAgents["architect-planner"] = builtInAgentConfigs["architect-planner"];
26727
27026
  allAgents["swarm-orchestrator"] = builtInAgentConfigs["swarm-orchestrator"];
26728
27027
  allAgents["scout-researcher"] = builtInAgentConfigs["scout-researcher"];
26729
27028
  allAgents["forager-worker"] = builtInAgentConfigs["forager-worker"];
26730
27029
  allAgents["hygienic-reviewer"] = builtInAgentConfigs["hygienic-reviewer"];
27030
+ allAgents["codebase-locator"] = builtInAgentConfigs["codebase-locator"];
27031
+ allAgents["codebase-analyzer"] = builtInAgentConfigs["codebase-analyzer"];
27032
+ allAgents["pattern-finder"] = builtInAgentConfigs["pattern-finder"];
27033
+ allAgents["project-initializer"] = builtInAgentConfigs["project-initializer"];
27034
+ allAgents["code-reviewer"] = builtInAgentConfigs["code-reviewer"];
27035
+ allAgents["code-simplifier"] = builtInAgentConfigs["code-simplifier"];
26731
27036
  }
26732
27037
  Object.assign(allAgents, customSubagents);
26733
27038
  const primaryAgent = agentMode === "unified" ? "zetta" : "architect-planner";
@@ -26775,9 +27080,52 @@ ${Object.entries(customAgentConfigs).sort(([left], [right]) => left.localeCompar
26775
27080
  }
26776
27081
  }
26777
27082
  }
27083
+ },
27084
+ event: async (input) => {
27085
+ const smartTitleConfig = configService.get().smartTitle;
27086
+ if (!smartTitleConfig?.enabled)
27087
+ return;
27088
+ const { event } = input;
27089
+ if (event.type === "message.created" || event.type === "message-part.created") {
27090
+ const sessionID = event.properties?.sessionID;
27091
+ const role = event.properties?.role;
27092
+ const content = event.properties?.content;
27093
+ if (sessionID && role === "user" && content && !sessionFirstMessage.has(sessionID)) {
27094
+ sessionFirstMessage.set(sessionID, content);
27095
+ }
27096
+ }
27097
+ if (event.type === "session.idle") {
27098
+ const sessionID = event.properties?.sessionID;
27099
+ if (!sessionID)
27100
+ return;
27101
+ if (sessionID.includes("-worker-") || sessionID.includes("-sub-"))
27102
+ return;
27103
+ const currentCount = (sessionIdleCount.get(sessionID) || 0) + 1;
27104
+ sessionIdleCount.set(sessionID, currentCount);
27105
+ if (currentCount % (smartTitleConfig.updateThreshold ?? 1) !== 0)
27106
+ return;
27107
+ const firstMessage = sessionFirstMessage.get(sessionID);
27108
+ if (firstMessage) {
27109
+ const title = generateSmartTitle(firstMessage);
27110
+ console.log(`[hive:smart-title] Title for ${sessionID}: "${title}"`);
27111
+ }
27112
+ }
26778
27113
  }
26779
27114
  };
26780
27115
  };
27116
+ var sessionIdleCount = new Map;
27117
+ var sessionFirstMessage = new Map;
27118
+ function generateSmartTitle(message) {
27119
+ if (!message)
27120
+ return "Untitled session";
27121
+ let cleaned = message.trim();
27122
+ cleaned = cleaned.replace(/^(user[:\s]*|human[:\s]*|hi[,!\s]*|hello[,!\s]*|hey[,!\s]*)/i, "");
27123
+ if (cleaned.length > 50) {
27124
+ cleaned = cleaned.slice(0, 47) + "...";
27125
+ }
27126
+ cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
27127
+ return cleaned || "Untitled session";
27128
+ }
26781
27129
  var src_default = plugin;
26782
27130
  export {
26783
27131
  src_default as default
@@ -33,3 +33,15 @@ export declare const hiveMemoryReplaceTool: ToolDefinition;
33
33
  export declare const hiveJournalWriteTool: ToolDefinition;
34
34
  export declare const hiveJournalSearchTool: ToolDefinition;
35
35
  export declare function buildMemoryInjection(projectRoot: string): Promise<string>;
36
+ export type MemoryType = 'decision' | 'learning' | 'preference' | 'blocker' | 'context' | 'pattern';
37
+ export interface TypedMemory {
38
+ ts: string;
39
+ type: MemoryType;
40
+ scope: string;
41
+ content: string;
42
+ issue?: string;
43
+ tags?: string[];
44
+ }
45
+ export declare const hiveMemoryRecallTool: ToolDefinition;
46
+ export declare const hiveMemoryUpdateTool: ToolDefinition;
47
+ export declare const hiveMemoryForgetTool: ToolDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hung319/opencode-hive",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent Hive - from vibe coding to hive coding",
6
6
  "license": "MIT WITH Commons-Clause",