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>
- Add agentCreationMutex (Map<agentId, Promise>) that serializes
guard-check + job-create per agent, eliminating the TOCTOU race where
two concurrent execute() calls both pass the list-then-create check.
- Change catch {} on listNamespacedJob errors to return
errorCode: "k8s_concurrency_guard_unreachable" (fail-closed) instead
of silently bypassing the concurrency guard.
- Add ensureSigtermHandler() which tracks active Jobs in activeJobs Map
and deletes all of them (plus prompt Secrets) on SIGTERM before exit.
- Track orphaned-job reattaches in activeJobs for consistent cleanup.
- Update execute.test.ts: change "proceeds on list error" test to assert
k8s_concurrency_guard_unreachable; add mutex serialization test and
SIGTERM handler registration tests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- 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>
- Add describe block "execute — large-prompt Secret path" with 5 cases:
buildJobManifest called twice (promptSecretName on second call),
Secret created before Job, ownerReference patched after Job creation,
Secret deleted in finally block, Secret cleaned up on Job create failure
- Update vi.mock for job-manifest to export LARGE_PROMPT_THRESHOLD_BYTES
- Add createNamespacedSecret/deleteNamespacedSecret/patchNamespacedSecret
to makeCoreApi for completeness
- Update makeBatchApi to return { metadata: { uid } } so ownerRef tests work
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Split streamPodLogs into streamPodLogsOnce (with bail timer + stopSignal)
and streamPodLogs (reconnect loop, up to MAX_LOG_RECONNECT_ATTEMPTS=50)
- LogLineDedupFilter suppresses replayed JSONL events on reconnect, keyed
by type+sessionID+part.id (OpenCode shape)
- Bail timer (LOG_STREAM_BAIL_TIMEOUT_MS=3s) forces writable.destroy() +
promise resolution when stopSignal fires and logApi.log hangs
- Keepalive: emits '[paperclip] keepalive — job X running (Ns since last output)'
every 15s during silent phases, with 2-consecutive-reading latch to avoid
false-positive terminal detections
- completionGraced uses logExitTime + grace poller so log stream stop signal
is set immediately when job condition resolves
- All 235 tests pass, tsc clean
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- 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>
When both a JSONL error (e.g. "killed") and a pod terminated reason (e.g. "OOMKilled")
are present, join them with "; " so the richer pod classification is never silently
dropped by the parsedError short-circuit.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Replace getPodExitCode with getPodTerminatedInfo to capture exit code
and reason (OOMKilled, Error, etc.) from terminated container state;
pod failure description now surfaces in returned errorMessage
- Add partial-stdout fallback: readPodLogs is triggered when stdout is
non-empty but contains no sessionId (missing session result), not just
when stdout is fully empty
- Detect empty LLM response: when a session ran but produced 0 output
tokens and no messages, return errorCode "llm_api_error"
- Add 13 new unit tests covering all three new paths
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- config-schema: add instructionsFilePath UI field (Core group, text type)
- server/index.ts: set supportsInstructionsBundle=true, instructionsPathKey="instructionsFilePath"
- execute.ts: read instructionsFilePath file + desired skill markdown files from PVC; pass to buildJobManifest as instructionsContent / skillsBundleContent
- job-manifest.ts: accept instructionsContent + skillsBundleContent in JobBuildInput; prepend both to prompt via joinPromptSections; add instructionsChars + skillsBundleChars to promptMetrics
- index.ts: document instructionsFilePath and skills injection in agentConfigurationDoc
- CLAUDE.md: document skill materialization (ephemeral mode) and instructionsFilePath field
- Bump version to 0.1.18
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Implement skill sync handlers that were missing, matching the approach
used in the claude_k8s adapter. The adapter now surfaces available,
configured, and external skills from /paperclip/.claude/skills in K8s
pods, resolving desired skills from config and reporting missing ones.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upgrade from ^2026.411.0-canary.8 to 2026.415.0-canary.7 to get
ServerAdapterModule capability flag fields (supportsInstructionsBundle,
instructionsPathKey, requiresMaterializedRuntimeSkills).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Declare supportsInstructionsBundle, instructionsPathKey, and
requiresMaterializedRuntimeSkills on ServerAdapterModule. opencode_k8s
does not support instructions bundles (instructions are piped via init
container) and does not require materialized runtime skills (bundled in
container image).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace joinPromptSections, stringifyPaperclipWakePayload, and
renderPaperclipWakePrompt with imports from adapter-utils/server-utils
(the fork's renderPaperclipWakePrompt adds execution stage routing,
resume delta sections, and full comment batch rendering)
- Replace local inferOpenAiCompatibleBiller with import from adapter-utils
- Declare sessionManagement using getAdapterSessionManagement("opencode_local")
with fallback defaults for proper session compaction policy
- Add log redaction via redactHomePathUserSegments in streamPodLogs
- Bump peerDependency to >=0.3.1 and version to 0.1.14
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Lock file was stale at 0.1.11 with an outdated peerDependency constraint;
bring it in line with package.json (0.1.13, >=0.3.0).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The adapter config schema was re-declaring model, promptTemplate, env,
extraArgs, timeoutSec, and graceSec which the Paperclip platform already
surfaces as standard fields, causing duplicate controls in the agent
configuration UI.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The peer dependency on @paperclipai/adapter-utils was pinned to
>=2026.411.0-canary.8 which conflicts with the stable 2026.403.0
version used by other adapters (claude-k8s, hermes-k8s). Since the
canary types (AdapterConfigSchema, getConfigSchema on ServerAdapterModule)
are only needed at compile time, we can safely relax the peer dep to
>=0.3.0 while keeping the canary as a devDependency for our own build.
Bumps version to 0.1.13.
Closes FAR-49
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Rewrites the README to clearly document all prerequisites for running the
adapter on Kubernetes: shared ReadWriteMany PVC at /paperclip, full RBAC
Role/RoleBinding with all required permissions, namespace scoping, security
context, and resource defaults. Uses deployment "paperclip" in namespace
"paperclip" as example nomenclature throughout.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The UI wasn't surfacing config parameters because getConfigSchema wasn't
part of the ServerAdapterModule interface in adapter-utils >=0.3.0. The
canary release (2026.411.0-canary.8) adds ConfigFieldSchema,
AdapterConfigSchema, and getConfigSchema to the type. This removes the
local type augmentation workaround and the unsafe `as ServerAdapterModule`
cast, letting TypeScript properly validate the schema contract.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Avoids 403 E403 "cannot publish over previously published version" error
when pushing non-version-bump commits to master.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- src/server/config-schema.ts: define schema with Core, Kubernetes,
and Operational field groups matching agentConfigurationDoc
- src/server/config-schema.test.ts: 10 tests covering field types,
defaults, options, and group structure
- src/server/index.ts: attach getConfigSchema to ServerAdapterModule
- Revert k8s-client.ts loadFromConfig change (not available in installed
@kubernetes/client-node version)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ui-parser.ts: inline all logic, zero external imports (matches Paperclip
adapter plugin UI parser contract)
- Export parseStdoutLine as named export from index.ts (like claude_k8s
exports printClaudeStreamEvent directly)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New src/cli/format-event.ts handles formatting OpenCode JSONL events:
- step_start: skip in normal mode, show in debug
- text: display as-is
- tool_use: show errors, skip in normal mode
- step_finish: show message + tokens/cost in debug
- error: display error message
Exports cliAdapter.formatStdoutEvent for Paperclip UI to call.
Also fixes ui-parser.ts to re-export from format-event.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip step_start/step_finish/tool_use events in UI display, extract
text from type:text events. Provides cleaner real-time output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OpenCode outputs the final response in step_finish.part.message, not
just as type:text events. Added parsing of part.message to ensure
the summary is captured when the agent responds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove INHERITED_ENV_KEYS; read directly from mainContainer.env
- Any Deployment env var is now forwarded automatically to Job pods
- Layer ordering preserved: pod spec → paperclip vars → agent config env
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Runs typecheck and tests on push/PR to master
- Publishes to npm on master push (requires NPM_TOKEN secret)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- Rename to @farhoodliquor/paperclip-adapter-opencode-k8s
- Add .gitignore
- Emphasize RWX PVC requirement in README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>