@hmharness/agent 0.23.23 → 0.23.25
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/code-wm-live.d.ts +44 -0
- package/dist/code-wm-live.js +72 -0
- package/dist/code-wm-recorder.d.ts +15 -15
- package/dist/code-wm-recorder.js +54 -9
- package/dist/runner.js +7 -3
- package/package.json +11 -11
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CodeWorldModel } from '@hmharness/cognitive';
|
|
2
|
+
import type { LspClient, Diagnostic } from '@hmharness/lsp';
|
|
3
|
+
export interface LiveCodeWmSensorOptions {
|
|
4
|
+
workspaceRoot: string;
|
|
5
|
+
languageId?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class LiveCodeWmSensor {
|
|
8
|
+
private client;
|
|
9
|
+
private cwm;
|
|
10
|
+
private opts;
|
|
11
|
+
private diagPushes;
|
|
12
|
+
private lastSyncAt;
|
|
13
|
+
private tracked;
|
|
14
|
+
constructor(client: LspClient, cwm: CodeWorldModel, opts: LiveCodeWmSensorOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Full sync for one file: open the doc on the server (triggers analysis),
|
|
17
|
+
* pull symbols, pull diagnostics from the model's sensor state, push both
|
|
18
|
+
* into the Code WM. Returns the ingested counts.
|
|
19
|
+
*/
|
|
20
|
+
syncFile(absPath: string, text: string): Promise<{
|
|
21
|
+
entitiesIngested: number;
|
|
22
|
+
relationsIngested: number;
|
|
23
|
+
diagnosticsIngested: number;
|
|
24
|
+
}>;
|
|
25
|
+
/** Pull the CURRENT diagnostics for a uri from the model's sensor state.
|
|
26
|
+
* Range-restored: the Code WM keeps message-level state (no ranges);
|
|
27
|
+
* zero ranges are the honest placeholder for sensor re-reads, never a
|
|
28
|
+
* fabricated location. */
|
|
29
|
+
pullDiagnostics(uri: string): Diagnostic[];
|
|
30
|
+
get stats(): {
|
|
31
|
+
tracked: number;
|
|
32
|
+
diagPushes: number;
|
|
33
|
+
lastSyncAt: string | null;
|
|
34
|
+
};
|
|
35
|
+
/** Sync many files (workspace scan path). */
|
|
36
|
+
syncFiles(files: Array<{
|
|
37
|
+
path: string;
|
|
38
|
+
text: string;
|
|
39
|
+
}>): Promise<{
|
|
40
|
+
entitiesIngested: number;
|
|
41
|
+
relationsIngested: number;
|
|
42
|
+
diagnosticsIngested: number;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/agent - live LSP sensor → Code World Model bridge (audit line B)
|
|
3
|
+
*
|
|
4
|
+
* The mapping functions (lspSymbolsToOntology etc.) are pure; THIS class
|
|
5
|
+
* makes them LIVE: an LspClient's push stream (diagnostics) and pull
|
|
6
|
+
* results (documentSymbols) flow into a CodeWorldModel continuously, with
|
|
7
|
+
* openDoc() driving the server to analyze the file in the first place.
|
|
8
|
+
*
|
|
9
|
+
* Usage: create with a connected LspClient + a CodeWorldModel + the
|
|
10
|
+
* workspace root; call syncFile(uri, text) after each edit the agent makes
|
|
11
|
+
* — the model then carries a current code world for the slice/MEA/digest
|
|
12
|
+
* layers to reason over.
|
|
13
|
+
*/
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { fileToUri } from '@hmharness/lsp';
|
|
16
|
+
import { syncCodeWorldModel } from "./code-wm-sensor.js";
|
|
17
|
+
export class LiveCodeWmSensor {
|
|
18
|
+
client;
|
|
19
|
+
cwm;
|
|
20
|
+
opts;
|
|
21
|
+
diagPushes = 0;
|
|
22
|
+
lastSyncAt = null;
|
|
23
|
+
tracked = new Set();
|
|
24
|
+
constructor(client, cwm, opts) {
|
|
25
|
+
this.client = client;
|
|
26
|
+
this.cwm = cwm;
|
|
27
|
+
this.opts = opts;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Full sync for one file: open the doc on the server (triggers analysis),
|
|
31
|
+
* pull symbols, pull diagnostics from the model's sensor state, push both
|
|
32
|
+
* into the Code WM. Returns the ingested counts.
|
|
33
|
+
*/
|
|
34
|
+
async syncFile(absPath, text) {
|
|
35
|
+
const uri = fileToUri(absPath);
|
|
36
|
+
this.client.openDoc(uri, this.opts.languageId ?? 'typescript', text);
|
|
37
|
+
const symbols = await this.client.documentSymbols({ uri }).catch(() => []);
|
|
38
|
+
const diagnostics = this.pullDiagnostics(uri);
|
|
39
|
+
const result = syncCodeWorldModel(this.cwm, { uri, symbols, diagnostics });
|
|
40
|
+
this.diagPushes += result.diagnosticsIngested;
|
|
41
|
+
this.lastSyncAt = new Date().toISOString();
|
|
42
|
+
this.tracked.add(uri);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/** Pull the CURRENT diagnostics for a uri from the model's sensor state.
|
|
46
|
+
* Range-restored: the Code WM keeps message-level state (no ranges);
|
|
47
|
+
* zero ranges are the honest placeholder for sensor re-reads, never a
|
|
48
|
+
* fabricated location. */
|
|
49
|
+
pullDiagnostics(uri) {
|
|
50
|
+
const diags = this.cwm.diagnosticsFor(uri);
|
|
51
|
+
return diags.map((d) => ({
|
|
52
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
|
|
53
|
+
severity: (d.severity ?? 1),
|
|
54
|
+
message: d.message,
|
|
55
|
+
source: 'cwm',
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
get stats() {
|
|
59
|
+
return { tracked: this.tracked.size, diagPushes: this.diagPushes, lastSyncAt: this.lastSyncAt };
|
|
60
|
+
}
|
|
61
|
+
/** Sync many files (workspace scan path). */
|
|
62
|
+
async syncFiles(files) {
|
|
63
|
+
const total = { entitiesIngested: 0, relationsIngested: 0, diagnosticsIngested: 0 };
|
|
64
|
+
for (const f of files) {
|
|
65
|
+
const r = await this.syncFile(join(this.opts.workspaceRoot, f.path), f.text);
|
|
66
|
+
total.entitiesIngested += r.entitiesIngested;
|
|
67
|
+
total.relationsIngested += r.relationsIngested;
|
|
68
|
+
total.diagnosticsIngested += r.diagnosticsIngested;
|
|
69
|
+
}
|
|
70
|
+
return total;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @hmharness/agent - Code World Model recorder (production sensor wiring)
|
|
3
|
-
*
|
|
4
|
-
* Round 38 closes the loop the sensors were waiting for: extension page
|
|
5
|
-
* observations now reach a PERSISTED Code World Model. The recorder is a
|
|
6
|
-
* lazy per-home singleton — the first observation loads the model from
|
|
7
|
-
* HMH_HOME, the sensor feeds it, and a debounce flushes to disk. The LSP
|
|
8
|
-
* sensor (round 35) rides the same store once its tools gain an event
|
|
9
|
-
* hook; until then it stays library-only, honestly.
|
|
10
|
-
*
|
|
11
|
-
* Failure posture: everything best-effort — a storage error logs nothing
|
|
12
|
-
* and never breaks the tool that observed the page (evidence must not be
|
|
13
|
-
* able to kill the work it evidences). A crash loses at most one debounce
|
|
14
|
-
* window of observations.
|
|
15
|
-
*/
|
|
16
1
|
import { type CodeWorldModel } from '@hmharness/cognitive';
|
|
17
2
|
import type { RawPageData } from '@hmharness/extension';
|
|
3
|
+
import type { Tool } from '@hmharness/kernel';
|
|
4
|
+
import { type CodeWorldSyncInput } from './code-wm-sensor.js';
|
|
18
5
|
/** The onPageRead hook handed to extensionTools — one closure per home. */
|
|
19
6
|
export declare function pageReadSink(home: string): (raw: RawPageData) => void;
|
|
7
|
+
/** The onObserve hook handed to lspTools — the LSP tools' read-only pulls
|
|
8
|
+
* become durable Code WM evidence, same store, same debounce. */
|
|
9
|
+
export declare function lspObserveSink(home: string): (obs: CodeWorldSyncInput) => void;
|
|
10
|
+
/** Build facts replace (the model keeps only the LATEST build verdict). */
|
|
11
|
+
export declare function buildSink(home: string): (fact: {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
outputDigest?: string;
|
|
14
|
+
}) => void;
|
|
15
|
+
/** Registry-assembly wrapper (round 44): the harmony build tools gain a
|
|
16
|
+
* build-fact observation without the domain package learning about
|
|
17
|
+
* cognitive. The wrap never alters the tool's result; a sink error is
|
|
18
|
+
* swallowed (the observation must not fail the work it evidences). */
|
|
19
|
+
export declare function observeBuildTools(tools: Tool[], home: string): Tool[];
|
|
20
20
|
/** Test isolation: drop caches + pending timers (never used in production). */
|
|
21
21
|
export declare function resetCodeWorldRecorder(): void;
|
|
22
22
|
/** Introspection for tests/status surfaces: the live model for a home. */
|
package/dist/code-wm-recorder.js
CHANGED
|
@@ -1,29 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @hmharness/agent - Code World Model recorder (production sensor wiring)
|
|
3
3
|
*
|
|
4
|
-
* Round 38
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Round 38 closed the loop for extension page observations; round 43 adds
|
|
5
|
+
* the LSP side (lspTools onObserve → symbols/diagnostics); round 44 adds
|
|
6
|
+
* the build side: the harmony build tools' results become the model's
|
|
7
|
+
* LATEST BuildFact, wrapped at registry assembly so the domain package
|
|
8
|
+
* stays single-minded (kernel-only deps — the audit's composition-layer
|
|
9
|
+
* rule). All three families share the lazy per-home singleton and one
|
|
10
|
+
* debounced flush. LiveCodeWmSensor remains the wrapper for callers that
|
|
11
|
+
* own a client and want file-driven syncs; the tool-hook paths share the
|
|
12
|
+
* ingestion core.
|
|
10
13
|
*
|
|
11
14
|
* Failure posture: everything best-effort — a storage error logs nothing
|
|
12
15
|
* and never breaks the tool that observed the page (evidence must not be
|
|
13
16
|
* able to kill the work it evidences). A crash loses at most one debounce
|
|
14
17
|
* window of observations.
|
|
15
18
|
*/
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
16
20
|
import { loadCodeWorldModel, saveCodeWorldModel } from '@hmharness/cognitive';
|
|
17
21
|
import { syncExtensionRuntime } from "./extension-wm-sensor.js";
|
|
22
|
+
import { syncCodeWorldModel } from "./code-wm-sensor.js";
|
|
18
23
|
const SAVE_DEBOUNCE_MS = 2_000;
|
|
19
24
|
const entries = new Map();
|
|
20
25
|
/** The onPageRead hook handed to extensionTools — one closure per home. */
|
|
21
26
|
export function pageReadSink(home) {
|
|
22
27
|
return (raw) => {
|
|
23
|
-
void
|
|
28
|
+
void mutate(home, (cwm) => syncExtensionRuntime(cwm, raw));
|
|
24
29
|
};
|
|
25
30
|
}
|
|
26
|
-
|
|
31
|
+
/** The onObserve hook handed to lspTools — the LSP tools' read-only pulls
|
|
32
|
+
* become durable Code WM evidence, same store, same debounce. */
|
|
33
|
+
export function lspObserveSink(home) {
|
|
34
|
+
return (obs) => {
|
|
35
|
+
if (!obs.symbols?.length && !obs.diagnostics?.length)
|
|
36
|
+
return;
|
|
37
|
+
void mutate(home, (cwm) => syncCodeWorldModel(cwm, obs));
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Build facts replace (the model keeps only the LATEST build verdict). */
|
|
41
|
+
export function buildSink(home) {
|
|
42
|
+
return (fact) => {
|
|
43
|
+
void mutate(home, (cwm) => cwm.ingest({ build: { ok: fact.ok, ...(fact.outputDigest ? { outputDigest: fact.outputDigest } : {}), at: new Date().toISOString() } }));
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Registry-assembly wrapper (round 44): the harmony build tools gain a
|
|
47
|
+
* build-fact observation without the domain package learning about
|
|
48
|
+
* cognitive. The wrap never alters the tool's result; a sink error is
|
|
49
|
+
* swallowed (the observation must not fail the work it evidences). */
|
|
50
|
+
export function observeBuildTools(tools, home) {
|
|
51
|
+
const sink = buildSink(home);
|
|
52
|
+
return tools.map((t) => {
|
|
53
|
+
if (t.name !== 'harmony_build' && t.name !== 'harmony_cjpm_build')
|
|
54
|
+
return t;
|
|
55
|
+
const orig = t.execute.bind(t);
|
|
56
|
+
const wrapped = {
|
|
57
|
+
...t,
|
|
58
|
+
async execute(args, ctx) {
|
|
59
|
+
const r = await orig(args, ctx);
|
|
60
|
+
try {
|
|
61
|
+
const out = typeof r.output === 'string' ? r.output : '';
|
|
62
|
+
sink({ ok: !r.isError, ...(out ? { outputDigest: createHash('sha256').update(out).digest('hex').slice(0, 16) } : {}) });
|
|
63
|
+
}
|
|
64
|
+
catch { /* observation must never fail the tool */ }
|
|
65
|
+
return r;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
return wrapped;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function mutate(home, fn) {
|
|
27
72
|
try {
|
|
28
73
|
let entry = entries.get(home);
|
|
29
74
|
if (!entry) {
|
|
@@ -31,7 +76,7 @@ async function observe(home, raw) {
|
|
|
31
76
|
entries.set(home, entry);
|
|
32
77
|
}
|
|
33
78
|
const cwm = await entry.load;
|
|
34
|
-
|
|
79
|
+
fn(cwm);
|
|
35
80
|
entry.dirty = true;
|
|
36
81
|
if (!entry.timer) {
|
|
37
82
|
entry.timer = setTimeout(() => {
|
package/dist/runner.js
CHANGED
|
@@ -18,7 +18,7 @@ import { opsTools } from '@hmharness/domain-ops';
|
|
|
18
18
|
import { discoverServers, lspTools } from '@hmharness/lsp';
|
|
19
19
|
import { discoverBrowsers, browserTools } from '@hmharness/browser';
|
|
20
20
|
import { discoverExtensionBridgeSync, extensionTools } from '@hmharness/extension';
|
|
21
|
-
import { pageReadSink } from "./code-wm-recorder.js";
|
|
21
|
+
import { pageReadSink, lspObserveSink, observeBuildTools } from "./code-wm-recorder.js";
|
|
22
22
|
import * as readline from 'node:readline/promises';
|
|
23
23
|
import { stdin } from 'node:process';
|
|
24
24
|
import { baseTools } from "./tools.js";
|
|
@@ -39,7 +39,7 @@ export function toServerConfig(c) {
|
|
|
39
39
|
export const spawnBase = {};
|
|
40
40
|
export function nativeRegistry(depth, opts = {}) {
|
|
41
41
|
const reg = new Registry();
|
|
42
|
-
reg.registerAll(baseTools).registerAll(harmonyTools).registerAll(opsTools);
|
|
42
|
+
reg.registerAll(baseTools).registerAll(observeBuildTools(harmonyTools, homeDir())).registerAll(opsTools);
|
|
43
43
|
// W6: LSP Tier-0 code-intelligence tools — registered ONLY when a real
|
|
44
44
|
// server is discoverable on PATH; a machine with no language servers
|
|
45
45
|
// must not carry dead tools. Discovery is synchronous (memoized PATH
|
|
@@ -49,7 +49,11 @@ export function nativeRegistry(depth, opts = {}) {
|
|
|
49
49
|
// healthy-only: a PATH shim whose toolchain lacks the component would
|
|
50
50
|
// register a tool that always errors — skip it, keep the registry honest
|
|
51
51
|
if (discoverServers().some((s) => s.healthy)) {
|
|
52
|
-
|
|
52
|
+
// onObserve feeds the PERSISTED Code World Model (round 43): every
|
|
53
|
+
// lsp_symbols/lsp_diagnostics pull becomes durable entity/relation/
|
|
54
|
+
// diagnostic evidence — best-effort, same store as the extension
|
|
55
|
+
// recorder, never able to fail the tool that observed.
|
|
56
|
+
reg.registerAll(lspTools({ workspaceRoot: opts.workspaceRoot ?? process.cwd(), home: homeDir(), onObserve: lspObserveSink(homeDir()) }));
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
catch { /* lsp optional: never block tool assembly */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/agent",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.25",
|
|
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,16 +15,16 @@
|
|
|
15
15
|
"build": "tsc -p tsconfig.build.json"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@hmharness/cognitive": "0.23.
|
|
19
|
-
"@hmharness/domain-harmony": "0.23.
|
|
20
|
-
"@hmharness/domain-ops": "0.23.
|
|
21
|
-
"@hmharness/evolution": "0.23.
|
|
22
|
-
"@hmharness/kernel": "0.23.
|
|
23
|
-
"@hmharness/observability": "0.23.
|
|
24
|
-
"@hmharness/sandbox": "0.23.
|
|
25
|
-
"@hmharness/lsp": "0.23.
|
|
26
|
-
"@hmharness/browser": "0.23.
|
|
27
|
-
"@hmharness/extension": "0.23.
|
|
18
|
+
"@hmharness/cognitive": "0.23.25",
|
|
19
|
+
"@hmharness/domain-harmony": "0.23.25",
|
|
20
|
+
"@hmharness/domain-ops": "0.23.25",
|
|
21
|
+
"@hmharness/evolution": "0.23.25",
|
|
22
|
+
"@hmharness/kernel": "0.23.25",
|
|
23
|
+
"@hmharness/observability": "0.23.25",
|
|
24
|
+
"@hmharness/sandbox": "0.23.25",
|
|
25
|
+
"@hmharness/lsp": "0.23.25",
|
|
26
|
+
"@hmharness/browser": "0.23.25",
|
|
27
|
+
"@hmharness/extension": "0.23.25"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist"
|