pi-reason-harness 1.0.1

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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * pi-reason-harness — Lifecycle helpers
3
+ */
4
+
5
+ import { homedir } from 'node:os';
6
+ import * as fs from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
9
+
10
+ export function getDirs() {
11
+ const baseDir = join(process.cwd(), '.pi', 'reason-harness');
12
+ return { base: baseDir };
13
+ }
14
+
15
+ export function writeSessionId(ctx: ExtensionContext): void {
16
+ try {
17
+ const dirs = getDirs();
18
+ const sessionId = ctx.sessionManager.getSessionId();
19
+ if (sessionId) {
20
+ if (!fs.existsSync(dirs.base)) fs.mkdirSync(dirs.base, { recursive: true });
21
+ fs.writeFileSync(join(dirs.base, 'session-id'), sessionId, 'utf-8');
22
+ }
23
+ } catch {}
24
+ }
25
+
26
+ export function installShellAlias(cliPath: string, projectRoot: string): void {
27
+ try {
28
+ const agentBinDir = join(homedir(), '.pi', 'agent', 'bin');
29
+ if (!fs.existsSync(agentBinDir)) {
30
+ fs.mkdirSync(agentBinDir, { recursive: true });
31
+ }
32
+ const linkPath = join(agentBinDir, 'pi-reason-harness');
33
+
34
+ const wrapperContent = `#!/bin/sh
35
+ cd "${projectRoot}" 2>/dev/null
36
+ exec npx tsx "${cliPath}" "$@"
37
+ `;
38
+
39
+ let currentContent: string | null = null;
40
+ try {
41
+ currentContent = fs.readFileSync(linkPath, 'utf-8');
42
+ } catch {}
43
+ if (currentContent !== wrapperContent) {
44
+ fs.writeFileSync(linkPath, wrapperContent, { mode: 0o755 });
45
+ }
46
+ } catch {}
47
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * pi-reason-harness — Runtime state management
3
+ */
4
+
5
+ import type { ReasonHarnessRuntime } from '../types/index.js';
6
+
7
+ export function createRuntimeState(): ReasonHarnessRuntime {
8
+ return {
9
+ sessionName: null,
10
+ taskType: null,
11
+ status: 'idle',
12
+ iterationCount: 0,
13
+ bestScore: 0,
14
+ solved: false,
15
+ expertCount: 0,
16
+ models: [],
17
+ totalTokens: 0,
18
+ totalCost: 0,
19
+ adaptations: 0,
20
+ budget: {
21
+ costUsed: 0,
22
+ timeUsed: 0,
23
+ problemsSolved: 0,
24
+ problemsAttempted: 0,
25
+ },
26
+ };
27
+ }
28
+
29
+ export interface RuntimeStore {
30
+ ensure(key: string): ReasonHarnessRuntime;
31
+ clear(key: string): void;
32
+ }
33
+
34
+ export function createRuntimeStore(): RuntimeStore {
35
+ const runtimes = new Map<string, ReasonHarnessRuntime>();
36
+ return {
37
+ ensure(key: string): ReasonHarnessRuntime {
38
+ let runtime = runtimes.get(key);
39
+ if (!runtime) {
40
+ runtime = createRuntimeState();
41
+ runtimes.set(key, runtime);
42
+ }
43
+ return runtime;
44
+ },
45
+ clear(key: string): void {
46
+ runtimes.delete(key);
47
+ },
48
+ };
49
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * pi-reason-harness — Registered tools
3
+ */
4
+
5
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
6
+ import { Type } from '@sinclair/typebox';
7
+
8
+ export function registerTools(pi: ExtensionAPI): void {
9
+ pi.registerTool({
10
+ name: 'reason_harness_init',
11
+ label: 'Reason Harness Init',
12
+ description: 'Initialize a reasoning harness session for iterative solve-verify-feedback problem solving. Creates experts with verification strategies.',
13
+ parameters: Type.Object({
14
+ name: Type.String({ description: 'Session name' }),
15
+ type: Type.Union([
16
+ Type.Literal('code-reasoning'),
17
+ Type.Literal('knowledge-extraction'),
18
+ Type.Literal('hybrid'),
19
+ ], { description: 'Task type' }),
20
+ models: Type.Array(Type.String(), { description: 'Models to use (provider/id format, e.g. "anthropic/claude-sonnet-4-5")', default: ['openai/gpt-4o'] }),
21
+ numExperts: Type.Number({ description: 'Number of parallel experts', default: 1 }),
22
+ verification: Type.Union([
23
+ Type.Literal('sandbox'),
24
+ Type.Literal('self-audit'),
25
+ Type.Literal('external'),
26
+ Type.Literal('none'),
27
+ ], { description: 'Verification method', default: 'sandbox' }),
28
+ }),
29
+ execute: async (_toolCallId, params, _signal?, _onUpdate?) => {
30
+ const { execFile } = await import('node:child_process');
31
+ const payload = JSON.stringify({ action: 'init', ...params });
32
+ return new Promise((resolve) => {
33
+ execFile('pi-reason-harness', [payload], { timeout: 10000 }, (err, stdout, stderr) => {
34
+ if (err) {
35
+ resolve({ content: [{ type: 'text' as const, text: `Error: ${stderr || err.message}` }], details: {} });
36
+ } else {
37
+ resolve({ content: [{ type: 'text' as const, text: stdout.trim() }], details: {} });
38
+ }
39
+ });
40
+ });
41
+ },
42
+ });
43
+
44
+ pi.registerTool({
45
+ name: 'reason_harness_solve',
46
+ label: 'Reason Harness Solve',
47
+ description: 'Run the iterative solve-verify-feedback loop on a problem. Generates candidate solutions, verifies them, builds feedback from failures, and votes across parallel experts.',
48
+ parameters: Type.Object({
49
+ problem: Type.String({ description: 'The problem to solve' }),
50
+ trainInputs: Type.Array(Type.Any(), { description: 'Training input data for verification' }),
51
+ trainOutputs: Type.Array(Type.Any(), { description: 'Training output data (ground truth) for verification' }),
52
+ testInputs: Type.Array(Type.Any(), { description: 'Test input data' }),
53
+ }),
54
+ execute: async (_toolCallId, params, _signal?, _onUpdate?) => {
55
+ const { execFile } = await import('node:child_process');
56
+ const payload = JSON.stringify({ action: 'solve', ...params });
57
+ return new Promise((resolve) => {
58
+ execFile('pi-reason-harness', [payload], { timeout: 600000 }, (err, stdout, stderr) => {
59
+ if (err) {
60
+ resolve({ content: [{ type: 'text' as const, text: `Error: ${stderr || err.message}` }], details: {} });
61
+ } else {
62
+ resolve({ content: [{ type: 'text' as const, text: stdout.trim() }], details: {} });
63
+ }
64
+ });
65
+ });
66
+ },
67
+ });
68
+
69
+ pi.registerTool({
70
+ name: 'reason_harness_status',
71
+ label: 'Reason Harness Status',
72
+ description: 'Check the current reasoning harness session status, including iterations, scores, cost, and learned strategy adaptations.',
73
+ parameters: Type.Object({}),
74
+ execute: async (_toolCallId, _params, _signal?, _onUpdate?) => {
75
+ const { execFile } = await import('node:child_process');
76
+ const payload = JSON.stringify({ action: 'status' });
77
+ return new Promise((resolve) => {
78
+ execFile('pi-reason-harness', [payload], { timeout: 10000 }, (err, stdout, stderr) => {
79
+ if (err) {
80
+ resolve({ content: [{ type: 'text' as const, text: `Error: ${stderr || err.message}` }], details: {} });
81
+ } else {
82
+ resolve({ content: [{ type: 'text' as const, text: stdout.trim() }], details: {} });
83
+ }
84
+ });
85
+ });
86
+ },
87
+ });
88
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * pi-reason-harness — Shared types
3
+ */
4
+
5
+ export interface ReasonHarnessRuntime {
6
+ sessionName: string | null;
7
+ taskType: string | null;
8
+ status: string;
9
+ iterationCount: number;
10
+ bestScore: number;
11
+ solved: boolean;
12
+ expertCount: number;
13
+ models: string[];
14
+ totalTokens: number;
15
+ totalCost: number;
16
+ adaptations: number;
17
+ budget: {
18
+ costUsed: number;
19
+ timeUsed: number;
20
+ problemsSolved: number;
21
+ problemsAttempted: number;
22
+ };
23
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * pi-reason-harness — UI widgets
3
+ */
4
+
5
+ import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
6
+ import { Text, truncateToWidth } from '@earendil-works/pi-tui';
7
+ import type { ReasonHarnessRuntime } from '../types/index.js';
8
+
9
+ export function createWidgetUpdater(
10
+ getRuntime: (ctx: ExtensionContext) => ReasonHarnessRuntime
11
+ ) {
12
+ return function updateWidget(extCtx: ExtensionContext): void {
13
+ if (!extCtx.hasUI) return;
14
+ const runtime = getRuntime(extCtx);
15
+ const width = process.stdout.columns || 120;
16
+
17
+ if (!runtime.sessionName) {
18
+ extCtx.ui.setWidget('reason-harness', undefined);
19
+ return;
20
+ }
21
+
22
+ extCtx.ui.setWidget('reason-harness', (_tui, theme) => {
23
+ const parts = [
24
+ theme.fg('accent', '🧠'),
25
+ theme.fg('text', ` ${runtime.sessionName}`),
26
+ theme.fg('dim', ` │ ${runtime.taskType}`),
27
+ theme.fg('dim', ' │ '),
28
+ theme.fg(
29
+ runtime.status === 'solving' ? 'warning' : runtime.solved ? 'success' : 'muted',
30
+ runtime.status
31
+ ),
32
+ theme.fg('dim', ' │ '),
33
+ theme.fg('muted', `E:${runtime.expertCount}`),
34
+ theme.fg('dim', ' │ '),
35
+ theme.fg(runtime.solved ? 'success' : 'warning', `★ ${runtime.bestScore.toFixed(2)}`),
36
+ theme.fg('dim', ` │ ${runtime.iterationCount} iters`),
37
+ ];
38
+
39
+ if (runtime.totalTokens > 0) {
40
+ const tokStr =
41
+ runtime.totalTokens > 1000000
42
+ ? `${(runtime.totalTokens / 1000000).toFixed(1)}M`
43
+ : runtime.totalTokens > 1000
44
+ ? `${(runtime.totalTokens / 1000).toFixed(1)}K`
45
+ : `${runtime.totalTokens}`;
46
+ parts.push(theme.fg('dim', ` │ ${tokStr} tok`));
47
+ }
48
+
49
+ if (runtime.totalCost > 0) {
50
+ const costStr =
51
+ runtime.totalCost < 0.01
52
+ ? `$${runtime.totalCost.toFixed(4)}`
53
+ : `$${runtime.totalCost.toFixed(2)}`;
54
+ parts.push(theme.fg('dim', ` │ ${costStr}`));
55
+ }
56
+
57
+ if (runtime.adaptations > 0) {
58
+ parts.push(theme.fg('accent', ` │ 🧠${runtime.adaptations}`));
59
+ }
60
+
61
+ if (runtime.solved) {
62
+ parts.push(theme.fg('success', ' ✓'));
63
+ }
64
+
65
+ parts.push(theme.fg('dim', ' (ctrl+shift+r)'));
66
+
67
+ return new Text(truncateToWidth(parts.join(''), width), width);
68
+ });
69
+ };
70
+ }
71
+
72
+ export function clearSessionUi(extCtx: ExtensionContext): void {
73
+ if (extCtx.hasUI) {
74
+ extCtx.ui.setWidget('reason-harness', undefined);
75
+ }
76
+ }