@jitsusama/agentic-harness.core 0.1.0 → 0.3.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.
Files changed (42) hide show
  1. package/dist/bin/cli.js +7 -0
  2. package/dist/bin/web.d.ts +30 -0
  3. package/dist/bin/web.js +97 -0
  4. package/dist/observability/index.d.ts +8 -0
  5. package/dist/observability/index.js +8 -0
  6. package/dist/observability/ledger/index.d.ts +14 -0
  7. package/dist/observability/ledger/index.js +13 -0
  8. package/dist/observability/ledger/store.d.ts +50 -0
  9. package/dist/observability/ledger/store.js +573 -0
  10. package/dist/observability/ledger/types.d.ts +197 -0
  11. package/dist/observability/ledger/types.js +1 -0
  12. package/dist/observability/recorder.d.ts +9 -2
  13. package/dist/observability/recorder.js +11 -18
  14. package/dist/observability/store.d.ts +5 -3
  15. package/dist/observability/store.js +152 -55
  16. package/dist/observability/types.d.ts +32 -4
  17. package/dist/web/audit/index.d.ts +1 -0
  18. package/dist/web/audit/index.js +1 -0
  19. package/dist/web/audit/motion.d.ts +87 -0
  20. package/dist/web/audit/motion.js +239 -0
  21. package/dist/web/design/index.d.ts +1 -0
  22. package/dist/web/design/index.js +1 -0
  23. package/dist/web/design/typography.d.ts +71 -0
  24. package/dist/web/design/typography.js +221 -0
  25. package/dist/web/hydration/capture.d.ts +37 -0
  26. package/dist/web/hydration/capture.js +96 -0
  27. package/dist/web/hydration/index.d.ts +10 -0
  28. package/dist/web/hydration/index.js +10 -0
  29. package/dist/web/hydration/judge.d.ts +56 -0
  30. package/dist/web/hydration/judge.js +191 -0
  31. package/dist/web/index.d.ts +1 -0
  32. package/dist/web/index.js +1 -0
  33. package/dist/web/perf/index.d.ts +1 -1
  34. package/dist/web/perf/index.js +1 -1
  35. package/dist/web/perf/view.js +19 -1
  36. package/dist/web/perf/vitals.d.ts +29 -0
  37. package/dist/web/perf/vitals.js +70 -0
  38. package/dist/web/session.d.ts +31 -2
  39. package/dist/web/session.js +75 -2
  40. package/package.json +12 -3
  41. package/dist/memory/db.d.ts +0 -15
  42. package/dist/memory/db.js +0 -25
package/dist/bin/cli.js CHANGED
@@ -27,6 +27,7 @@ import { runNotesAction } from "./notes.js";
27
27
  import { processExec } from "./process-exec.js";
28
28
  import { runQuestAction } from "./quest.js";
29
29
  import { runSlackAuthLogin, runSlackAuthStatus } from "./slack-auth.js";
30
+ import { runWebCheck } from "./web.js";
30
31
  /** Where a loop's state lives when the caller doesn't override it. */
31
32
  const DEFAULT_STATE_FILE = ".agentic-harness/tdd-loop.json";
32
33
  /** Where quest state lives when the caller doesn't override it. */
@@ -218,6 +219,12 @@ async function main() {
218
219
  return;
219
220
  }
220
221
  }
222
+ if (options.domain === "web" && options.command === "check") {
223
+ const result = await runWebCheck(await readStdin());
224
+ process.stdout.write(`${JSON.stringify(result)}\n`);
225
+ process.exitCode = result.ok ? 0 : 1;
226
+ return;
227
+ }
221
228
  if (options.domain === "slack-auth") {
222
229
  if (options.command === "status") {
223
230
  process.stdout.write(`${JSON.stringify(await runSlackAuthStatus())}\n`);
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The web check commands: one browser, one page, one verdict.
3
+ *
4
+ * This is the CLI adapter for the judgment checks a hook-and-skill
5
+ * consumer cannot reach through a library import: it opens a
6
+ * session, runs one check against one URL and answers JSON with
7
+ * the same rendered report the pi tools show. Stateless per
8
+ * invocation like every other command here; the browser lives and
9
+ * dies inside the call.
10
+ */
11
+ /** The checks this command knows how to run. */
12
+ export declare const WEB_CHECK_KINDS: readonly ["motion", "typography", "hydration", "perf"];
13
+ /** What arrives on stdin. */
14
+ export interface WebCheckInput {
15
+ readonly kind: string;
16
+ readonly url: string;
17
+ /** For perf: how many loads to sample. Defaults to 3. */
18
+ readonly samples?: number;
19
+ }
20
+ /** What goes out on stdout. */
21
+ export interface WebCheckOutput {
22
+ readonly ok: boolean;
23
+ readonly kind?: string;
24
+ readonly url?: string;
25
+ /** The same rendered verdict the pi tools show. */
26
+ readonly report?: string;
27
+ readonly error?: string;
28
+ }
29
+ /** Run one web check against one URL. */
30
+ export declare function runWebCheck(raw: string): Promise<WebCheckOutput>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The web check commands: one browser, one page, one verdict.
3
+ *
4
+ * This is the CLI adapter for the judgment checks a hook-and-skill
5
+ * consumer cannot reach through a library import: it opens a
6
+ * session, runs one check against one URL and answers JSON with
7
+ * the same rendered report the pi tools show. Stateless per
8
+ * invocation like every other command here; the browser lives and
9
+ * dies inside the call.
10
+ */
11
+ import { tallyFindings } from "../web/audit/index.js";
12
+ import { analyseMotion } from "../web/audit/motion.js";
13
+ import { renderAudit } from "../web/audit/report.js";
14
+ import { analyseTypography, renderTypography, } from "../web/design/typography.js";
15
+ import { judgeHydration, renderHydration } from "../web/hydration/index.js";
16
+ import { measureSamples, renderVitals } from "../web/perf/index.js";
17
+ import { BrowserSession } from "../web/session.js";
18
+ /** The checks this command knows how to run. */
19
+ export const WEB_CHECK_KINDS = [
20
+ "motion",
21
+ "typography",
22
+ "hydration",
23
+ "perf",
24
+ ];
25
+ /** Perf samples when the caller does not say. */
26
+ const DEFAULT_SAMPLES = 3;
27
+ /** The most loads one perf call will pay for. */
28
+ const MAX_SAMPLES = 9;
29
+ function isKind(kind) {
30
+ return WEB_CHECK_KINDS.includes(kind);
31
+ }
32
+ async function checkOn(session, kind, input) {
33
+ if (kind === "perf") {
34
+ const wanted = Math.min(Math.max(1, Math.round(input.samples ?? DEFAULT_SAMPLES)), MAX_SAMPLES);
35
+ const samples = [await session.vitals()];
36
+ while (samples.length < wanted) {
37
+ const { failure } = await session.reload();
38
+ // A reload that failed ends the sampling rather than the
39
+ // check: the loads that did land are still a measurement.
40
+ if (failure)
41
+ break;
42
+ samples.push(await session.vitals());
43
+ }
44
+ const last = samples[samples.length - 1];
45
+ return renderVitals(last, measureSamples(samples));
46
+ }
47
+ if (kind === "motion") {
48
+ const findings = analyseMotion(await session.motionUnderReduce());
49
+ return renderAudit(findings, tallyFindings(findings), {
50
+ measured: "Emulated prefers-reduced-motion: reduce, reloaded, and read " +
51
+ "what was still moving.",
52
+ });
53
+ }
54
+ if (kind === "typography") {
55
+ const blocks = await session.typography();
56
+ return renderTypography(blocks, analyseTypography(blocks));
57
+ }
58
+ const capture = await session.hydration();
59
+ const lines = session
60
+ .logs()
61
+ .entries.map(({ item }) => ({ level: item.level, text: item.text }));
62
+ return renderHydration(judgeHydration(capture, lines));
63
+ }
64
+ /** Run one web check against one URL. */
65
+ export async function runWebCheck(raw) {
66
+ let input;
67
+ try {
68
+ input = JSON.parse(raw);
69
+ }
70
+ catch {
71
+ return { ok: false, error: "stdin must be JSON: { kind, url }" };
72
+ }
73
+ if (!input.url) {
74
+ return { ok: false, error: "url is required" };
75
+ }
76
+ if (!input.kind || !isKind(input.kind)) {
77
+ return {
78
+ ok: false,
79
+ error: `kind must be one of: ${WEB_CHECK_KINDS.join(", ")}`,
80
+ };
81
+ }
82
+ const session = await BrowserSession.open(`web-check-${process.pid}`);
83
+ try {
84
+ const { failure, status } = await session.navigate(input.url);
85
+ if (failure) {
86
+ return { ok: false, error: `Could not load ${input.url}: ${failure}` };
87
+ }
88
+ if (status !== undefined && status >= 400) {
89
+ return { ok: false, error: `${input.url} answered ${status}` };
90
+ }
91
+ const report = await checkOn(session, input.kind, input);
92
+ return { ok: true, kind: input.kind, url: input.url, report };
93
+ }
94
+ finally {
95
+ await session.close();
96
+ }
97
+ }
@@ -7,7 +7,15 @@
7
7
  * own database file. Rows are queryable on demand, roll up
8
8
  * into periodic per-model and per-persona summaries before
9
9
  * they age out, and drive a compact status-line figure.
10
+ *
11
+ * The ledger beside it covers the other side of the bill: the
12
+ * main loop's own turns, addressed by content so a total can
13
+ * be trusted.
14
+ *
15
+ * Reading a harness's log format into those turns is that
16
+ * harness's own business and lives in its package.
10
17
  */
18
+ export { type CallScope, type CostDimension, type CostSlice, type DroppedCallRecord, type LedgerTotal, openTurnStore, type PaybackReplay, type RecordOutcome, type Regret, type RegretReport, type RepeatedCall, type SessionRecord, type ToolCallRecord, type TurnKind, type TurnRecord, type TurnStore, type VerifierKind, type VerifierOutcome, } from "./ledger/index.js";
11
19
  export { type RunRecorder, type RunRecordInput, recordRunEverywhere, registerRunRecorder, runRecordFrom, } from "./recorder.js";
12
20
  export { openRunStore, type RunQuery, type RunStore } from "./store.js";
13
21
  export type { RunCost, RunRecord, RunRollup, RunSummary, RunTokens, VerifyOutcome, } from "./types.js";
@@ -7,6 +7,14 @@
7
7
  * own database file. Rows are queryable on demand, roll up
8
8
  * into periodic per-model and per-persona summaries before
9
9
  * they age out, and drive a compact status-line figure.
10
+ *
11
+ * The ledger beside it covers the other side of the bill: the
12
+ * main loop's own turns, addressed by content so a total can
13
+ * be trusted.
14
+ *
15
+ * Reading a harness's log format into those turns is that
16
+ * harness's own business and lives in its package.
10
17
  */
18
+ export { openTurnStore, } from "./ledger/index.js";
11
19
  export { recordRunEverywhere, registerRunRecorder, runRecordFrom, } from "./recorder.js";
12
20
  export { openRunStore } from "./store.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The ledger: a store of billable turns, and what one is.
3
+ *
4
+ * Cost is derived from the logs a harness already writes rather than
5
+ * recorded a second time, so there is exactly one writer of the truth
6
+ * and the ledger can be rebuilt from scratch whenever its shape
7
+ * changes.
8
+ *
9
+ * Reading those logs is not here. A turn is a portable idea; the format
10
+ * it was written in is not, so each harness parses its own and hands
11
+ * over records. This module knows what a turn is and where to keep it.
12
+ */
13
+ export { type CostDimension, type CostSlice, type LedgerTotal, openTurnStore, type RecordOutcome, type TurnStore, } from "./store.js";
14
+ export type { CallScope, DroppedCallRecord, PaybackReplay, Regret, RegretReport, RepeatedCall, SessionRecord, ToolCallRecord, TurnKind, TurnRecord, VerifierKind, VerifierOutcome, } from "./types.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The ledger: a store of billable turns, and what one is.
3
+ *
4
+ * Cost is derived from the logs a harness already writes rather than
5
+ * recorded a second time, so there is exactly one writer of the truth
6
+ * and the ledger can be rebuilt from scratch whenever its shape
7
+ * changes.
8
+ *
9
+ * Reading those logs is not here. A turn is a portable idea; the format
10
+ * it was written in is not, so each harness parses its own and hands
11
+ * over records. This module knows what a turn is and where to keep it.
12
+ */
13
+ export { openTurnStore, } from "./store.js";
@@ -0,0 +1,50 @@
1
+ import type { CallScope, DroppedCallRecord, PaybackReplay, RegretReport, RepeatedCall, SessionRecord, ToolCallRecord, TurnRecord, VerifierOutcome } from "./types.js";
2
+ /** What a dimension's slice of spend came to. */
3
+ export interface CostSlice {
4
+ readonly key: string;
5
+ readonly cost: number;
6
+ readonly turns: number;
7
+ }
8
+ /** Everything the ledger holds, with its own blind spots stated. */
9
+ export interface LedgerTotal {
10
+ readonly cost: number;
11
+ readonly turns: number;
12
+ /** Turns held with no cost, so a total can say what it is missing. */
13
+ readonly unmetered: number;
14
+ readonly cacheWriteTokens: number;
15
+ /** Of those, the ones billed at the one-hour rate. */
16
+ readonly cacheWrite1hTokens: number;
17
+ }
18
+ /** How many turns a write added, and how many it had already seen. */
19
+ export interface RecordOutcome {
20
+ readonly inserted: number;
21
+ readonly duplicates: number;
22
+ }
23
+ /** What a total or a slice may be narrowed to. */
24
+ export type CostDimension = "model" | "session" | "kind" | "day" | "repo" | "quest";
25
+ /** A content-addressed store of billable turns. */
26
+ export interface TurnStore {
27
+ recordTurns(turns: readonly TurnRecord[]): Promise<RecordOutcome>;
28
+ recordSession(session: SessionRecord): Promise<void>;
29
+ recordCalls(calls: readonly ToolCallRecord[]): Promise<RecordOutcome>;
30
+ queryCalls(): Promise<ToolCallRecord[]>;
31
+ /** Arguments asked more than once in one session, heaviest first. */
32
+ repeatedCalls(scope?: CallScope): Promise<RepeatedCall[]>;
33
+ recordDropped(dropped: readonly DroppedCallRecord[]): Promise<RecordOutcome>;
34
+ queryDropped(): Promise<DroppedCallRecord[]>;
35
+ /** Dropped calls asked again after the drop, with how many could have been. */
36
+ regret(scope?: CallScope): Promise<RegretReport>;
37
+ /** Pass, fail and unknown counts per verifier kind that ran at all. */
38
+ verifierOutcomes(): Promise<VerifierOutcome[]>;
39
+ /** How real compactions compare against the payback test. */
40
+ paybackReplay(): Promise<PaybackReplay>;
41
+ total(): Promise<LedgerTotal>;
42
+ costBy(dimension: CostDimension): Promise<CostSlice[]>;
43
+ close(): Promise<void>;
44
+ }
45
+ /**
46
+ * Open (creating if needed) a turn ledger at the given path. Safe to
47
+ * point at the same file as the run store: the tables are disjoint and
48
+ * WAL keeps readers clear of the single writer.
49
+ */
50
+ export declare function openTurnStore(dbPath: string): Promise<TurnStore>;