@hmharness/agent 0.14.7 → 0.14.9

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
@@ -56,6 +56,34 @@ export interface Strings {
56
56
  thought: string;
57
57
  /** busy spinner text, shown in the input-box status line (TUI) */
58
58
  tuiRunning: string;
59
+ /** M2: Ctrl+T full-transcript overlay title (B5) */
60
+ tuiTranscript: string;
61
+ /** M2: Ctrl+R history-search modal (B4) */
62
+ histSearchTitle: string;
63
+ histSearchNone: string;
64
+ histSearchHint: string;
65
+ /** M2: @-file palette hint (B2) */
66
+ atPalHint: string;
67
+ /** M2: transcript-overlay / pager footer hint (B5/B9) */
68
+ overlayHint: string;
69
+ /** M2: Esc-Esc fork-edit armed hint (T13) */
70
+ forkArmed: string;
71
+ cmdCompact: string;
72
+ cmdDiff: string;
73
+ cmdNew: string;
74
+ cmdNewDone: string;
75
+ cmdFork: string;
76
+ cmdCopy: string;
77
+ cmdCopied: string;
78
+ cmdPlan: string;
79
+ cmdPlanOn: string;
80
+ cmdPlanOff: string;
81
+ cmdGoal: string;
82
+ cmdGoalCleared: string;
83
+ cmdUsage: string;
84
+ cmdStatusline: string;
85
+ cmdKeymap: string;
86
+ cmdReview: string;
59
87
  cmdModelHint: string;
60
88
  /** palette footer key hint: how to drive the slash/model picker (TUI) */
61
89
  panelHint: string;
@@ -74,6 +102,10 @@ export interface Strings {
74
102
  cmdResumeNone: string;
75
103
  cmdResumeNotFound: (p: string) => string;
76
104
  cmdResumeLoaded: (n: number) => string;
105
+ /** banner shown when /resume actually loads a transcript into the TUI */
106
+ cmdResuming: (id: string, total: number, hidden: number) => string;
107
+ /** why tools-required cases are skipped inside /evolve (TUI runs a lite loop) */
108
+ cmdEvolveHint: string;
77
109
  /** Codex-style resume picker (v0.8.2 transplant) */
78
110
  pickerTitle: string;
79
111
  pickerHint1: string;
package/dist/i18n.js CHANGED
@@ -40,11 +40,34 @@ const zh = {
40
40
  tuiApprove: '批准',
41
41
  tuiDeny: '拒绝',
42
42
  tuiApprovalHint: 'enter/y 批准 · esc/n 拒绝',
43
- tuiHints: 'enter 发送 · 滚轮/↑↓ 翻页 · ^P/^N 历史 · esc 清空 · 拖选复制 · ^C 退出 · /help',
44
- tuiBusyHints: (queued) => `运行中 · 空行回车=停止 · 新输入=排队(${queued}) · esc 清空 · /help`,
43
+ tuiHints: 'enter 发送 · Esc 中断/清空 · Ctrl+Enter 注入 · Ctrl+R 历史搜索 · Ctrl+T 转录 · ^P/^N 历史 · esc 清空 · ^C 退出 · /help',
44
+ tuiBusyHints: (queued) => `运行中 · Esc=停止 · 输入=排队(${queued}) · Ctrl+Enter=注入 · /help`,
45
45
  thinking: '思考中',
46
46
  thought: '已完成思考',
47
47
  tuiRunning: '运行中…',
48
+ tuiTranscript: '完整转录(含工具输出)',
49
+ histSearchTitle: '历史搜索',
50
+ histSearchNone: '(无匹配)',
51
+ histSearchHint: '↑↓ 选择 · Enter 采用 · Esc 取消',
52
+ atPalHint: '↑↓ 选择 · Enter 插入路径 · Esc 取消',
53
+ overlayHint: '↑↓/PgUp/PgDn 滚动 · g/G 首尾 · q/Esc 返回',
54
+ forkArmed: '再按 Esc:编辑上一条并 fork',
55
+ cmdCompact: '压缩上下文并报告释放量',
56
+ cmdDiff: '工作区 git diff(含未跟踪文件)的 pager 查看',
57
+ cmdNew: '新会话(清转录,开新 rollout)',
58
+ cmdNewDone: '✓ 新会话开始(旧转录可用 Ctrl+T 回看)',
59
+ cmdFork: 'fork 当前会话为新线程(继承全部上下文)',
60
+ cmdCopy: '复制最后一条 AI 输出到剪贴板',
61
+ cmdCopied: '✓ 已复制最后一条 AI 输出',
62
+ cmdPlan: '计划模式开关(先出计划待确认再执行)',
63
+ cmdPlanOn: '🔥 计划模式:先出计划,确认后才执行',
64
+ cmdPlanOff: '计划模式关闭',
65
+ cmdGoal: '设置/查看/清除会话目标(/goal <text>|clear)',
66
+ cmdGoalCleared: '目标已清除',
67
+ cmdUsage: '本次会话 token 用量与上下文占用',
68
+ cmdStatusline: '自定义底栏字段(/statusline "{model} · {cwd}")',
69
+ cmdKeymap: '查看/重映射动作键(/keymap <action>=<key>)',
70
+ cmdReview: '一键发起「审查当前工作树」任务',
48
71
  cmdModelHint: '切换 chat 路由: /model <name>',
49
72
  panelHint: '↑↓/滚轮/点击 选择 · Enter 确认 · Esc 关闭',
50
73
  cmdModelPreset: (name, envVar) => `${name} 尚未配置 — 设置 ${envVar} 后运行 /providers scan 添加`,
@@ -60,6 +83,9 @@ const zh = {
60
83
  cmdResumeNone: '还没有历史会话',
61
84
  cmdResumeNotFound: (p) => `没有匹配 "${p}" 的会话`,
62
85
  cmdResumeLoaded: (n) => `已恢复会话(${n} 条消息),直接输入内容继续对话`,
86
+ cmdResuming: (id, total, hidden) => '--- 已恢复会话 ' + id + ' · ' + total + ' 条消息'
87
+ + (hidden > 0 ? '(前 ' + hidden + ' 条保留在上下文中,未显示)' : '') + ' ---',
88
+ cmdEvolveHint: '(TUI 内跳过;运行 hmh evolve 执行完整循环)',
63
89
  pickerTitle: '恢复会话',
64
90
  pickerHint1: 'Enter 恢复 · ↑↓ 浏览 · Tab 切筛选/排序 · ←→ 调整',
65
91
  pickerHint2: 'Esc 关闭 · 输入即过滤(标题/id/目录/分支)',
@@ -150,11 +176,34 @@ const en = {
150
176
  tuiApprove: 'Approve',
151
177
  tuiDeny: 'Deny',
152
178
  tuiApprovalHint: 'enter/y approve · esc/n deny',
153
- tuiHints: 'enter send · wheel/arrows scroll · ^P/^N history · esc clear · drag to copy · ^C quit · /help',
154
- tuiBusyHints: (queued) => `running · Enter on empty line = stop · typing queues (${queued}) · esc clear · /help`,
179
+ tuiHints: 'enter send · Esc interrupt/clear · Ctrl+Enter inject · Ctrl+R history · Ctrl+T transcript · ^P/^N history · esc clear · ^C quit · /help',
180
+ tuiBusyHints: (queued) => `running · Esc=stop · typing queues (${queued}) · Ctrl+Enter=inject · /help`,
155
181
  thinking: 'thinking',
156
182
  thought: 'thought (done)',
157
183
  tuiRunning: 'running…',
184
+ tuiTranscript: 'full transcript (incl. tool outputs)',
185
+ histSearchTitle: 'history search',
186
+ histSearchNone: '(no match)',
187
+ histSearchHint: '↑↓ select · Enter adopt · Esc close',
188
+ atPalHint: '↑↓ select · Enter insert path · Esc close',
189
+ overlayHint: '↑↓/PgUp/PgDn scroll · g/G top/bottom · q/Esc back',
190
+ forkArmed: 'Esc again: edit last message and fork',
191
+ cmdCompact: 'compact the context and report freed space',
192
+ cmdDiff: 'pager-view the worktree git diff (incl. untracked)',
193
+ cmdNew: 'new session (clear transcript, fresh rollout)',
194
+ cmdNewDone: '✓ new session (old transcript: Ctrl+T to replay)',
195
+ cmdFork: 'fork this session to a new thread (inherits full context)',
196
+ cmdCopy: 'copy the last AI output to the clipboard',
197
+ cmdCopied: '✓ copied the last AI output',
198
+ cmdPlan: 'plan mode toggle (plan first, confirm, then execute)',
199
+ cmdPlanOn: '🔥 plan mode: present a plan, wait for confirmation before executing',
200
+ cmdPlanOff: 'plan mode off',
201
+ cmdGoal: 'set/view/clear the session goal (/goal <text>|clear)',
202
+ cmdGoalCleared: 'goal cleared',
203
+ cmdUsage: 'session token usage and context occupancy',
204
+ cmdStatusline: 'custom bottom-line fields (/statusline "{model} · {cwd}")',
205
+ cmdKeymap: 'view/remap action keys (/keymap <action>=<key>)',
206
+ cmdReview: 'one-click "review the current worktree" task',
158
207
  cmdModelHint: 'switch chat route: /model <name>',
159
208
  panelHint: '↑↓ / wheel / click to select · Enter confirms · Esc closes',
160
209
  cmdModelPreset: (name, envVar) => `${name} not configured - set ${envVar}, then run /providers scan`,
@@ -170,6 +219,9 @@ const en = {
170
219
  cmdResumeNone: 'no past sessions yet',
171
220
  cmdResumeNotFound: (p) => `no session matches "${p}"`,
172
221
  cmdResumeLoaded: (n) => `session resumed (${n} messages) - just type to continue`,
222
+ cmdResuming: (id, total, hidden) => '--- resumed ' + id + ' · ' + total + ' messages'
223
+ + (hidden > 0 ? ' (' + hidden + ' earlier kept in context, not shown)' : '') + ' ---',
224
+ cmdEvolveHint: '(skipped in tui; run hmh evolve)',
173
225
  pickerTitle: 'Resume session',
174
226
  pickerHint1: 'Enter resume · ↑↓ browse · Tab filter/sort · ←→ change',
175
227
  pickerHint2: 'Esc close · type to filter (title/id/dir/branch)',
package/dist/prompt.d.ts CHANGED
@@ -7,4 +7,5 @@ export declare function buildSystemPrompt(opts: {
7
7
  model: string;
8
8
  locale?: string;
9
9
  agentsMd?: string;
10
+ goal?: string;
10
11
  }): string;
package/dist/prompt.js CHANGED
@@ -14,7 +14,12 @@ export function buildSystemPrompt(opts) {
14
14
  // ---- [DeepSeek] 分段执行 + 合理质疑 ----
15
15
  '', 'For tasks requiring reasoning across multiple steps, work in phases: search and list the key facts, plan the concrete steps, then execute. Do not skip planning to act directly.', 'If the user\'s requested approach carries risk or there is a clearly better alternative, raise it proactively - state the tradeoffs and let the user choose. Only execute confirmed plans.',
16
16
  // ---- [Codex] 用户反馈 ----
17
- '', "When the user gives feedback saying something didn't work, treat it as ground truth. Use available tools to verify the current state, identify the root cause, fix it, and explain what changed.",
17
+ '', "When the user gives feedback saying something didn't work, treat it as ground truth. Use available tools to verify the current state, identify the root cause, fix it, and explain what changed.");
18
+ // ---- 会话目标 ----
19
+ if (opts.goal) {
20
+ parts.push('', '## Active goal', 'Work toward this goal across turns until it is complete or explicitly cleared:', opts.goal);
21
+ }
22
+ parts.push(
18
23
  // ---- 宿主环境 ----
19
24
  '', '## Host environment (facts - rely on these, do not guess)', `- OS: ${process.platform} (${process.arch}). The run_command tool executes through ${isWin ? 'cmd.exe (Windows cmd - NOT bash, NOT PowerShell)' : '/bin/sh'}.`, isWin
20
25
  ? '- cmd.exe has NO grep/head/tail/ls/cat/$(...) and wmic may be absent. Use: findstr (search), "more" or node -e (head/text ops), dir /b (ls), type (cat), where (which). For anything richer: powershell -NoProfile -Command "..."'
package/dist/runner.d.ts CHANGED
@@ -73,6 +73,8 @@ export interface RunnerEvents {
73
73
  onToolCall?(name: string, args: Record<string, unknown>): void;
74
74
  onToolResult?(name: string, output: string, isError: boolean): void;
75
75
  onApproval?(name: string, args: Record<string, unknown>, granted: boolean): void;
76
+ /** A runtime-steering user message entered the running loop's transcript. */
77
+ onInjected?(message: string): void;
76
78
  onFinal?(r: {
77
79
  text: string;
78
80
  turns: number;
@@ -84,6 +86,22 @@ export interface RunnerEvents {
84
86
  };
85
87
  }): void;
86
88
  }
89
+ /** Live runtime-steering channel (web Ctrl+Enter / TUI Enter-while-running):
90
+ * the caller pushes user messages into `queue`; the running loop drains them
91
+ * between tool batches. `active` flips to false when the task ends, so the
92
+ * caller can route a late push into the next task's queue instead. */
93
+ export interface InjectChannel {
94
+ queue: string[];
95
+ active: boolean;
96
+ }
97
+ /** Poll-shaped runtime steering (codex Enter-inject semantics): poll() is
98
+ * drained at every turn boundary; each returned entry becomes a user turn
99
+ * in the running loop. */
100
+ export interface InjectPoll {
101
+ poll(): Array<{
102
+ text: string;
103
+ }>;
104
+ }
87
105
  export interface AgentTaskOptions {
88
106
  task: string;
89
107
  registry: Registry;
@@ -99,6 +117,17 @@ export interface AgentTaskOptions {
99
117
  events?: RunnerEvents;
100
118
  /** AbortSignal: cancels the agent loop at the next turn boundary. */
101
119
  signal?: AbortSignal;
120
+ /** Runtime-steering channel (web Ctrl+Enter / TUI Enter-while-running):
121
+ * either the push queue (InjectChannel, drained between tool batches) or
122
+ * the poll shape (drained at every turn boundary). */
123
+ inject?: InjectChannel | InjectPoll;
124
+ /** Fork this run from an existing session: the new rollout records the
125
+ * parent session id as `forkedFrom` in its session_meta. */
126
+ forkFrom?: string;
127
+ /** Session goal: injected into the system prompt and persisted under
128
+ * HMH_HOME/goals.json (when unset or null, the persisted goal for the
129
+ * session is used, so the goal survives resumes/forks). */
130
+ goal?: string | null;
102
131
  }
103
132
  /** Run one full agent task end-to-end; audit + insight recording included. */
104
133
  export declare function runAgentTask(opts: AgentTaskOptions): Promise<LoopResult & {
package/dist/runner.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Also owns the native registry factory (spawn_agent recursion) and the
6
6
  * approval gate construction.
7
7
  */
8
- import { homeDir, loadConfig, resolveProvider, mcpServerTools, Registry, runLoop, Session, } from '@hmharness/kernel';
8
+ import { homeDir, loadConfig, resolveProvider, getGoal, mcpServerTools, Registry, runLoop, Session, } from '@hmharness/kernel';
9
9
  import { brief, createTrajectoryRecorder } from '@hmharness/observability';
10
10
  import { readFile } from 'node:fs/promises';
11
11
  import { readFileSync, writeFileSync } from 'node:fs';
@@ -230,10 +230,18 @@ export async function runAgentTask(opts) {
230
230
  const cfg = opts.cfg ?? (await loadConfig());
231
231
  const ctx = opts.ctx ?? { cwd: process.cwd(), home: homeDir() };
232
232
  const events = opts.events ?? {};
233
+ // inject accepts either the push channel (legacy) or the poll shape
234
+ const injectChannel = opts.inject && 'queue' in opts.inject ? opts.inject : undefined;
235
+ const injectPoll = opts.inject && 'poll' in opts.inject ? opts.inject : undefined;
236
+ if (injectChannel)
237
+ injectChannel.active = true; // channel is live for this task
233
238
  // resume = append to the same rollout; a missing/unreadable id falls back
234
239
  // to a fresh session so a renamed-away file never breaks the conversation
235
240
  const session = (opts.sessionId ? await Session.resume(ctx.home, opts.sessionId) : null)
236
- ?? Session.create(ctx.home, ctx.cwd, cfg.provider.model);
241
+ ?? Session.create(ctx.home, ctx.cwd, cfg.provider.model, opts.forkFrom ? { forkedFrom: opts.forkFrom } : {});
242
+ // session-level goal: explicit opts.goal wins; otherwise the persisted
243
+ // goal (HMH_HOME/goals.json) carries over into resumed/forked sessions
244
+ const goal = opts.goal ?? (await getGoal(ctx.home, session.id));
237
245
  // V2 M1 flight recorder: every run leaves a typed, replayable trajectory
238
246
  // under <home>/runs/<run-id>/. Best-effort by contract - storage failures
239
247
  // are swallowed inside the recorder and can never fail the task itself.
@@ -280,6 +288,7 @@ export async function runAgentTask(opts) {
280
288
  model: cfg.provider.model,
281
289
  locale: cfg.locale,
282
290
  agentsMd: agentsMd ?? undefined,
291
+ ...(goal ? { goal } : {}),
283
292
  });
284
293
  // system prompt token count: the prompt has been quietly growing (Codex 9
285
294
  // instructions + AGENTS.md + memory + skills + insights) while no cost gate
@@ -339,9 +348,22 @@ export async function runAgentTask(opts) {
339
348
  maxTurns: cfg.maxTurns,
340
349
  maxContextChars: cfg.maxContextChars,
341
350
  summarizeContext,
351
+ // runtime steering: drain the caller's injection channel after each tool
352
+ // batch; the injected text lands as a user message in the live transcript
353
+ // (web Ctrl+Enter / TUI Enter-while-running)
354
+ injectQueue: injectChannel ? () => injectChannel.queue.length ? injectChannel.queue.shift() : null : undefined,
355
+ // poll-shaped steering: drained at every turn boundary (codex semantics)
356
+ injections: injectPoll,
342
357
  approval: spawnBase.current.approval,
343
358
  events: {
344
359
  onDelta: (kind, chunk) => events.onDelta?.(kind, chunk),
360
+ onInjected: (message) => {
361
+ // keep the rollout complete: an injected user message is part of the
362
+ // session transcript, not just the working copy
363
+ void session.user(message);
364
+ traj.emit('user.injected', 'user', { preview: brief(message, 160) });
365
+ events.onInjected?.(message);
366
+ },
345
367
  onToolCall: (name, args) => {
346
368
  toolsUsed.push(name);
347
369
  traj.emit('tool.requested', 'tool', { name, args: brief(args, 120) });
@@ -375,6 +397,10 @@ export async function runAgentTask(opts) {
375
397
  traj.finish({ success: false, reason: 'error', error: brief(String(err), 200) }, { toolUses: toolsUsed.length });
376
398
  throw err;
377
399
  });
400
+ // the task ended (or died): late pushes must queue for the NEXT task instead
401
+ // of silently vanishing into a channel nothing drains
402
+ if (injectChannel)
403
+ injectChannel.active = false;
378
404
  traj.finish({ success: result.reason === 'final', reason: result.reason }, { turns: result.turns, toolUses: result.toolUses, promptTokens: result.usage.promptTokens, completionTokens: result.usage.completionTokens });
379
405
  // routing.outcome backfill: did the ACTUAL route succeed? (shadow stats)
380
406
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/agent",
3
- "version": "0.14.7",
3
+ "version": "0.14.9",
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",
@@ -17,8 +17,8 @@
17
17
  "dependencies": {
18
18
  "@hmharness/domain-harmony": "0.13.5",
19
19
  "@hmharness/domain-ops": "0.8.0",
20
- "@hmharness/evolution": "0.14.7",
21
- "@hmharness/kernel": "0.9.0",
20
+ "@hmharness/evolution": "0.14.9",
21
+ "@hmharness/kernel": "0.9.1",
22
22
  "@hmharness/observability": "0.7.0",
23
23
  "@hmharness/sandbox": "0.8.0"
24
24
  },