@hmharness/agent 0.8.2 → 0.8.4
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 +1 -0
- package/dist/index.js +1 -0
- package/dist/project.d.ts +67 -0
- package/dist/project.js +265 -0
- package/package.json +5 -4
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>;
|
package/dist/project.js
ADDED
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/agent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
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/domain-harmony": "0.8.0",
|
|
19
|
+
"@hmharness/domain-ops": "0.8.0",
|
|
20
|
+
"@hmharness/evolution": "0.8.4",
|
|
18
21
|
"@hmharness/kernel": "0.8.2",
|
|
19
22
|
"@hmharness/observability": "0.7.0",
|
|
20
|
-
"@hmharness/
|
|
21
|
-
"@hmharness/domain-harmony": "0.8.0",
|
|
22
|
-
"@hmharness/domain-ops": "0.8.0"
|
|
23
|
+
"@hmharness/sandbox": "0.8.0"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"dist"
|