Commit Graph

12 Commits

Author SHA1 Message Date
Chris Farhood 78d655eeb6 feat: replace K8s log streaming with PVC filesystem tailing
- Replaced streamPodLogs / streamPodLogsOnce / readPodLogs / waitForPodTermination
  with tailPodLogFile() that polls a shared PVC file path with adaptive cadence
  (250ms active, 1000ms idle after 5 consecutive empty polls)
- Added buildPodLogPath() export and podLogPath to JobBuildResult
- Added assertSafePathComponent with [a-zA-Z0-9-:] allowance for UUIDs
- Updated Job manifest to tee stdout to /paperclip/instances/default/run-logs/<companyId>/<agentId>/<runId>.pod.ndjson
- Added hasOutOfProcessLiveness: true to createServerAdapter (cast required)
- Deleted log-dedup.ts and log-dedup.test.ts entirely
- Removed all LogLineDedupFilter, Writable, and LOG_STREAM_* constants
- Removed completionResult.status workaround (completionWithGrace returns directly)
- Test infrastructure: mocked node:fs/promises to prevent unmocked fs.stat hangs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:56:58 -04:00
Chris Farhood 2d057f085d refactor: remove PAPERCLIP_DEV_API_KEY runtime hack throughout
Cancel poll now uses ctx.authToken exclusively. Remove forwarding of
PAPERCLIP_DEV_API_KEY into job pods and all associated tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:24:14 -04:00
Chris Farhood e364e09113 test: assert config.image overrides selfPod image in job manifest
Locks in the existing override behavior so a future regression that
reverts to a hardcoded image is caught immediately. Closes the
investigation on FAR-90.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-26 01:12:05 +00:00
Chris Farhood 5670da320a Fix OPENCODE_DB to be a file path inside /opencode-db mount
The env var was set to /opencode-db (the mount point directory), but sqlite
requires a file path. Changed to /opencode-db/opencode.db.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 23:23:59 +00:00
Chris Farhood 798b80f2f2 test: push coverage to 90%+ on lines for all files except execute.ts (FAR-85)
Overall before: 80.36% lines / 79.06% statements
Overall after:  94.65% lines / 93.30% statements

Per-file lines coverage (all targets ≥90% except execute.ts):

| File              | Before | After  |
|-------------------|--------|--------|
| ui-parser.ts      | 93.63% | 99.09% |
| cli/format-event  | 59.85% | 99.27% |
| server/execute    | 81.47% | 89.64% |
| server/job-mfst   | 90.30% | 98.78% |
| server/k8s-client | 37.50% | 95.83% |
| server/log-dedup  | 97.77% | 97.77% |
| server/parse      | 89.85% | 98.55% |
| server/skills     | 100%   | 100%   |

New tests added:

- k8s-client.test.ts: getSelfPodInfo (env-var inheritance, secret volumes,
  PVC discovery, dnsConfig, all error paths) + kubeconfig file branch
- format-event.test.ts: parseStdoutLine (cli) — full event-type matrix,
  tool_use status branches, errorText fallback paths
- ui-parser.test.ts: errorText edge cases, empty event paths
- parse.test.ts: errorText fallback to data.message, name, code, JSON
- job-manifest.test.ts: workspace context env wiring, linkedIssueIds,
  paperclipWorkspaces/RuntimeServices JSON, authToken, inherited URLs,
  prompt-secret + data PVC + secret-volume mount paths
- execute.test.ts: parseModelProvider, completionWithGrace,
  instructionsFilePath read failure, ensureAgentDbPvc throw paths,
  large-prompt secret create failure, step-limit detection,
  waitForPod no-pod messaging, init-container ImagePullBackOff /
  CrashLoopBackOff, main-container CrashLoopBackOff, all-inits-done
  happy path, skill bundle source loading (SKILL.md + flat-file
  fallback), SIGTERM handler full body via vi.resetModules()

execute.ts remains at 89.64% lines — the residual gap is deep async/timer
paths inside streamAndAwaitJob (grace poller, keepalive ticker, log-stream
stop-signal/bail timer). Those need fake-timer scaffolding heavier than
this batch warrants; tracking separately.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 22:27:04 +00:00
Chris Farhood 46ce5cc599 feat: dedicated PVC per agent for OPENCODE_DB (FAR-63, Option B)
Replaces the Option A shared-PVC path implementation with a long-lived
dedicated PVC per agent, mounted at /opencode-db with OPENCODE_DB=/opencode-db.

Changes:
- k8s-client.ts: add getPvc/createPvc/deletePvc CoreV1Api helpers
- execute.ts: add ensureAgentDbPvc() that gets-or-creates a PVC named
  opencode-db-<agentId> before Job creation; pass agentDbClaimName through
  to buildJobManifest; return null for ephemeral mode (emptyDir used instead)
