@hmharness/agent 0.10.0 → 0.11.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.
@@ -1,5 +1,6 @@
1
1
  import { runLoop, type LoopResult, type ProviderConfig, type Registry, type ToolContext } from '@hmharness/kernel';
2
- export type PipelineStage = 'plan' | 'code' | 'test' | 'review' | 'judge';
2
+ import type { DeviceTestOptions, DeviceTestStep } from '@hmharness/domain-harmony';
3
+ export type PipelineStage = 'plan' | 'code' | 'test' | 'review' | 'judge' | 'device';
3
4
  /** stage labels used in runStage (repairer = the repair-loop role) */
4
5
  export type StageRole = PipelineStage | 'repairer';
5
6
  export interface StageRecord {
@@ -38,6 +39,20 @@ export interface PipelineOptions {
38
39
  signal?: AbortSignal;
39
40
  /** injectable loop (tests); default kernel runLoop */
40
41
  runLoopImpl?: typeof runLoop;
42
+ /** V3 device gate (ADR-0007): when set, run an on-device install/launch/
43
+ * log-marker/uninstall pass after the test stage and feed the four steps
44
+ * to the judge as mechanical evidence. Pure command execution - no model
45
+ * turns. */
46
+ deviceGate?: {
47
+ hdc: string;
48
+ target?: string;
49
+ hap: string;
50
+ bundle: string;
51
+ ability: string;
52
+ expectLog: string;
53
+ /** injectable runner (tests); default = domain-harmony runDeviceTest */
54
+ runDeviceTestImpl?: (o: DeviceTestOptions) => Promise<DeviceTestStep[]>;
55
+ };
41
56
  }
42
57
  /** Pull the judge's verdict line out of the final text; absence = FAIL
43
58
  * (an judge that did not follow the contract did not render a verdict). */
package/dist/pipeline.js CHANGED
@@ -107,6 +107,35 @@ export async function runPipeline(opts) {
107
107
  catch { /* best-effort persistence */ }
108
108
  return rec;
109
109
  };
110
+ /** Device gate: run the four-step on-device pass, record it as a 'device'
111
+ * stage (no model turns spent), and mirror it into the repair context.
112
+ * Defined BEFORE the try block - the try body calls it (TDZ otherwise). */
113
+ const runDeviceGate = async () => {
114
+ const g = opts.deviceGate;
115
+ const runner = g.runDeviceTestImpl ?? (await import('@hmharness/domain-harmony')).runDeviceTest;
116
+ let steps;
117
+ try {
118
+ steps = await runner({ hdc: g.hdc, ...(g.target ? { target: g.target } : {}), hap: g.hap, bundle: g.bundle, ability: g.ability, expectLog: g.expectLog });
119
+ }
120
+ catch (err) {
121
+ steps = [{ step: 'device-gate', pass: false, detail: String(err).slice(0, 300) }];
122
+ }
123
+ const allPass = steps.every((s) => s.pass);
124
+ const rec = {
125
+ stage: 'device',
126
+ attempt: repairs + 1,
127
+ verdict: allPass ? 'PASS' : 'FAIL',
128
+ text: steps.map((s) => `${s.pass ? 'PASS' : 'FAIL'} ${s.step}: ${s.detail}`).join('\n').slice(0, 4000),
129
+ turns: 0,
130
+ toolUses: 0,
131
+ reason: 'final',
132
+ };
133
+ stages.push(rec);
134
+ try {
135
+ await writeFile(join(dir, `stage-${String(stages.length).padStart(2, '0')}-device.json`), JSON.stringify(rec, null, 2) + '\n', 'utf8');
136
+ }
137
+ catch { /* best-effort */ }
138
+ };
110
139
  try {
111
140
  // 1. plan
112
141
  const plan = await runStage('plan', 1, DIRECTIVES.plan(opts.task, ''));
@@ -126,6 +155,10 @@ export async function runPipeline(opts) {
126
155
  status = 'budget';
127
156
  return await finish();
128
157
  }
158
+ // 3b. device gate (V3 slice, ADR-0007): mechanical on-device evidence,
159
+ // zero model turns; judge sees it in the evidence summary
160
+ if (opts.deviceGate)
161
+ await runDeviceGate();
129
162
  let review = await runStage('review', 1, DIRECTIVES.review(opts.task, ''));
130
163
  if (spent >= totalBudget) {
131
164
  status = 'budget';
@@ -141,6 +174,8 @@ export async function runPipeline(opts) {
141
174
  break;
142
175
  }
143
176
  testOut = await runStage('test', repairs + 1, DIRECTIVES.test(opts.task, `Plan:\n${plan.text.slice(0, 1500)}\nRepair ${repairs} applied - re-verify.`));
177
+ if (opts.deviceGate)
178
+ await runDeviceGate();
144
179
  review = await runStage('review', repairs + 1, DIRECTIVES.review(opts.task, `Repair ${repairs} was applied; focus on it.`));
145
180
  if (spent >= totalBudget) {
146
181
  status = 'budget';
@@ -174,6 +209,6 @@ export async function runPipeline(opts) {
174
209
  }
175
210
  }
176
211
  function evidenceSummary(stages) {
177
- const relevant = stages.filter((s) => s.stage === 'test' || s.stage === 'review');
212
+ const relevant = stages.filter((s) => s.stage === 'test' || s.stage === 'review' || s.stage === 'device');
178
213
  return relevant.map((s) => `[${s.stage} #${s.attempt}] ${s.text.slice(0, 600)}`).join('\n\n').slice(0, 3000);
179
214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/agent",
3
- "version": "0.10.0",
3
+ "version": "0.11.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,7 +15,7 @@
15
15
  "build": "tsc -p tsconfig.build.json"
16
16
  },
17
17
  "dependencies": {
18
- "@hmharness/domain-harmony": "0.8.0",
18
+ "@hmharness/domain-harmony": "0.11.0",
19
19
  "@hmharness/domain-ops": "0.8.0",
20
20
  "@hmharness/evolution": "0.9.0",
21
21
  "@hmharness/kernel": "0.9.0",