@hmharness/agent 0.2.0 → 0.4.0

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/dist/i18n.d.ts CHANGED
@@ -67,6 +67,7 @@ export interface Strings {
67
67
  cmdProvidersAdded: (n: number, names: string) => string;
68
68
  tuiScrolled: string;
69
69
  tuiWebHint: string;
70
+ updateHint: (latest: string) => string;
70
71
  tuiRadarScanning: string;
71
72
  tuiEvolveDone: (proposals: number, insights: number, notes: number) => string;
72
73
  tuiPassRate: (pct: string) => string;
package/dist/i18n.js CHANGED
@@ -54,6 +54,7 @@ const zh = {
54
54
  cmdProvidersAdded: (n, names) => `已添加 ${n} 个厂商: ${names} — /model <name> 启用`,
55
55
  tuiScrolled: '↑ 已上滚 · PgDn/End/滚轮 回底',
56
56
  tuiWebHint: '浏览器界面: 在另一个终端运行 hmh web --port=7788',
57
+ updateHint: (latest) => `新版本 ${latest} 可用: npm i -g @hmharness/cli 更新`,
57
58
  tuiRadarScanning: '雷达扫描中…',
58
59
  tuiEvolveDone: (p, i, n) => `evolve 完成: ${p} 提案 · 洞察 ${i} · 记忆 ${n}`,
59
60
  tuiPassRate: (pct) => `pass rate: ${pct}`,
@@ -143,6 +144,7 @@ const en = {
143
144
  cmdProvidersAdded: (n, names) => `added ${n} providers: ${names} - enable via /model <name>`,
144
145
  tuiScrolled: '↑ scrolled up · PgDn/End/wheel back to bottom',
145
146
  tuiWebHint: 'web UI: run hmh web --port=7788 in another terminal',
147
+ updateHint: (latest) => `version ${latest} available - update with: npm i -g @hmharness/cli`,
146
148
  tuiRadarScanning: 'scanning radar…',
147
149
  tuiEvolveDone: (p, i, n) => `evolve done: ${p} proposals · ${i} insights · ${n} notes`,
148
150
  tuiPassRate: (pct) => `pass rate: ${pct}`,
package/dist/runner.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * approval gate construction.
7
7
  */
8
8
  import { Registry, type ChatMessage, type DeltaKind, type HmhConfig, type LoopApproval, type LoopResult, type McpClient, type McpServerConfig, type McpServerImport, type ToolContext } from '@hmharness/kernel';
9
+ import { type EmbeddingProvider } from '@hmharness/evolution';
9
10
  import * as readline from 'node:readline/promises';
10
11
  import { type SpawnBase } from './spawn.ts';
11
12
  /** Flatten the config.json shape into the runtime discriminated union. */
@@ -29,7 +30,10 @@ export declare function buildRegistry(opts?: {
29
30
  * P0 canary: ~20% of sessions (deterministic by session id) also receive
30
31
  * the canary skill block, watermarked as experimental references - the
31
32
  * impact loop compares these sessions against the rest. */
32
- export declare function contextPack(task: string, sessionId?: string): Promise<{
33
+ export declare function contextPack(task: string, sessionId?: string, opts?: {
34
+ workspace?: string | null;
35
+ embedding?: EmbeddingProvider;
36
+ }): Promise<{
33
37
  memory: string;
34
38
  skills: string;
35
39
  insights: string;
package/dist/runner.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * approval gate construction.
7
7
  */
8
8
  import { homeDir, loadConfig, resolveProvider, mcpServerTools, Registry, runLoop, Session, } from '@hmharness/kernel';
9
- import { appendMemory, listSkills, readInsights, readNotes, recentInsights, recordInsight, retrieveMemory, skillsToPrompt, sessionGetsCanary, canaryWatermark, listCanary } from '@hmharness/evolution';
9
+ import { appendMemory, listSkills, readInsights, readNotes, recentInsights, recordInsight, retrieveMemory, skillsToPrompt, sessionGetsCanary, canaryWatermark, listCanary, workspaceForCwd } from '@hmharness/evolution';
10
10
  import { harmonyTools } from '@hmharness/domain-harmony';
11
11
  import { opsTools } from '@hmharness/domain-ops';
12
12
  import * as readline from 'node:readline/promises';
@@ -76,10 +76,10 @@ export async function buildRegistry(opts = {}) {
76
76
  * P0 canary: ~20% of sessions (deterministic by session id) also receive
77
77
  * the canary skill block, watermarked as experimental references - the
78
78
  * impact loop compares these sessions against the rest. */
79
- export async function contextPack(task, sessionId) {
79
+ export async function contextPack(task, sessionId, opts = {}) {
80
80
  const home = homeDir();
81
81
  const [memory, skills, insights] = await Promise.all([
82
- retrieveMemory(home, task),
82
+ retrieveMemory(home, task, { workspace: opts.workspace ?? undefined, embedding: opts.embedding }),
83
83
  listSkills(home),
84
84
  recentInsights(home),
85
85
  ]);
@@ -129,9 +129,17 @@ export async function runAgentTask(opts) {
129
129
  const ctx = opts.ctx ?? { cwd: process.cwd(), home: homeDir() };
130
130
  const events = opts.events ?? {};
131
131
  const session = new Session(ctx.home, ctx.cwd, cfg.provider.model);
132
+ // workspace scoping + optional embedding hybrid for memory retrieval.
133
+ // Embeddings only when routing.embedding is EXPLICITLY set - an inherited
134
+ // chat route would 404 on /embeddings once per task for nothing.
135
+ const workspace = await workspaceForCwd(ctx.home, ctx.cwd);
136
+ const embeddingRoute = cfg.routing?.['embedding'];
137
+ const embedding = embeddingRoute && cfg.providers?.[embeddingRoute]
138
+ ? cfg.providers[embeddingRoute]
139
+ : undefined;
132
140
  // contextPack needs the session id: canary injection is deterministic
133
141
  // per-session (stable attribution), decided before the prompt is built
134
- const pack = await contextPack(opts.task, session.id);
142
+ const pack = await contextPack(opts.task, session.id, { workspace, embedding });
135
143
  const system = buildSystemPrompt({
136
144
  cwd: ctx.cwd,
137
145
  home: ctx.home,
@@ -160,13 +168,26 @@ export async function runAgentTask(opts) {
160
168
  // note, so the NEXT session starts knowing what broke this one (the
161
169
  // self-evolution loop's missing per-session feedback channel)
162
170
  const toolErrors = new Map();
171
+ // rolling digest hook: compaction-evicted tool output is distilled into a
172
+ // persistent summary note by the chat model instead of being dropped
173
+ // (failures degrade silently to the deterministic prune inside the kernel)
174
+ const chatProvider = resolveProvider(cfg, 'chat');
175
+ const { chat: chatFn } = await import('@hmharness/kernel');
176
+ const summarizeContext = async (input) => {
177
+ const r = await chatFn(chatProvider, [
178
+ { role: 'system', content: 'You compress evicted agent transcript content into a dense factual digest. Keep: what was done, key results, paths, versions, decisions, errors and their fixes. Drop: raw listings, repetition, fluff. Max 120 words. Plain text bullets, no preamble.' },
179
+ { role: 'user', content: (input.previousDigest ? `PREVIOUS DIGEST (merge, keep still-relevant facts):\n${input.previousDigest}\n\n` : '') + `NEWLY EVICTED CONTENT:\n${input.evicted.join('\n---\n').slice(0, 24_000)}` },
180
+ ]);
181
+ return r.message.content ?? '';
182
+ };
163
183
  const result = await runLoop({
164
- provider: resolveProvider(cfg, 'chat'),
184
+ provider: chatProvider,
165
185
  registry: opts.registry,
166
186
  messages,
167
187
  ctx,
168
188
  maxTurns: cfg.maxTurns,
169
189
  maxContextChars: cfg.maxContextChars,
190
+ summarizeContext,
170
191
  approval: spawnBase.current.approval,
171
192
  events: {
172
193
  onDelta: (kind, chunk) => events.onDelta?.(kind, chunk),
@@ -207,7 +228,7 @@ export async function runAgentTask(opts) {
207
228
  const last = notes.slice(-40).map((n) => n.text).join('\n');
208
229
  if (last.includes(`[self-note] tool ${name}`))
209
230
  continue;
210
- await appendMemory(ctx.home, `[self-note] tool ${name} failed ${errs.length}x in one session; samples: ${[...new Set(errs)].slice(0, 2).join(' | ')}`);
231
+ await appendMemory(ctx.home, `[self-note] tool ${name} failed ${errs.length}x in one session; samples: ${[...new Set(errs)].slice(0, 2).join(' | ')}`, workspace ?? undefined);
211
232
  }
212
233
  }
213
234
  catch {
@@ -232,7 +253,7 @@ export async function runAgentTask(opts) {
232
253
  ]);
233
254
  const lesson = (r.message.content ?? '').trim();
234
255
  if (lesson && lesson.toUpperCase() !== 'NONE' && lesson.length < 300) {
235
- await appendMemory(ctx.home, `[lesson] ${lesson}`);
256
+ await appendMemory(ctx.home, `[lesson] ${lesson}`, workspace ?? undefined);
236
257
  }
237
258
  }
238
259
  catch {
package/dist/tools.js CHANGED
@@ -528,11 +528,13 @@ export const rememberTool = {
528
528
  properties: { note: { type: 'string', description: 'the fact/lesson to remember, one line preferred' } },
529
529
  required: ['note'],
530
530
  },
531
- async execute(args) {
531
+ async execute(args, ctx) {
532
532
  try {
533
- const { appendMemory } = await import('@hmharness/evolution');
534
- await appendMemory(homeDir(), String(args.note ?? ''));
535
- return { output: 'remembered.' };
533
+ const { appendMemory, workspaceForCwd } = await import('@hmharness/evolution');
534
+ const home = ctx?.home ?? homeDir();
535
+ const ws = await workspaceForCwd(home, ctx?.cwd ?? process.cwd());
536
+ await appendMemory(home, String(args.note ?? ''), ws ?? undefined);
537
+ return { output: ws ? `remembered (workspace: ${ws}).` : 'remembered.' };
536
538
  }
537
539
  catch (err) {
538
540
  return { output: String(err), isError: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/agent",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "hmharness agent execution layer: base tools, system prompt, sub-agent spawn, and the shared task runner that frontends (cli, web) drive.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,10 +15,10 @@
15
15
  "build": "tsc -p tsconfig.build.json"
16
16
  },
17
17
  "dependencies": {
18
- "@hmharness/kernel": "0.2.0",
19
- "@hmharness/evolution": "0.2.0",
20
- "@hmharness/domain-harmony": "0.2.0",
21
- "@hmharness/domain-ops": "0.2.0"
18
+ "@hmharness/kernel": "0.4.0",
19
+ "@hmharness/evolution": "0.4.0",
20
+ "@hmharness/domain-harmony": "0.4.0",
21
+ "@hmharness/domain-ops": "0.4.0"
22
22
  },
23
23
  "files": [
24
24
  "dist"