- job-manifest.ts: accept agentDbClaimName on JobBuildInput; mount dedicated
  PVC or emptyDir at /opencode-db; set OPENCODE_DB=/opencode-db; revert init
  container to simple form (no mkdir, no PVC mount)
- config-schema.ts: replace opencodeDbMode/opencodeDbPath with agentDbMode
  (dedicated_pvc|ephemeral, default dedicated_pvc), agentDbStorageClass
  (required for dedicated_pvc), agentDbStorageCapacity (default 1Gi)
- test.ts: add create/delete RBAC checks for persistentvolumeclaims
- pvc.test.ts: unit tests for ensureAgentDbPvc (7 cases incl. error paths)
- 289/289 tests pass; typecheck clean
- No agent-delete hook exists; opencode-db PVC janitor routine is a deferred
  follow-up task

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 12:38:54 +00:00
Chris Farhood 908880a25b feat: implement OPENCODE_DB per-agent path on shared PVC (FAR-62)
- shared_pvc mode (default): sets OPENCODE_DB to /paperclip/.opencode/db/<agentId>
  and prepends mkdir -p to the busybox init container when a PVC is present
- ephemeral mode: mounts an emptyDir at /opencode-db and points OPENCODE_DB there
- config-schema: adds opencodeDbMode (select, default shared_pvc) and
  opencodeDbPath (optional text override for shared_pvc path)
- No agent-delete hook exists in this adapter; per-agent DB dir cleanup is
  deferred to a janitor routine (follow-up work)
- 284/284 tests pass; typecheck clean

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 12:29:38 +00:00
Chris Farhood d858df7cf2 feat: external-cancel polling via heartbeat-runs endpoint (FAR-42)
Poll PAPERCLIP_API_URL/api/heartbeat-runs/{runId} at keepalive cadence
during log streaming. When status != "running", delete the Job with
propagationPolicy=Background and return errorCode="cancelled" as a
distinct result, matching the claude_k8s reference implementation.

Also includes: reattachOrphanedJobs config field that lets the adapter
reattach to a same-task Job left over from a prior server restart;
task-id and session-id K8s labels on Job manifests for observability.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 00:22:28 +00:00
Chris Farhood 0a3d9c4172 feat: sanitize K8s label values + collision-resistant job names (FAR-43)
- Add `sanitizeLabelValue()` export: strips [^a-z0-9._-], lowercases, truncates to 63 chars, warns on drop
- Apply sanitizer to all paperclip.io/* label values (agent-id, run-id, company-id, extra labels)
- Job name now includes 6-char sha256 hash over raw agent.id+runId for collision resistance
- Trailing hyphens stripped from final job name
- Slugs extended from 8 to 16 chars to match claude_k8s reference
- 32 unit tests covering sanitizeLabelValue, job name format, determinism, and collision avoidance

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 00:08:33 +00:00
Chris Farhood 61d2a42a66 feat: inherit valueFrom/envFrom env from Deployment; prefer paperclip container
- SelfPodInfo gains inheritedEnvValueFrom (V1EnvVar[]) and inheritedEnvFrom (V1EnvFromSource[])
- Container selection now prefers the container named "paperclip", falls back to first
- buildJobManifest appends valueFrom env vars (skipping names already overridden)
  and sets envFrom on the opencode container when present
- Tests updated: mock updated, 5 new cases covering secretKeyRef forwarding,
  dedup, envFrom passthrough, and empty-envFrom omission

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-24 22:12:31 +00:00
Chris Farhood 13c2a3032b feat: UI parser kinds, nodeSelector textarea, step-limit session clear, per-line path redaction
- ui-parser: add thinking kind + handler for standalone thinking events, thinking
  blocks in assistant content arrays, and user-turn tool_result blocks
- job-manifest: parseKeyValueOrObject helper so nodeSelector (and labels) accept
  key=value textarea lines in addition to JSON objects
- parse: isOpenCodeStepLimitResult detects step_finish with max_turns / max_steps /
  step_limit reason
- execute: return clearSession:true when step limit reached so next run starts fresh;
  redactHomePathUserSegments moved to per-line to prevent paths split across chunks
- tests: ui-parser.test.ts (new), extended parse.test.ts and job-manifest.test.ts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-24 22:01:35 +00:00
Chris Farhood 6866da42bf Add tests, free-text model field, and K8s job improvements
- Add vitest with 26 passing tests for parse and job-manifest
- Set models to undefined for free-text model input
- Add fsGroupChangePolicy: "OnRootMismatch" to reduce volume chown delays
- Change job name prefix to agent-opencode- for adapter identification
- Add .npmrc to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 08:14:57 -04:00