2026-04-02 09:11:49 -05:00
|
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
createAppMock,
|
|
|
|
|
createDbMock,
|
|
|
|
|
detectPortMock,
|
Tighten publicBaseUrl port rewriting (#4553)
## Thinking Path
> - Paperclip is a control plane for autonomous agent companies, so its
local and authenticated deployment behavior has to stay predictable
under port rebinding and worktree isolation.
> - This change sits in the server/worktree configuration path that
derives runtime URLs and auth origins from `auth.publicBaseUrl`.
> - The original hostname-port rewrite change fixed one real gap for
private/tailnet host:port worktree setups, but it widened the rewrite
rule too far.
> - Rewriting every explicit `auth.publicBaseUrl` can corrupt public or
reverse-proxy URLs by turning a stable origin like
`https://paperclip.example` into a local listen-port URL.
> - Paperclip's auth and trusted-origin handling depend on that URL
staying semantically correct, so this had to be narrowed before merge.
> - This pull request tightens the rewrite rule to explicit-port URLs
only and adds regression coverage across the CLI helper, worktree config
persistence, and server startup path.
> - The benefit is that private host:port worktree flows still work,
while public/default-port URLs remain stable and safe.
## What Changed
- Tightened `rewriteLocalUrlPort` in `cli/src/commands/worktree-lib.ts`,
`server/src/worktree-config.ts`, and `server/src/index.ts` so it only
rewrites URLs that already include an explicit port.
- Removed the old loopback-only hostname gate from the CLI/worktree
helpers and replaced it with the more precise `parsed.port` guard.
- Updated CLI helper coverage to assert that explicit-port non-loopback
URLs still rewrite while no-port public URLs stay unchanged.
- Expanded `server/src/__tests__/worktree-config.test.ts` to cover
explicit-port rewrite and no-port stability for both persisted worktree
config and in-memory runtime port selection.
- Added startup-path coverage in
`server/src/__tests__/server-startup-feedback-export.test.ts` for
`detect-port` rebinding with both explicit-port and no-port
`auth.publicBaseUrl` values.
## Verification
- `pnpm --filter @paperclipai/plugin-sdk build`
- `npx vitest run
server/src/__tests__/server-startup-feedback-export.test.ts`
- `npx vitest run cli/src/__tests__/worktree.test.ts
server/src/__tests__/worktree-config.test.ts`
- All of the above were run locally in this issue worktree and passed.
## Risks
- Low risk. The behavior change is deliberately narrower than the
reviewed broad-host rewrite and is guarded by regression coverage for
both the explicit-port and no-port cases.
- The main remaining risk is behavioral only if another code path starts
depending on port rewriting for URLs that never declared a port, which
would be a separate bug.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex local agent using `gpt-5.4` with high reasoning effort,
tool use, shell execution, and file editing.
- Anthropic Claude local agent using `claude-opus-4-6` for follow-up
code review approval on the implementation issue.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-26 14:29:22 -07:00
|
|
|
loadConfigMock,
|
2026-04-02 09:11:49 -05:00
|
|
|
feedbackExportServiceMock,
|
|
|
|
|
feedbackServiceFactoryMock,
|
|
|
|
|
fakeServer,
|
|
|
|
|
} = vi.hoisted(() => {
|
|
|
|
|
const createAppMock = vi.fn(async () => ((_: unknown, __: unknown) => {}) as never);
|
|
|
|
|
const createDbMock = vi.fn(() => ({}) as never);
|
|
|
|
|
const detectPortMock = vi.fn(async (port: number) => port);
|
Tighten publicBaseUrl port rewriting (#4553)
## Thinking Path
> - Paperclip is a control plane for autonomous agent companies, so its
local and authenticated deployment behavior has to stay predictable
under port rebinding and worktree isolation.
> - This change sits in the server/worktree configuration path that
derives runtime URLs and auth origins from `auth.publicBaseUrl`.
> - The original hostname-port rewrite change fixed one real gap for
private/tailnet host:port worktree setups, but it widened the rewrite
rule too far.
> - Rewriting every explicit `auth.publicBaseUrl` can corrupt public or
reverse-proxy URLs by turning a stable origin like
`https://paperclip.example` into a local listen-port URL.
> - Paperclip's auth and trusted-origin handling depend on that URL
staying semantically correct, so this had to be narrowed before merge.
> - This pull request tightens the rewrite rule to explicit-port URLs
only and adds regression coverage across the CLI helper, worktree config
persistence, and server startup path.
> - The benefit is that private host:port worktree flows still work,
while public/default-port URLs remain stable and safe.
## What Changed
- Tightened `rewriteLocalUrlPort` in `cli/src/commands/worktree-lib.ts`,
`server/src/worktree-config.ts`, and `server/src/index.ts` so it only
rewrites URLs that already include an explicit port.
- Removed the old loopback-only hostname gate from the CLI/worktree
helpers and replaced it with the more precise `parsed.port` guard.
- Updated CLI helper coverage to assert that explicit-port non-loopback
URLs still rewrite while no-port public URLs stay unchanged.
- Expanded `server/src/__tests__/worktree-config.test.ts` to cover
explicit-port rewrite and no-port stability for both persisted worktree
config and in-memory runtime port selection.
- Added startup-path coverage in
`server/src/__tests__/server-startup-feedback-export.test.ts` for
`detect-port` rebinding with both explicit-port and no-port
`auth.publicBaseUrl` values.
## Verification
- `pnpm --filter @paperclipai/plugin-sdk build`
- `npx vitest run
server/src/__tests__/server-startup-feedback-export.test.ts`
- `npx vitest run cli/src/__tests__/worktree.test.ts
server/src/__tests__/worktree-config.test.ts`
- All of the above were run locally in this issue worktree and passed.
## Risks
- Low risk. The behavior change is deliberately narrower than the
reviewed broad-host rewrite and is guarded by regression coverage for
both the explicit-port and no-port cases.
- The main remaining risk is behavioral only if another code path starts
depending on port rewriting for URLs that never declared a port, which
would be a separate bug.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex local agent using `gpt-5.4` with high reasoning effort,
tool use, shell execution, and file editing.
- Anthropic Claude local agent using `claude-opus-4-6` for follow-up
code review approval on the implementation issue.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-26 14:29:22 -07:00
|
|
|
const loadConfigMock = vi.fn(() => ({
|
|
|
|
|
deploymentMode: "authenticated",
|
|
|
|
|
deploymentExposure: "private",
|
|
|
|
|
bind: "loopback",
|
|
|
|
|
customBindHost: undefined,
|
|
|
|
|
host: "127.0.0.1",
|
|
|
|
|
port: 3210,
|
|
|
|
|
allowedHostnames: [],
|
|
|
|
|
authBaseUrlMode: "auto",
|
|
|
|
|
authPublicBaseUrl: undefined,
|
|
|
|
|
authDisableSignUp: false,
|
|
|
|
|
databaseMode: "postgres",
|
|
|
|
|
databaseUrl: "postgres://paperclip:paperclip@127.0.0.1:5432/paperclip",
|
|
|
|
|
embeddedPostgresDataDir: "/tmp/paperclip-test-db",
|
|
|
|
|
embeddedPostgresPort: 54329,
|
|
|
|
|
databaseBackupEnabled: false,
|
|
|
|
|
databaseBackupIntervalMinutes: 60,
|
|
|
|
|
databaseBackupRetentionDays: 30,
|
|
|
|
|
databaseBackupDir: "/tmp/paperclip-test-backups",
|
|
|
|
|
serveUi: false,
|
|
|
|
|
uiDevMiddleware: false,
|
|
|
|
|
secretsProvider: "local_encrypted",
|
|
|
|
|
secretsStrictMode: false,
|
|
|
|
|
secretsMasterKeyFilePath: "/tmp/paperclip-master.key",
|
|
|
|
|
storageProvider: "local_disk",
|
|
|
|
|
storageLocalDiskBaseDir: "/tmp/paperclip-storage",
|
|
|
|
|
storageS3Bucket: "paperclip-test",
|
|
|
|
|
storageS3Region: "us-east-1",
|
|
|
|
|
storageS3Endpoint: undefined,
|
|
|
|
|
storageS3Prefix: "",
|
|
|
|
|
storageS3ForcePathStyle: false,
|
|
|
|
|
feedbackExportBackendUrl: "https://telemetry.example.com",
|
|
|
|
|
feedbackExportBackendToken: "telemetry-token",
|
|
|
|
|
heartbeatSchedulerEnabled: false,
|
|
|
|
|
heartbeatSchedulerIntervalMs: 30000,
|
|
|
|
|
companyDeletionEnabled: false,
|
|
|
|
|
}));
|
2026-04-02 09:11:49 -05:00
|
|
|
const feedbackExportServiceMock = {
|
|
|
|
|
flushPendingFeedbackTraces: vi.fn(async () => ({ attempted: 0, sent: 0, failed: 0 })),
|
|
|
|
|
};
|
|
|
|
|
const feedbackServiceFactoryMock = vi.fn(() => feedbackExportServiceMock);
|
|
|
|
|
const fakeServer = {
|
|
|
|
|
once: vi.fn().mockReturnThis(),
|
|
|
|
|
off: vi.fn().mockReturnThis(),
|
|
|
|
|
listen: vi.fn((_port: number, _host: string, callback?: () => void) => {
|
|
|
|
|
callback?.();
|
|
|
|
|
return fakeServer;
|
|
|
|
|
}),
|
|
|
|
|
close: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
createAppMock,
|
|
|
|
|
createDbMock,
|
|
|
|
|
detectPortMock,
|
Tighten publicBaseUrl port rewriting (#4553)
## Thinking Path
> - Paperclip is a control plane for autonomous agent companies, so its
local and authenticated deployment behavior has to stay predictable
under port rebinding and worktree isolation.
> - This change sits in the server/worktree configuration path that
derives runtime URLs and auth origins from `auth.publicBaseUrl`.
> - The original hostname-port rewrite change fixed one real gap for
private/tailnet host:port worktree setups, but it widened the rewrite
rule too far.
> - Rewriting every explicit `auth.publicBaseUrl` can corrupt public or
reverse-proxy URLs by turning a stable origin like
`https://paperclip.example` into a local listen-port URL.
> - Paperclip's auth and trusted-origin handling depend on that URL
staying semantically correct, so this had to be narrowed before merge.
> - This pull request tightens the rewrite rule to explicit-port URLs
only and adds regression coverage across the CLI helper, worktree config
persistence, and server startup path.
> - The benefit is that private host:port worktree flows still work,
while public/default-port URLs remain stable and safe.
## What Changed
- Tightened `rewriteLocalUrlPort` in `cli/src/commands/worktree-lib.ts`,
`server/src/worktree-config.ts`, and `server/src/index.ts` so it only
rewrites URLs that already include an explicit port.
- Removed the old loopback-only hostname gate from the CLI/worktree
helpers and replaced it with the more precise `parsed.port` guard.
- Updated CLI helper coverage to assert that explicit-port non-loopback
URLs still rewrite while no-port public URLs stay unchanged.
- Expanded `server/src/__tests__/worktree-config.test.ts` to cover
explicit-port rewrite and no-port stability for both persisted worktree
config and in-memory runtime port selection.
- Added startup-path coverage in
`server/src/__tests__/server-startup-feedback-export.test.ts` for
`detect-port` rebinding with both explicit-port and no-port
`auth.publicBaseUrl` values.
## Verification
- `pnpm --filter @paperclipai/plugin-sdk build`
- `npx vitest run
server/src/__tests__/server-startup-feedback-export.test.ts`
- `npx vitest run cli/src/__tests__/worktree.test.ts
server/src/__tests__/worktree-config.test.ts`
- All of the above were run locally in this issue worktree and passed.
## Risks
- Low risk. The behavior change is deliberately narrower than the
reviewed broad-host rewrite and is guarded by regression coverage for
both the explicit-port and no-port cases.
- The main remaining risk is behavioral only if another code path starts
depending on port rewriting for URLs that never declared a port, which
would be a separate bug.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex local agent using `gpt-5.4` with high reasoning effort,
tool use, shell execution, and file editing.
- Anthropic Claude local agent using `claude-opus-4-6` for follow-up
code review approval on the implementation issue.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-26 14:29:22 -07:00
|
|
|
loadConfigMock,
|
2026-04-02 09:11:49 -05:00
|
|
|
feedbackExportServiceMock,
|
|
|
|
|
feedbackServiceFactoryMock,
|
|
|
|
|
fakeServer,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
vi.mock("node:http", () => ({
|
|
|
|
|
createServer: vi.fn(() => fakeServer),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("detect-port", () => ({
|
|
|
|
|
default: detectPortMock,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("@paperclipai/db", () => ({
|
|
|
|
|
createDb: createDbMock,
|
|
|
|
|
ensurePostgresDatabase: vi.fn(),
|
|
|
|
|
getPostgresDataDirectory: vi.fn(),
|
|
|
|
|
inspectMigrations: vi.fn(async () => ({ status: "upToDate" })),
|
|
|
|
|
applyPendingMigrations: vi.fn(),
|
|
|
|
|
reconcilePendingMigrationHistory: vi.fn(async () => ({ repairedMigrations: [] })),
|
|
|
|
|
formatDatabaseBackupResult: vi.fn(() => "ok"),
|
|
|
|
|
runDatabaseBackup: vi.fn(),
|
|
|
|
|
authUsers: {},
|
|
|
|
|
companies: {},
|
|
|
|
|
companyMemberships: {},
|
|
|
|
|
instanceUserRoles: {},
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../app.js", () => ({
|
|
|
|
|
createApp: createAppMock,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../config.js", () => ({
|
Tighten publicBaseUrl port rewriting (#4553)
## Thinking Path
> - Paperclip is a control plane for autonomous agent companies, so its
local and authenticated deployment behavior has to stay predictable
under port rebinding and worktree isolation.
> - This change sits in the server/worktree configuration path that
derives runtime URLs and auth origins from `auth.publicBaseUrl`.
> - The original hostname-port rewrite change fixed one real gap for
private/tailnet host:port worktree setups, but it widened the rewrite
rule too far.
> - Rewriting every explicit `auth.publicBaseUrl` can corrupt public or
reverse-proxy URLs by turning a stable origin like
`https://paperclip.example` into a local listen-port URL.
> - Paperclip's auth and trusted-origin handling depend on that URL
staying semantically correct, so this had to be narrowed before merge.
> - This pull request tightens the rewrite rule to explicit-port URLs
only and adds regression coverage across the CLI helper, worktree config
persistence, and server startup path.
> - The benefit is that private host:port worktree flows still work,
while public/default-port URLs remain stable and safe.
## What Changed
- Tightened `rewriteLocalUrlPort` in `cli/src/commands/worktree-lib.ts`,
`server/src/worktree-config.ts`, and `server/src/index.ts` so it only
rewrites URLs that already include an explicit port.
- Removed the old loopback-only hostname gate from the CLI/worktree
helpers and replaced it with the more precise `parsed.port` guard.
- Updated CLI helper coverage to assert that explicit-port non-loopback
URLs still rewrite while no-port public URLs stay unchanged.
- Expanded `server/src/__tests__/worktree-config.test.ts` to cover
explicit-port rewrite and no-port stability for both persisted worktree
config and in-memory runtime port selection.
- Added startup-path coverage in
`server/src/__tests__/server-startup-feedback-export.test.ts` for
`detect-port` rebinding with both explicit-port and no-port
`auth.publicBaseUrl` values.
## Verification
- `pnpm --filter @paperclipai/plugin-sdk build`
- `npx vitest run
server/src/__tests__/server-startup-feedback-export.test.ts`
- `npx vitest run cli/src/__tests__/worktree.test.ts
server/src/__tests__/worktree-config.test.ts`
- All of the above were run locally in this issue worktree and passed.
## Risks
- Low risk. The behavior change is deliberately narrower than the
reviewed broad-host rewrite and is guarded by regression coverage for
both the explicit-port and no-port cases.
- The main remaining risk is behavioral only if another code path starts
depending on port rewriting for URLs that never declared a port, which
would be a separate bug.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex local agent using `gpt-5.4` with high reasoning effort,
tool use, shell execution, and file editing.
- Anthropic Claude local agent using `claude-opus-4-6` for follow-up
code review approval on the implementation issue.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-26 14:29:22 -07:00
|
|
|
loadConfig: loadConfigMock,
|
2026-04-02 09:11:49 -05:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../middleware/logger.js", () => ({
|
|
|
|
|
logger: {
|
Add sandbox environment support (#4415)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The environment/runtime layer decides where agent work executes and
how the control plane reaches those runtimes.
> - Today Paperclip can run locally and over SSH, but sandboxed
execution needs a first-class environment model instead of one-off
adapter behavior.
> - We also want sandbox providers to be pluggable so the core does not
hardcode every provider implementation.
> - This branch adds the Sandbox environment path, the provider
contract, and a deterministic fake provider plugin.
> - That required synchronized changes across shared contracts, plugin
SDK surfaces, server runtime orchestration, and the UI
environment/workspace flows.
> - The result is that sandbox execution becomes a core control-plane
capability while keeping provider implementations extensible and
testable.
## What Changed
- Added sandbox runtime support to the environment execution path,
including runtime URL discovery, sandbox execution targeting,
orchestration, and heartbeat integration.
- Added plugin-provider support for sandbox environments so providers
can be supplied via plugins instead of hardcoded server logic.
- Added the fake sandbox provider plugin with deterministic behavior
suitable for local and automated testing.
- Updated shared types, validators, plugin protocol definitions, and SDK
helpers to carry sandbox provider and workspace-runtime contracts across
package boundaries.
- Updated server routes and services so companies can create sandbox
environments, select them for work, and execute work through the sandbox
runtime path.
- Updated the UI environment and workspace surfaces to expose sandbox
environment configuration and selection.
- Added test coverage for sandbox runtime behavior, provider seams,
environment route guards, orchestration, and the fake provider plugin.
## Verification
- Ran locally before the final fixture-only scrub:
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`
- Ran locally after the final scrub amend:
- `pnpm vitest run server/src/__tests__/runtime-api.test.ts`
- Reviewer spot checks:
- create a sandbox environment backed by the fake provider plugin
- run work through that environment
- confirm sandbox provider execution does not inherit host secrets
implicitly
## Risks
- This touches shared contracts, plugin SDK plumbing, server runtime
orchestration, and UI environment/workspace flows, so regressions would
likely show up as cross-layer mismatches rather than isolated type
errors.
- Runtime URL discovery and sandbox callback selection are sensitive to
host/bind configuration; if that logic is wrong, sandbox-backed
callbacks may fail even when execution succeeds.
- The fake provider plugin is intentionally deterministic and
test-oriented; future providers may expose capability gaps that this
branch does not yet cover.
## Model Used
- OpenAI Codex coding agent on a GPT-5-class backend in the
Paperclip/Codex harness. Exact backend model ID is not exposed
in-session. Tool-assisted workflow with shell execution, file editing,
git history inspection, and local test execution.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-24 12:15:53 -07:00
|
|
|
child: vi.fn(function child() {
|
|
|
|
|
return this;
|
|
|
|
|
}),
|
2026-04-02 09:11:49 -05:00
|
|
|
info: vi.fn(),
|
|
|
|
|
warn: vi.fn(),
|
|
|
|
|
error: vi.fn(),
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../realtime/live-events-ws.js", () => ({
|
|
|
|
|
setupLiveEventsWebSocketServer: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../services/index.js", () => ({
|
|
|
|
|
feedbackService: feedbackServiceFactoryMock,
|
|
|
|
|
heartbeatService: vi.fn(() => ({
|
|
|
|
|
reapOrphanedRuns: vi.fn(async () => undefined),
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
promoteDueScheduledRetries: vi.fn(async () => ({ promoted: 0, runIds: [] })),
|
2026-04-02 09:11:49 -05:00
|
|
|
resumeQueuedRuns: vi.fn(async () => undefined),
|
[codex] Harden execution reliability and heartbeat tooling (#3679)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Reliable execution depends on heartbeat routing, issue lifecycle
semantics, telemetry, and a fast enough local verification loop to keep
regressions visible
> - The remaining commits on this branch were mostly server/runtime
correctness fixes plus test and documentation follow-ups in that area
> - Those changes are logically separate from the UI-focused
issue-detail and workspace/navigation branches even when they touch
overlapping issue APIs
> - This pull request groups the execution reliability, heartbeat,
telemetry, and tooling changes into one standalone branch
> - The benefit is a focused review of the control-plane correctness
work, including the follow-up fix that restored the implicit
comment-reopen helpers after branch splitting
## What Changed
- Hardened issue/heartbeat execution behavior, including self-review
stage skipping, deferred mention wakes during active execution, stranded
execution recovery, active-run scoping, assignee resolution, and
blocked-to-todo wake resumption
- Reduced noisy polling/logging overhead by trimming issue run payloads,
compacting persisted run logs, silencing high-volume request logs, and
capping heartbeat-run queries in dashboard/inbox surfaces
- Expanded telemetry and status semantics with adapter/model fields on
task completion plus clearer status guidance in docs/onboarding material
- Updated test infrastructure and verification defaults with faster
route-test module isolation, cheaper default `pnpm test`, e2e isolation
from local state, and repo verification follow-ups
- Included docs/release housekeeping from the branch and added a small
follow-up commit restoring the implicit comment-reopen helpers that were
dropped during branch reconstruction
## Verification
- `pnpm vitest run
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts`
- `pnpm vitest run server/src/__tests__/http-log-policy.test.ts
server/src/__tests__/heartbeat-run-log.test.ts
server/src/__tests__/health.test.ts`
- `server/src/__tests__/activity-service.test.ts`,
`server/src/__tests__/heartbeat-comment-wake-batching.test.ts`, and
`server/src/__tests__/heartbeat-process-recovery.test.ts` were attempted
on this host but the embedded Postgres harness reported
init-script/data-dir problems and skipped or failed to start, so they
are noted as environment-limited
## Risks
- Medium: this branch changes core issue/heartbeat routing and
reopen/wakeup behavior, so regressions would affect agent execution flow
rather than isolated UI polish
- Because it also updates verification infrastructure, reviewers should
pay attention to whether the new tests are asserting the right failure
modes and not just reshaping harness behavior
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 13:34:52 -05:00
|
|
|
reconcileStrandedAssignedIssues: vi.fn(async () => ({
|
|
|
|
|
dispatchRequeued: 0,
|
|
|
|
|
continuationRequeued: 0,
|
|
|
|
|
escalated: 0,
|
|
|
|
|
skipped: 0,
|
|
|
|
|
issueIds: [],
|
|
|
|
|
})),
|
2026-04-02 09:11:49 -05:00
|
|
|
tickTimers: vi.fn(async () => ({ enqueued: 0 })),
|
|
|
|
|
})),
|
[codex] Add backup endpoint and dev runtime hardening (#4087)
## Thinking Path
> - Paperclip is a local-first control plane for AI-agent companies.
> - Operators need predictable local dev behavior, recoverable instance
data, and scripts that do not churn the running app.
> - Several accumulated changes improve backup streaming, dev-server
health, static UI caching/logging, diagnostic-file ignores, and instance
isolation.
> - These are operational improvements that can land independently from
product UI work.
> - This pull request groups the dev-infra and backup changes from the
split branch into one standalone branch.
> - The benefit is safer local operation, easier manual backups, less
noisy dev output, and less cross-instance auth leakage.
## What Changed
- Added a manual instance database backup endpoint and route tests.
- Streamed backup/restore handling to avoid materializing large payloads
at once.
- Reduced dev static UI log/cache churn and ignored Node diagnostic
report captures.
- Added guarded dev auto-restart health polling coverage.
- Preserved worktree config during provisioning and scoped auth cookies
by instance.
- Added a Discord daily digest helper script and environment
documentation.
- Hardened adapter-route and startup feedback export tests around the
changed infrastructure.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run packages/db/src/backup-lib.test.ts
server/src/__tests__/instance-database-backups-routes.test.ts
server/src/__tests__/server-startup-feedback-export.test.ts
server/src/__tests__/adapter-routes.test.ts
server/src/__tests__/dev-runner-paths.test.ts
server/src/__tests__/health-dev-server-token.test.ts
server/src/__tests__/http-log-policy.test.ts
server/src/__tests__/vite-html-renderer.test.ts
server/src/__tests__/workspace-runtime.test.ts
server/src/__tests__/better-auth.test.ts`
- Split integration check: merged after the runtime/governance branch
and before UI branches with no merge conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches server startup, backup streaming, auth cookie
naming, dev health checks, and worktree provisioning.
- Backup endpoint behavior depends on existing board/admin access
controls and database backup helpers.
- No database migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.4 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:08:55 -05:00
|
|
|
instanceSettingsService: vi.fn(() => ({
|
|
|
|
|
getGeneral: vi.fn(async () => ({
|
|
|
|
|
backupRetention: {
|
|
|
|
|
dailyDays: 7,
|
|
|
|
|
weeklyWeeks: 4,
|
|
|
|
|
monthlyMonths: 1,
|
|
|
|
|
},
|
|
|
|
|
})),
|
|
|
|
|
})),
|
2026-04-02 09:11:49 -05:00
|
|
|
reconcilePersistedRuntimeServicesOnStartup: vi.fn(async () => ({ reconciled: 0 })),
|
|
|
|
|
routineService: vi.fn(() => ({
|
|
|
|
|
tickScheduledTriggers: vi.fn(async () => ({ triggered: 0 })),
|
|
|
|
|
})),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../storage/index.js", () => ({
|
|
|
|
|
createStorageServiceFromConfig: vi.fn(() => ({ id: "storage-service" })),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../services/feedback-share-client.js", () => ({
|
|
|
|
|
createFeedbackTraceShareClientFromConfig: vi.fn(() => ({ id: "feedback-share-client" })),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../startup-banner.js", () => ({
|
|
|
|
|
printStartupBanner: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../board-claim.js", () => ({
|
|
|
|
|
getBoardClaimWarningUrl: vi.fn(() => null),
|
|
|
|
|
initializeBoardClaimChallenge: vi.fn(async () => undefined),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
vi.mock("../auth/better-auth.js", () => ({
|
|
|
|
|
createBetterAuthHandler: vi.fn(() => undefined),
|
|
|
|
|
createBetterAuthInstance: vi.fn(() => ({})),
|
|
|
|
|
deriveAuthTrustedOrigins: vi.fn(() => []),
|
|
|
|
|
resolveBetterAuthSession: vi.fn(async () => null),
|
|
|
|
|
resolveBetterAuthSessionFromHeaders: vi.fn(async () => null),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
import { startServer } from "../index.ts";
|
|
|
|
|
|
|
|
|
|
describe("startServer feedback export wiring", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
process.env.BETTER_AUTH_SECRET = "test-secret";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("passes the feedback export service into createApp so pending traces flush in runtime", async () => {
|
|
|
|
|
const started = await startServer();
|
|
|
|
|
|
|
|
|
|
expect(started.server).toBe(fakeServer);
|
|
|
|
|
expect(feedbackServiceFactoryMock).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(createAppMock).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(createAppMock.mock.calls[0]?.[1]).toMatchObject({
|
|
|
|
|
feedbackExportService: feedbackExportServiceMock,
|
|
|
|
|
storageService: { id: "storage-service" },
|
|
|
|
|
serverPort: 3210,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-04-15 07:43:48 -04:00
|
|
|
|
|
|
|
|
describe("startServer PAPERCLIP_API_URL handling", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
process.env.BETTER_AUTH_SECRET = "test-secret";
|
|
|
|
|
delete process.env.PAPERCLIP_API_URL;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("uses the externally set PAPERCLIP_API_URL when provided", async () => {
|
|
|
|
|
process.env.PAPERCLIP_API_URL = "http://custom-api:3100";
|
|
|
|
|
|
|
|
|
|
const started = await startServer();
|
|
|
|
|
|
|
|
|
|
expect(started.apiUrl).toBe("http://custom-api:3100");
|
|
|
|
|
expect(process.env.PAPERCLIP_API_URL).toBe("http://custom-api:3100");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("falls back to host-based URL when PAPERCLIP_API_URL is not set", async () => {
|
|
|
|
|
const started = await startServer();
|
|
|
|
|
|
|
|
|
|
expect(started.apiUrl).toBe("http://127.0.0.1:3210");
|
|
|
|
|
expect(process.env.PAPERCLIP_API_URL).toBe("http://127.0.0.1:3210");
|
|
|
|
|
});
|
Tighten publicBaseUrl port rewriting (#4553)
## Thinking Path
> - Paperclip is a control plane for autonomous agent companies, so its
local and authenticated deployment behavior has to stay predictable
under port rebinding and worktree isolation.
> - This change sits in the server/worktree configuration path that
derives runtime URLs and auth origins from `auth.publicBaseUrl`.
> - The original hostname-port rewrite change fixed one real gap for
private/tailnet host:port worktree setups, but it widened the rewrite
rule too far.
> - Rewriting every explicit `auth.publicBaseUrl` can corrupt public or
reverse-proxy URLs by turning a stable origin like
`https://paperclip.example` into a local listen-port URL.
> - Paperclip's auth and trusted-origin handling depend on that URL
staying semantically correct, so this had to be narrowed before merge.
> - This pull request tightens the rewrite rule to explicit-port URLs
only and adds regression coverage across the CLI helper, worktree config
persistence, and server startup path.
> - The benefit is that private host:port worktree flows still work,
while public/default-port URLs remain stable and safe.
## What Changed
- Tightened `rewriteLocalUrlPort` in `cli/src/commands/worktree-lib.ts`,
`server/src/worktree-config.ts`, and `server/src/index.ts` so it only
rewrites URLs that already include an explicit port.
- Removed the old loopback-only hostname gate from the CLI/worktree
helpers and replaced it with the more precise `parsed.port` guard.
- Updated CLI helper coverage to assert that explicit-port non-loopback
URLs still rewrite while no-port public URLs stay unchanged.
- Expanded `server/src/__tests__/worktree-config.test.ts` to cover
explicit-port rewrite and no-port stability for both persisted worktree
config and in-memory runtime port selection.
- Added startup-path coverage in
`server/src/__tests__/server-startup-feedback-export.test.ts` for
`detect-port` rebinding with both explicit-port and no-port
`auth.publicBaseUrl` values.
## Verification
- `pnpm --filter @paperclipai/plugin-sdk build`
- `npx vitest run
server/src/__tests__/server-startup-feedback-export.test.ts`
- `npx vitest run cli/src/__tests__/worktree.test.ts
server/src/__tests__/worktree-config.test.ts`
- All of the above were run locally in this issue worktree and passed.
## Risks
- Low risk. The behavior change is deliberately narrower than the
reviewed broad-host rewrite and is guarded by regression coverage for
both the explicit-port and no-port cases.
- The main remaining risk is behavioral only if another code path starts
depending on port rewriting for URLs that never declared a port, which
would be a separate bug.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex local agent using `gpt-5.4` with high reasoning effort,
tool use, shell execution, and file editing.
- Anthropic Claude local agent using `claude-opus-4-6` for follow-up
code review approval on the implementation issue.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-26 14:29:22 -07:00
|
|
|
|
|
|
|
|
it("rewrites explicit-port auth public URLs when detect-port selects a new port", async () => {
|
|
|
|
|
loadConfigMock.mockReturnValueOnce({
|
|
|
|
|
...loadConfigMock(),
|
|
|
|
|
port: 3100,
|
|
|
|
|
authBaseUrlMode: "explicit",
|
|
|
|
|
authPublicBaseUrl: "http://my-host.ts.net:3100",
|
|
|
|
|
});
|
|
|
|
|
detectPortMock.mockResolvedValueOnce(3110);
|
|
|
|
|
|
|
|
|
|
const started = await startServer();
|
|
|
|
|
|
|
|
|
|
expect(started.listenPort).toBe(3110);
|
|
|
|
|
expect(started.apiUrl).toBe("http://my-host.ts.net:3110");
|
|
|
|
|
expect(process.env.PAPERCLIP_RUNTIME_API_URL).toBe("http://my-host.ts.net:3110");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("keeps no-port auth public URLs stable when detect-port selects a new port", async () => {
|
|
|
|
|
loadConfigMock.mockReturnValueOnce({
|
|
|
|
|
...loadConfigMock(),
|
|
|
|
|
port: 3100,
|
|
|
|
|
authBaseUrlMode: "explicit",
|
|
|
|
|
authPublicBaseUrl: "https://paperclip.example",
|
|
|
|
|
});
|
|
|
|
|
detectPortMock.mockResolvedValueOnce(3110);
|
|
|
|
|
|
|
|
|
|
const started = await startServer();
|
|
|
|
|
|
|
|
|
|
expect(started.listenPort).toBe(3110);
|
|
|
|
|
expect(started.apiUrl).toBe("https://paperclip.example");
|
|
|
|
|
expect(process.env.PAPERCLIP_RUNTIME_API_URL).toBe("https://paperclip.example");
|
|
|
|
|
});
|
2026-04-15 07:43:48 -04:00
|
|
|
});
|