@hmharness/agent 0.8.2 → 0.9.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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { baseTools, readFileTool, writeFileTool, listDirTool, runCommandTool, rememberTool, seeImageTool } from './tools.ts';
2
2
  export { manifestFor, capabilityReport, authorize, type CapabilityManifest, type CapabilityRisk, type PolicyMode } from './capability.ts';
3
+ export { checkpointProject, createProject, findProject, interruptProject, listProjects, loadProject, projectFor, releaseProject, restoreCheckpoint, resumeBundle, transitionProject, attachRun, newProjectId, type CheckpointRef, type DecisionEntry, type ProjectRecord, type ProjectState, } from './project.ts';
3
4
  export { buildSystemPrompt } from './prompt.ts';
4
5
  export { strings, type Locale, type Strings } from './i18n.ts';
5
6
  export { makeSpawnTool, MAX_SPAWN_DEPTH, type SpawnBase } from './spawn.ts';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { baseTools, readFileTool, writeFileTool, listDirTool, runCommandTool, rememberTool, seeImageTool } from "./tools.js";
2
2
  export { manifestFor, capabilityReport, authorize } from "./capability.js";
3
+ export { checkpointProject, createProject, findProject, interruptProject, listProjects, loadProject, projectFor, releaseProject, restoreCheckpoint, resumeBundle, transitionProject, attachRun, newProjectId, } from "./project.js";
3
4
  export { buildSystemPrompt } from "./prompt.js";
4
5
  export { strings } from "./i18n.js";
5
6
  export { makeSpawnTool, MAX_SPAWN_DEPTH } from "./spawn.js";
@@ -0,0 +1,67 @@
1
+ import { type SandboxSession } from '@hmharness/sandbox';
2
+ export type ProjectState = 'created' | 'active' | 'paused' | 'completed' | 'archived';
3
+ export interface CheckpointRef {
4
+ id: string;
5
+ label?: string;
6
+ /** git tree sha, or 'copy:<relative dir>' for non-git workspaces */
7
+ tree: string;
8
+ time: string;
9
+ files: number;
10
+ }
11
+ export interface DecisionEntry {
12
+ time: string;
13
+ kind: 'checkpoint' | 'restore' | 'interrupt' | 'decision' | 'state' | 'attach-run' | 'release';
14
+ summary: string;
15
+ ref?: string;
16
+ }
17
+ export interface ProjectRecord {
18
+ projectId: string;
19
+ name?: string;
20
+ workspace: string;
21
+ state: ProjectState;
22
+ createdAt: string;
23
+ updatedAt: string;
24
+ checkpoints: CheckpointRef[];
25
+ tasks: string[];
26
+ decisions: DecisionEntry[];
27
+ memory: string;
28
+ skills: string[];
29
+ runs: Array<{
30
+ sessionId: string;
31
+ time: string;
32
+ }>;
33
+ benchmarks: string[];
34
+ releases: Array<{
35
+ version: string;
36
+ checkpointId?: string;
37
+ time: string;
38
+ notes?: string;
39
+ }>;
40
+ }
41
+ export declare function newProjectId(): string;
42
+ export declare function createProject(home: string, workspace: string, name?: string): Promise<ProjectRecord>;
43
+ export declare function listProjects(home: string): Promise<ProjectRecord[]>;
44
+ export declare function loadProject(home: string, projectId: string): Promise<ProjectRecord | null>;
45
+ /** Find the newest project bound to this workspace (null if none). */
46
+ export declare function findProject(home: string, workspace: string): Promise<ProjectRecord | null>;
47
+ /** find-or-create + auto-activate (the default entry point for CLIs). */
48
+ export declare function projectFor(home: string, workspace: string, name?: string): Promise<ProjectRecord>;
49
+ export declare function transitionProject(home: string, rec: ProjectRecord, next: ProjectState): Promise<ProjectRecord>;
50
+ /** Byte-exact snapshot via git plumbing (user index/refs/worktree untouched);
51
+ * directory-copy fallback for non-git workspaces. */
52
+ export declare function checkpointProject(home: string, rec: ProjectRecord, label?: string): Promise<CheckpointRef>;
53
+ /** Materialize a checkpoint into a fresh sandbox copy for inspection or a
54
+ * recovery run. The user's workspace is never touched. */
55
+ export declare function restoreCheckpoint(home: string, rec: ProjectRecord, checkpointId: string): Promise<SandboxSession>;
56
+ /** Remember the rollout a project conversation lives in (0.8.2 append semantics). */
57
+ export declare function attachRun(home: string, rec: ProjectRecord, sessionId: string): Promise<void>;
58
+ /** Recovery bundle: what a resume needs - last rollout + last checkpoint. */
59
+ export declare function resumeBundle(home: string, rec: ProjectRecord): Promise<{
60
+ project: ProjectRecord;
61
+ lastRun: string | null;
62
+ lastCheckpoint: CheckpointRef | null;
63
+ }>;
64
+ /** Interrupt: record the event and pause (paused = resumable per blueprint).
65
+ * The actual abort stays with the caller's AbortSignal. */
66
+ export declare function interruptProject(home: string, rec: ProjectRecord, reason?: string): Promise<ProjectRecord>;
67
+ export declare function releaseProject(home: string, rec: ProjectRecord, version: string, notes?: string): Promise<void>;
@@ -0,0 +1,265 @@
1
+ /**
2
+ * @hmharness/agent - project runtime (V2 M8, ADR-0002)
3
+ * The Project entity from the V2 blueprint: one record per project workspace
4
+ * binding state machine, checkpoints, decisions, run continuation, releases.
5
+ *
6
+ * Safety posture (the whole design bends around it):
7
+ * - Checkpoints are git PLUMBING snapshots: a temp index (GIT_INDEX_FILE
8
+ * inside the project dir) + read-tree/add/write-tree. Objects land in the
9
+ * repo's .git/objects; the user's index, refs, branches and worktree are
10
+ * never touched - checkpointing does not require the user to commit.
11
+ * - Restore MATERIALIZES a checkpoint into a fresh sandbox copy (git archive
12
+ * + tar). Recovery never mutates the user's tree; there is no code path
13
+ * here that resets anything in the real workspace.
14
+ * - Run continuation rides the 0.8.2 rollout append semantics: attachRun
15
+ * remembers the session id; the caller resumes with loadTranscript +
16
+ * runAgentTask({ sessionId }).
17
+ */
18
+ import { appendFile, cp, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
19
+ import { execFile as execCb } from 'node:child_process';
20
+ import { tmpdir } from 'node:os';
21
+ import { join, resolve } from 'node:path';
22
+ import { sandbox } from '@hmharness/sandbox';
23
+ /** blueprint lifecycle: created→active→paused(=resumable)→completed→archived */
24
+ const TRANSITIONS = {
25
+ created: ['active'],
26
+ active: ['paused', 'completed'],
27
+ paused: ['active', 'completed'],
28
+ completed: ['archived'],
29
+ archived: [],
30
+ };
31
+ /* ---------------- git plumbing (checkpoint side) ---------------- */
32
+ const GIT_CANDIDATES = ['git', 'C:\\Program Files\\Git\\cmd\\git.exe', 'C:\\Program Files (x86)\\Git\\cmd\\git.exe'];
33
+ let gitBin;
34
+ function run(exe, args, opts) {
35
+ return new Promise((res) => {
36
+ execCb(exe, args, { cwd: opts.cwd, env: { ...process.env, ...opts.env }, timeout: opts.timeoutMs ?? 60_000, windowsHide: true, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
37
+ if (!err) {
38
+ res({ stdout: String(stdout ?? ''), stderr: String(stderr ?? ''), code: 0, launchFailed: false });
39
+ return;
40
+ }
41
+ const e = err;
42
+ // ENOENT-style launch failure (string code) -> try next candidate;
43
+ // a real non-zero exit (numeric code) is a git answer, not a failure to launch
44
+ res({ stdout: String(stdout ?? ''), stderr: String(stderr ?? ''), code: typeof e.code === 'number' ? e.code : -1, launchFailed: typeof e.code !== 'number' });
45
+ });
46
+ });
47
+ }
48
+ async function git(args, opts) {
49
+ const candidates = [...(gitBin ? [gitBin] : []), ...GIT_CANDIDATES];
50
+ let last = { stdout: '', stderr: 'git not found', code: -1, launchFailed: true };
51
+ for (const exe of candidates) {
52
+ last = await run(exe, args, opts);
53
+ if (!last.launchFailed) {
54
+ gitBin = exe;
55
+ return last;
56
+ }
57
+ }
58
+ throw new Error(last.stderr || 'git not found');
59
+ }
60
+ async function isGitRepo(workspace) {
61
+ try {
62
+ const r = await git(['rev-parse', '--is-inside-work-tree'], { cwd: workspace });
63
+ return r.code === 0 && r.stdout.trim() === 'true';
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ /* ---------------- persistence ---------------- */
70
+ function projRoot(home) { return join(home, 'projects'); }
71
+ function projDir(home, id) { return join(projRoot(home), id); }
72
+ function projFile(home, id) { return join(projDir(home, id), 'project.json'); }
73
+ async function save(home, rec) {
74
+ rec.updatedAt = new Date().toISOString();
75
+ await mkdir(projDir(home, rec.projectId), { recursive: true });
76
+ await writeFile(projFile(home, rec.projectId), JSON.stringify(rec, null, 2) + '\n', 'utf8');
77
+ }
78
+ async function decide(home, rec, kind, summary, ref) {
79
+ const entry = { time: new Date().toISOString(), kind, summary, ...(ref ? { ref } : {}) };
80
+ rec.decisions.push(entry);
81
+ try {
82
+ await appendFile(join(projDir(home, rec.projectId), 'decisions.jsonl'), JSON.stringify(entry) + '\n', 'utf8');
83
+ }
84
+ catch { /* mirror is best-effort */ }
85
+ }
86
+ /* ---------------- lifecycle ---------------- */
87
+ export function newProjectId() {
88
+ return `proj_${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}_${Math.random().toString(36).slice(2, 8)}`;
89
+ }
90
+ export async function createProject(home, workspace, name) {
91
+ const rec = {
92
+ projectId: newProjectId(),
93
+ name,
94
+ workspace: resolve(workspace),
95
+ state: 'created',
96
+ createdAt: new Date().toISOString(),
97
+ updatedAt: new Date().toISOString(),
98
+ checkpoints: [],
99
+ tasks: [],
100
+ decisions: [],
101
+ memory: resolve(workspace),
102
+ skills: [],
103
+ runs: [],
104
+ benchmarks: [],
105
+ releases: [],
106
+ };
107
+ await decide(home, rec, 'state', `project created for ${rec.workspace}`);
108
+ await save(home, rec);
109
+ return rec;
110
+ }
111
+ export async function listProjects(home) {
112
+ let ids = [];
113
+ try {
114
+ ids = (await readdir(projRoot(home))).filter((d) => d.startsWith('proj_'));
115
+ }
116
+ catch {
117
+ return [];
118
+ }
119
+ const out = [];
120
+ for (const id of ids) {
121
+ try {
122
+ out.push(JSON.parse(await readFile(projFile(home, id), 'utf8')));
123
+ }
124
+ catch { /* skip torn */ }
125
+ }
126
+ return out.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
127
+ }
128
+ export async function loadProject(home, projectId) {
129
+ try {
130
+ return JSON.parse(await readFile(projFile(home, projectId), 'utf8'));
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ function samePath(a, b) {
137
+ return resolve(a).replace(/\\/g, '/').toLowerCase().replace(/\/+$/, '') === resolve(b).replace(/\\/g, '/').toLowerCase().replace(/\/+$/, '');
138
+ }
139
+ /** Find the newest project bound to this workspace (null if none). */
140
+ export async function findProject(home, workspace) {
141
+ const all = await listProjects(home);
142
+ return all.find((p) => samePath(p.workspace, workspace) && p.state !== 'archived') ?? null;
143
+ }
144
+ /** find-or-create + auto-activate (the default entry point for CLIs). */
145
+ export async function projectFor(home, workspace, name) {
146
+ const found = await findProject(home, workspace);
147
+ if (found) {
148
+ if (found.state === 'created')
149
+ return transitionProject(home, found, 'active');
150
+ return found;
151
+ }
152
+ const rec = await createProject(home, workspace, name);
153
+ return transitionProject(home, rec, 'active');
154
+ }
155
+ export async function transitionProject(home, rec, next) {
156
+ const allowed = TRANSITIONS[rec.state] ?? [];
157
+ if (!allowed.includes(next))
158
+ throw new Error(`illegal transition ${rec.state} -> ${next} (allowed: ${allowed.join(', ') || 'none'})`);
159
+ const from = rec.state;
160
+ rec.state = next;
161
+ await decide(home, rec, 'state', `${from} -> ${next}`);
162
+ await save(home, rec);
163
+ return rec;
164
+ }
165
+ /* ---------------- checkpoints ---------------- */
166
+ const COPY_SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.hvigor', '.idea']);
167
+ /** Byte-exact snapshot via git plumbing (user index/refs/worktree untouched);
168
+ * directory-copy fallback for non-git workspaces. */
169
+ export async function checkpointProject(home, rec, label) {
170
+ if (rec.state === 'archived')
171
+ throw new Error('archived projects cannot take checkpoints');
172
+ let ref;
173
+ if (await isGitRepo(rec.workspace)) {
174
+ const indexFile = join(projDir(home, rec.projectId), `index-${Date.now()}`);
175
+ await mkdir(projDir(home, rec.projectId), { recursive: true });
176
+ const env = { GIT_INDEX_FILE: indexFile };
177
+ // seed the temp index from HEAD (no commits yet -> start empty), then
178
+ // overlay the whole worktree; write-tree freezes it as an object
179
+ await git(['read-tree', 'HEAD'], { cwd: rec.workspace, env, timeoutMs: 30_000 });
180
+ await git(['add', '-A', '--', '.'], { cwd: rec.workspace, env, timeoutMs: 120_000 });
181
+ const tree = (await git(['write-tree'], { cwd: rec.workspace, env })).stdout.trim();
182
+ // workspace may sit inside a subdirectory of the repo - snapshot THE
183
+ // SUBTREE (what the project actually owns), not the whole monorepo
184
+ const prefix = (await git(['rev-parse', '--show-prefix'], { cwd: rec.workspace })).stdout.trim().replace(/\\/g, '/');
185
+ const subTree = prefix ? (await git(['rev-parse', `${tree}:${prefix}`], { cwd: rec.workspace })).stdout.trim() : tree;
186
+ if (!/^[0-9a-f]{40}$/.test(subTree))
187
+ throw new Error(`checkpoint subtree resolve failed (prefix "${prefix}")`);
188
+ const files = (await git(['ls-tree', '-r', '--name-only', subTree], { cwd: rec.workspace })).stdout.split('\n').filter(Boolean).length;
189
+ await rm(indexFile, { force: true });
190
+ ref = { id: subTree.slice(0, 10), label, tree: subTree, time: new Date().toISOString(), files };
191
+ }
192
+ else {
193
+ const rel = `artifacts/cp-${Date.now().toString(36)}`;
194
+ const dest = join(projDir(home, rec.projectId), rel);
195
+ await mkdir(dest, { recursive: true });
196
+ await cp(rec.workspace, dest, { recursive: true, filter: (src) => !COPY_SKIP.has(src.split(/[\\/]/).pop() ?? '') });
197
+ let files = 0;
198
+ const count = async (d) => {
199
+ for (const e of await readdir(d, { withFileTypes: true })) {
200
+ if (e.isDirectory())
201
+ await count(join(d, e.name));
202
+ else
203
+ files++;
204
+ }
205
+ };
206
+ await count(dest);
207
+ ref = { id: `cp_${rel.slice(-8)}`, label, tree: `copy:${rel}`, time: new Date().toISOString(), files };
208
+ }
209
+ rec.checkpoints.push(ref);
210
+ await decide(home, rec, 'checkpoint', `checkpoint ${ref.id}${label ? ` (${label})` : ''} - ${ref.files} files`, ref.tree);
211
+ await save(home, rec);
212
+ return ref;
213
+ }
214
+ /** Materialize a checkpoint into a fresh sandbox copy for inspection or a
215
+ * recovery run. The user's workspace is never touched. */
216
+ export async function restoreCheckpoint(home, rec, checkpointId) {
217
+ const cpRef = rec.checkpoints.find((c) => c.id === checkpointId || c.tree === checkpointId);
218
+ if (!cpRef)
219
+ throw new Error(`no checkpoint ${checkpointId} in project ${rec.projectId}`);
220
+ const dir = await mkdtemp(join(tmpdir(), 'hmh-proj-restore-'));
221
+ const session = await sandbox.create({ dir });
222
+ if (cpRef.tree.startsWith('copy:')) {
223
+ await cp(join(projDir(home, rec.projectId), cpRef.tree.slice(5)), dir, { recursive: true });
224
+ }
225
+ else {
226
+ const tarFile = join(dir, '..', `cp-${Date.now()}.tar`);
227
+ await git(['archive', '--format=tar', `--output=${tarFile}`, cpRef.tree], { cwd: rec.workspace, timeoutMs: 120_000 });
228
+ await new Promise((res, rej) => {
229
+ execCb('tar', ['-xf', tarFile, '-C', dir], { timeout: 120_000, windowsHide: true }, (err) => err ? rej(err) : res());
230
+ });
231
+ await rm(tarFile, { force: true });
232
+ }
233
+ await decide(home, rec, 'restore', `checkpoint ${cpRef.id} materialized to ${dir} (sandbox copy)`, cpRef.tree);
234
+ await save(home, rec);
235
+ return session;
236
+ }
237
+ /* ---------------- run continuation / interrupt / releases ---------------- */
238
+ /** Remember the rollout a project conversation lives in (0.8.2 append semantics). */
239
+ export async function attachRun(home, rec, sessionId) {
240
+ rec.runs.push({ sessionId, time: new Date().toISOString() });
241
+ await decide(home, rec, 'attach-run', `run ${sessionId}`);
242
+ await save(home, rec);
243
+ }
244
+ /** Recovery bundle: what a resume needs - last rollout + last checkpoint. */
245
+ export async function resumeBundle(home, rec) {
246
+ return {
247
+ project: rec,
248
+ lastRun: rec.runs.length > 0 ? rec.runs[rec.runs.length - 1].sessionId : null,
249
+ lastCheckpoint: rec.checkpoints.length > 0 ? rec.checkpoints[rec.checkpoints.length - 1] : null,
250
+ };
251
+ }
252
+ /** Interrupt: record the event and pause (paused = resumable per blueprint).
253
+ * The actual abort stays with the caller's AbortSignal. */
254
+ export async function interruptProject(home, rec, reason) {
255
+ await decide(home, rec, 'interrupt', `interrupted${reason ? `: ${reason}` : ''}`);
256
+ await save(home, rec);
257
+ if (rec.state === 'active')
258
+ return transitionProject(home, rec, 'paused');
259
+ return rec;
260
+ }
261
+ export async function releaseProject(home, rec, version, notes) {
262
+ rec.releases.push({ version, checkpointId: rec.checkpoints.length > 0 ? rec.checkpoints[rec.checkpoints.length - 1].id : undefined, time: new Date().toISOString(), notes });
263
+ await decide(home, rec, 'release', `release ${version}`);
264
+ await save(home, rec);
265
+ }
package/dist/runner.js CHANGED
@@ -10,7 +10,8 @@ import { brief, createTrajectoryRecorder } from '@hmharness/observability';
10
10
  import { readFile } from 'node:fs/promises';
11
11
  import { readFileSync, writeFileSync } from 'node:fs';
12
12
  import { join, dirname } from 'node:path';
13
- import { appendMemory, listSkills, readInsights, readNotes, recentInsights, recordInsight, retrieveMemory, skillsToPrompt, sessionGetsCanary, canaryWatermark, listCanary, workspaceForCwd } from '@hmharness/evolution';
13
+ import { extractFeatures, routeDecision, recordRoutingOutcome } from '@hmharness/kernel';
14
+ import { appendMemory, listSkills, readInsights, readNotes, recentInsights, recordInsight, redactSecrets, retrieveMemory, skillsToPrompt, sessionGetsCanary, canaryWatermark, listCanary, workspaceForCwd } from '@hmharness/evolution';
14
15
  import { harmonyTools } from '@hmharness/domain-harmony';
15
16
  import { opsTools } from '@hmharness/domain-ops';
16
17
  import * as readline from 'node:readline/promises';
@@ -238,6 +239,26 @@ export async function runAgentTask(opts) {
238
239
  // are swallowed inside the recorder and can never fail the task itself.
239
240
  const traj = createTrajectoryRecorder(ctx.home, { task: opts.task, model: cfg.provider.model, cwd: ctx.cwd });
240
241
  traj.emit('run.started', 'user', { task: brief(opts.task, 400) });
242
+ // V2 M10 shadow router: log what the adaptive router WOULD pick vs the live
243
+ // static route - data for a later, gated switch (never steers traffic now)
244
+ {
245
+ const routing = cfg.routing ?? {};
246
+ const features = extractFeatures(opts.task);
247
+ const sug = routeDecision(features, {
248
+ actual: routing.chat ?? 'default',
249
+ harmonyRoute: routing['chat-harmony'],
250
+ heavyRoute: routing['chat-heavy'],
251
+ defaultRoute: routing.chat ?? 'default',
252
+ });
253
+ void recordRoutingOutcome(ctx.home, {
254
+ time: new Date().toISOString(),
255
+ task: redactSecrets(opts.task).slice(0, 400),
256
+ features: sug.features,
257
+ actual: sug.actual,
258
+ suggested: sug.suggested,
259
+ reason: sug.reason,
260
+ }).catch(() => undefined);
261
+ }
241
262
  // workspace scoping + optional embedding hybrid for memory retrieval.
242
263
  // Embeddings only when routing.embedding is EXPLICITLY set - an inherited
243
264
  // chat route would 404 on /embeddings once per task for nothing.
@@ -355,6 +376,27 @@ export async function runAgentTask(opts) {
355
376
  throw err;
356
377
  });
357
378
  traj.finish({ success: result.reason === 'final', reason: result.reason }, { turns: result.turns, toolUses: result.toolUses, promptTokens: result.usage.promptTokens, completionTokens: result.usage.completionTokens });
379
+ // routing.outcome backfill: did the ACTUAL route succeed? (shadow stats)
380
+ {
381
+ const routing = cfg.routing ?? {};
382
+ const features = extractFeatures(opts.task);
383
+ const sug = routeDecision(features, {
384
+ actual: routing.chat ?? 'default',
385
+ harmonyRoute: routing['chat-harmony'],
386
+ heavyRoute: routing['chat-heavy'],
387
+ defaultRoute: routing.chat ?? 'default',
388
+ });
389
+ void recordRoutingOutcome(ctx.home, {
390
+ time: new Date().toISOString(),
391
+ task: redactSecrets(opts.task).slice(0, 400),
392
+ features: sug.features,
393
+ actual: sug.actual,
394
+ suggested: sug.suggested,
395
+ reason: sug.reason,
396
+ outcome: result.reason === 'final' ? 'ok' : result.reason,
397
+ tokens: result.usage.promptTokens + result.usage.completionTokens,
398
+ }).catch(() => undefined);
399
+ }
358
400
  // ---- instant feedback: learn from THIS task's mistakes, not 8 tasks later ----
359
401
  // Tier 1 (always, zero cost): raw error pattern → memory self-note. Lowered
360
402
  // to 1 failure for system-level patterns (shell incompat, auth, missing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/agent",
3
- "version": "0.8.2",
3
+ "version": "0.9.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,11 +15,12 @@
15
15
  "build": "tsc -p tsconfig.build.json"
16
16
  },
17
17
  "dependencies": {
18
- "@hmharness/kernel": "0.8.2",
19
- "@hmharness/observability": "0.7.0",
20
- "@hmharness/evolution": "0.8.3",
21
18
  "@hmharness/domain-harmony": "0.8.0",
22
- "@hmharness/domain-ops": "0.8.0"
19
+ "@hmharness/domain-ops": "0.8.0",
20
+ "@hmharness/evolution": "0.9.0",
21
+ "@hmharness/kernel": "0.9.0",
22
+ "@hmharness/observability": "0.7.0",
23
+ "@hmharness/sandbox": "0.8.0"
23
24
  },
24
25
  "files": [
25
26
  "dist"