Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/pi-harness/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"name": "@openzosma/pi-harness",
"name": "pi-harness",
"license": "Apache-2.0",
"keywords": ["pi-harness", "pi-coding-agent", "ai", "agent", "server", "sse"],
"repository": {
"type": "git",
"url": "https://github.com/zosmaai/openzosma.git",
"directory": "packages/pi-harness"
},
"version": "0.1.0",
"type": "module",
"description": "The top-level harness for the Pi ecosystem. Run pi-coding-agent and other Pi packages headlessly as a background HTTP/SSE server. Built with gratitude for Mario Zechner's pi-mono.",
Expand Down Expand Up @@ -35,12 +42,11 @@
},
"dependencies": {
"@hono/node-server": "^1.19.13",
"@openzosma/agents": "workspace:*",
"@openzosma/logger": "workspace:*",
"chalk": "^5.5.0",
"hono": "^4.12.12"
},
"devDependencies": {
"@openzosma/agents": "workspace:*",
"@types/node": "^22.15.2",
"esbuild": "^0.25.0",
"tsx": "^4.21.0",
Expand Down
26 changes: 20 additions & 6 deletions packages/pi-harness/scripts/build-bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* node scripts/build-bundle.mjs
*/

import { readFileSync, writeFileSync } from "node:fs"
import { readFileSync, unlinkSync, writeFileSync } from "node:fs"
import { resolve } from "node:path"
import { fileURLToPath } from "node:url"
import * as esbuild from "esbuild"
Expand All @@ -34,7 +34,7 @@ const EXTERNAL = [
"*.node",
]

async function bundleEntry(entry, outName) {
async function bundleEntry(entry, outName, { shebang = true } = {}) {
console.log(`📦 Bundling ${entry}...`)
await esbuild.build({
entryPoints: [resolve(__dirname, `../src/${entry}`)],
Expand All @@ -49,15 +49,29 @@ async function bundleEntry(entry, outName) {
sourcemap: true,
})

// Prepend shebang
const outPath = resolve(outDir, outName)
const content = readFileSync(outPath, "utf-8")
writeFileSync(outPath, `#!/usr/bin/env node\n${content}`, { mode: 0o755 })
if (shebang) {
const outPath = resolve(outDir, outName)
const content = readFileSync(outPath, "utf-8")
writeFileSync(outPath, `#!/usr/bin/env node\n${content}`, { mode: 0o755 })
}
}

async function main() {
await bundleEntry("cli.ts", "cli.js")
await bundleEntry("index.ts", "index.js")
await bundleEntry("server.ts", "server.js", { shebang: false })

// Remove internal session-manager outputs that reference unpublished workspace packages.
// session-manager is bundled into server.js/index.js and not part of the public API.
const toRemove = ["session-manager.d.ts", "session-manager.d.ts.map", "session-manager.js", "session-manager.js.map"]
for (const file of toRemove) {
try {
unlinkSync(resolve(outDir, file))
console.log(` 🧹 Removed ${file}`)
} catch {
/* ignore if already absent */
}
}

console.log(`\n✅ Bundles created in ${outDir}`)
console.log(` External deps: ${EXTERNAL.join(", ")}`)
Expand Down
2 changes: 1 addition & 1 deletion packages/pi-harness/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { closeSync, existsSync, openSync, readFileSync, writeFileSync } from "no
import { resolve } from "node:path"
import { fileURLToPath } from "node:url"
import { serve } from "@hono/node-server"
import { createLogger } from "@openzosma/logger"
import { loadConfig } from "./config.js"
import { createLogger } from "./logger.js"
import { createHarnessApp } from "./server.js"

const C = {
Expand Down
2 changes: 1 addition & 1 deletion packages/pi-harness/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
* Environment variables: see src/config.ts
*/
import { serve } from "@hono/node-server"
import { createLogger } from "@openzosma/logger"
import { loadConfig } from "./config.js"
import { createLogger } from "./logger.js"
import { createHarnessApp } from "./server.js"

const log = createLogger({ component: "pi-harness" })
Expand Down
71 changes: 71 additions & 0 deletions packages/pi-harness/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Minimal inline logger for pi-harness.
*
* Replaces @openzosma/logger so pi-harness can be published as a
* standalone package without unpublished workspace dependencies.
*/

export interface Logger {
debug(message: string, data?: Record<string, unknown>): void
info(message: string, data?: Record<string, unknown>): void
warn(message: string, data?: Record<string, unknown>): void
error(message: string, data?: Record<string, unknown>): void
fatal(message: string, data?: Record<string, unknown>): void
child(context: Record<string, unknown>): Logger
}

export interface LoggerConfig {
component: string
level?: "debug" | "info" | "warn" | "error" | "fatal"
}

const LEVELS: Record<string, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
fatal: 4,
}

function resolveLevel(): number {
const env = process.env.LOG_LEVEL?.toLowerCase() ?? "info"
return LEVELS[env] ?? 1
}

function formatLevel(level: string): string {
return level.toUpperCase().padStart(5, " ")
}

function formatData(data?: Record<string, unknown>): string {
if (!data || Object.keys(data).length === 0) return ""
const parts = Object.entries(data).map(([k, v]) => {
if (typeof v === "string") return `${k}="${v}"`
return `${k}=${String(v)}`
})
return ` ${parts.join(" ")}`
}

export function createLogger(config: LoggerConfig, parentContext?: Record<string, unknown>): Logger {
const minLevel = LEVELS[config.level ?? ""] ?? resolveLevel()
const baseContext = parentContext ?? {}

const emit = (level: string, message: string, data?: Record<string, unknown>): void => {
if ((LEVELS[level] ?? 1) < minLevel) return
const ts = new Date().toISOString()
const line = `[${ts}] ${formatLevel(level)} [${config.component}] ${message}${formatData({ ...baseContext, ...data })}`
if (level === "error" || level === "fatal") {
process.stderr.write(`${line}\n`)
} else {
process.stdout.write(`${line}\n`)
}
}

return {
debug: (message, data) => emit("debug", message, data),
info: (message, data) => emit("info", message, data),
warn: (message, data) => emit("warn", message, data),
error: (message, data) => emit("error", message, data),
fatal: (message, data) => emit("fatal", message, data),
child: (context) => createLogger(config, { ...baseContext, ...context }),
}
}
2 changes: 1 addition & 1 deletion packages/pi-harness/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createLogger } from "@openzosma/logger"
import { Hono } from "hono"
import { streamSSE } from "hono/streaming"
import type { HarnessConfig } from "./config.js"
import { createLogger } from "./logger.js"
import { HarnessSessionManager } from "./session-manager.js"
import type { CreateSessionRequest, SendMessageRequest } from "./types.js"

Expand Down
2 changes: 1 addition & 1 deletion packages/pi-harness/src/session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { existsSync, mkdirSync } from "node:fs"
import { resolve } from "node:path"
import type { AgentSession, AgentStreamEvent } from "@openzosma/agents"
import { PiAgentProvider } from "@openzosma/agents"
import { createLogger } from "@openzosma/logger"
import type { HarnessConfig } from "./config.js"
import { createLogger } from "./logger.js"

const log = createLogger({ component: "pi-harness" })

Expand Down
9 changes: 3 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading