diff --git a/packages/cli/src/commands/serve.ts b/packages/cli/src/commands/serve.ts index 67bc891..acd7e82 100644 --- a/packages/cli/src/commands/serve.ts +++ b/packages/cli/src/commands/serve.ts @@ -14,11 +14,12 @@ * Docs: /docs/cli § "penguin server / penguin web". */ import { spawn } from "node:child_process"; +import { DEFAULT_SERVER_PORT } from "@prismshadow/penguin-core"; import type { Command } from "commander"; import type { Messages } from "../i18n.js"; -/** Default service port (deliberately avoids common defaults like 3000/8080). */ -export const DEFAULT_PORT = 7364; +/** Default service port — core's DEFAULT_SERVER_PORT (7364), the single source of truth. */ +export const DEFAULT_PORT = DEFAULT_SERVER_PORT; /** Default service listen host. */ export const DEFAULT_HOST = "127.0.0.1"; diff --git a/packages/cli/test/serve.test.ts b/packages/cli/test/serve.test.ts index 3cd9702..deffc4a 100644 --- a/packages/cli/test/serve.test.ts +++ b/packages/cli/test/serve.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { Command } from "commander"; +import { DEFAULT_SERVER_PORT } from "@prismshadow/penguin-core"; import { DEFAULT_HOST, DEFAULT_PORT, @@ -11,6 +12,9 @@ import { import { getMessages } from "../src/i18n.js"; describe("resolvePort (option > env var > default 7364)", () => { + it("derives DEFAULT_PORT from core's DEFAULT_SERVER_PORT", () => { + expect(DEFAULT_PORT).toBe(DEFAULT_SERVER_PORT); + }); it("uses the default 7364 when neither is given", () => { expect(DEFAULT_PORT).toBe(7364); expect(resolvePort(undefined, undefined)).toBe(7364); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fef74c0..430d953 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -19,6 +19,9 @@ export * from "./omnimessage/index.js"; export * from "./interfaces.js"; +// Only the default server port leaves internal: the CLI / server default-port source of truth. +export { DEFAULT_SERVER_PORT } from "./internal/ports.js"; + // Submodules export * from "./state/index.js"; export * from "./llm/index.js"; diff --git a/packages/core/src/internal/ports.ts b/packages/core/src/internal/ports.ts new file mode 100644 index 0000000..9f29840 --- /dev/null +++ b/packages/core/src/internal/ports.ts @@ -0,0 +1,10 @@ +/** + * Default PenguinHarness server port (internal shared constant; the barrel re-exports + * only DEFAULT_SERVER_PORT, as the CLI `penguin server` / `penguin web` and server + * default-port source of truth — previously each hardcoded the number). It is a + * fallback only: the `--port` flag and the PORT environment variable override it at + * runtime. + */ + +/** Default main server / Web UI port; deliberately avoids common defaults like 3000/8080. */ +export const DEFAULT_SERVER_PORT = 7364; diff --git a/packages/core/src/state/default-config.ts b/packages/core/src/state/default-config.ts index c2242e5..1068265 100644 --- a/packages/core/src/state/default-config.ts +++ b/packages/core/src/state/default-config.ts @@ -92,13 +92,15 @@ Communicate with the user precisely and concisely, yet with warmth. Do not repea # Constraints - Make the smallest change that satisfies the request; do not modify unrelated files. - Destructive operations are forbidden. -- Never kill a process you did not start yourself (e.g. to free a busy port) unless the user explicitly asks you to. +- Never kill a process you did not start yourself unless the user explicitly asks you to, including PenguinHarness's own services. Never take a PenguinHarness service port for your own servers; when a port you want is busy, pick another free port instead of killing the listener. - If a tool call fails, read the error, adjust, and retry; never repeat the same failing input. +- On an API authentication/authorization or API-key error (401/403, invalid or missing key), retry at most once. # Stop rules - Stop and give the final answer once the success criteria are met. - If the request is ambiguous, stop and ask the user for clarification instead of guessing their intent. - If you hit an error you cannot resolve, stop and report the blocker to the user. +- If an API auth/key error persists after that one retry, stop calling tools and ask the user to update the key in the agent's vault or the model settings outside the chat — the secret value must never be pasted into the conversation. Updated secrets only take effect in the next conversation, so further retries cannot succeed. # Tool use - Prefer solving problems with your tools: inspect the real files and environment and run real commands instead of answering from memory or guessing. diff --git a/packages/core/test/state.test.ts b/packages/core/test/state.test.ts index bf5f6cf..32365cb 100644 --- a/packages/core/test/state.test.ts +++ b/packages/core/test/state.test.ts @@ -315,6 +315,22 @@ describe("assembleSystemPrompt", () => { expect(prompt).not.toContain(DATE_PLACEHOLDER); }); + it("default prompt carries the port and API-key guardrails", async () => { + const state = await loadOrInitAgentState(); + const prompt = assembleSystemPrompt(state); + // Ports: never kill listeners or take PenguinHarness service ports; numbers are deliberately not listed. + expect(prompt).toContain("pick another free port"); + expect(prompt).toContain("PenguinHarness service port"); + expect(prompt).not.toContain("7364"); + // Auth/key failures: retry at most once (Constraints), then stop and ask the user to update + // the key outside the chat (Stop rules) — no CLI commands, no secret values in the conversation. + expect(prompt).toContain("retry at most once"); + expect(prompt).toContain("stop calling tools"); + expect(prompt).toContain("never be pasted into the conversation"); + expect(prompt).toContain("next conversation"); + expect(prompt).not.toContain("penguin config vault set"); + }); + it("replaces AGENTS.md and specific Session environment fields at template locations", () => { const state = { root: tmpRoot, diff --git a/packages/docs/vite.config.ts b/packages/docs/vite.config.ts index 3203475..fae4d6d 100644 --- a/packages/docs/vite.config.ts +++ b/packages/docs/vite.config.ts @@ -16,5 +16,7 @@ import { defineConfig } from "vite"; export default defineConfig({ base: process.env.BASE_PATH ?? "/", plugins: [react(), tailwindcss()], + // Fixed PenguinHarness dev port (stands alone — only the main server default is + // shared, as DEFAULT_SERVER_PORT in core; vite configs cannot import core TS). server: { port: 7367 }, }); diff --git a/packages/landing/vite.config.ts b/packages/landing/vite.config.ts index e41c441..3d05923 100644 --- a/packages/landing/vite.config.ts +++ b/packages/landing/vite.config.ts @@ -13,5 +13,7 @@ import { defineConfig } from "vite"; export default defineConfig({ base: process.env.BASE_PATH ?? "/", plugins: [react(), tailwindcss()], + // Fixed PenguinHarness dev port (stands alone — only the main server default is + // shared, as DEFAULT_SERVER_PORT in core; vite configs cannot import core TS). server: { port: 7366 }, }); diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 5644530..b7c4486 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -12,7 +12,7 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { resolveRoot } from "@prismshadow/penguin-core"; +import { DEFAULT_SERVER_PORT, resolveRoot } from "@prismshadow/penguin-core"; export interface ServerConfig { /** Local data root directory (shared with the SDK/CLI). */ @@ -52,7 +52,7 @@ export function resolveServerConfig(env: NodeJS.ProcessEnv = process.env): Serve // An empty PORT string is treated as unset (the common `.env` case of an empty // `PORT=`): Number("") === 0 would pass the range check and bind to a random // port; this matches the CLI's resolvePort convention. - const port = Number(env.PORT || 7364); + const port = Number(env.PORT || DEFAULT_SERVER_PORT); if (!Number.isInteger(port) || port < 0 || port > 65535) { throw new Error(`Invalid port configuration PORT=${env.PORT}`); } diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index bcd307c..64d595c 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -14,6 +14,8 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [react(), tailwindcss()], server: { + // Fixed PenguinHarness dev port (stands alone — only the main server default is + // shared, as DEFAULT_SERVER_PORT in core; vite configs cannot import core TS). port: 7365, proxy: { "/api": {