@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 +32 -0
- package/dist/i18n.js +56 -4
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +6 -1
- package/dist/runner.d.ts +29 -0
- package/dist/runner.js +28 -2
- package/package.json +3 -3
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 发送 ·
|
|
44
|
-
tuiBusyHints: (queued) => `运行中 ·
|
|
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 ·
|
|
154
|
-
tuiBusyHints: (queued) => `running ·
|
|
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
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.
|
|
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.
|
|
21
|
-
"@hmharness/kernel": "0.9.
|
|
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
|
},
|