feat(core): reserved-port and API-key guardrails in the default system prompt (#24)
Co-authored-by: Alice <alice@prismshadow.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user