48 Commits

Author SHA1 Message Date
Chris Farhood cc34e05713 0.2.3 2026-04-30 16:58:40 -04:00
Chris Farhood c71757fbcd 0.2.2 2026-04-30 16:00:46 -04:00
Chris Farhood 098d9f9641 fix(ui-parser): emit as proper CJS subpackage so ESM named imports work
Paperclip's plugin-loader does ESM named imports of parseStdoutLine when
loading opencode_k8s, e.g.:

    import { parseStdoutLine } from "paperclip-adapter-opencode-k8s/ui-parser"

The previous esbuild bundle wrote CJS via __toCommonJS getters, which
cjs-module-lexer can't statically detect — Node fails the link with:

    SyntaxError: The requested module './ui-parser.js' does not provide
    an export named 'parseStdoutLine'

Also, with the package.json `"type": "module"` field, dist/ui-parser.js
was being interpreted as ESM by the loader, compounding the failure.

Fix: emit ui-parser as a proper CJS sub-package.

- Move output to dist/ui-parser/ui-parser.js
- Generate dist/ui-parser/package.json with `{"type":"commonjs"}` so Node
  treats the file as CJS regardless of the parent type:module
- Use `tsc -p tsconfig.ui-parser.json` (module: commonjs) instead of
  esbuild — the output is plain `exports.parseStdoutLine = parseStdoutLine`
  which cjs-module-lexer detects natively
- Update the exports map: `"./ui-parser": "./dist/ui-parser/ui-parser.js"`
- Drop the esbuild devDependency

Verified locally:
- `import { parseStdoutLine } from "...ui-parser"` works (Node 25)
- Read-file-as-text + `new Function(...)` worker pattern still works
- 382/382 tests pass; typecheck clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 16:00:42 -04:00
Chris Farhood 65321e091d 0.2.1 2026-04-30 15:25:28 -04:00
Chris Farhood c0c4c3f179 fix(build): exclude *.test.ts from emit so test files aren't shipped
Paperclip's plugin loader links the package as ESM, and dist/ui-parser.test.js
contained `import { parseStdoutLine } from "./ui-parser.js"` — but ui-parser.js
is bundled as CJS (see 480f7cf) so Node's ESM linker can't resolve the named
export. Result: adapter install fails with

    SyntaxError: The requested module './ui-parser.js' does not provide an
    export named 'parseStdoutLine'

Same root cause as c79eea7, just on the test file instead of src/index.ts.

Fix: introduce tsconfig.build.json that extends the base tsconfig and adds
"exclude": ["**/*.test.ts"]. The build script now runs tsc against that
config, so test files don't end up in dist/. tsconfig.json (used by --noEmit
typecheck and vitest) still includes them, so test type-safety is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:25:25 -04:00
Chris Farhood c5b555de17 0.2.0 2026-04-30 14:58:03 -04:00
Chris Farhood d9bc2e513b 0.1.40 2026-04-30 13:50:39 -04:00
Chris Farhood fa6c115be4 0.1.39 2026-04-30 09:03:07 -04:00
Chris Farhood 480f7cf3d1 fix(ui-parser): bundle as CJS so the sandboxed worker can load it
The Paperclip UI loads each adapter's ui-parser.js inside a sandboxed
Web Worker via `new Function(...)` to render the run transcript. The
worker can only evaluate CJS — ESM `export` syntax silently fails to
register `parseStdoutLine`, and the run window falls back to dumping
raw JSONL.

tsc was emitting ESM `export function parseStdoutLine`, so every
published version since the parser was added has shipped a parser the
UI can't load. Add the same esbuild step the claude-k8s adapter uses
(0.2.4) to overwrite dist/ui-parser.js with a CJS bundle that assigns
to module.exports.

Also bump @paperclipai/adapter-utils from a stale 2026.415.0-canary.7
pin to ^2026.428.0 (current stable). All 406 tests pass against the
new types; no API drift in the imported surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 09:03:03 -04:00
Chris Farhood fe6bc0c2d6 fix(config): remove instructionsFilePath from UI config schema
Handled by the framework via instructionsPathKey/supportsInstructionsBundle.
Surfacing it as an editable field in the config schema was redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:25:33 -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 570fdae9c4 fix(models): restore static model list matching opencode_local pattern
Adds back STATIC_MODELS with correct provider/model IDs and consistent
labels (matching listModels output format) so the UI is not blank before
listModels resolves. Restores server-adapter test with a contract check
that enforces provider/model label consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:18:46 -04:00
Chris Farhood 985d55e125 fix(cancel-poll): use ctx.authToken instead of process.env for cancel polling
The cancel poll was sending empty Authorization headers because
PAPERCLIP_API_KEY is not set on the Paperclip server pod. Use the
per-run authToken from ctx instead, which is the JWT issued by Paperclip
for this execution. PAPERCLIP_DEV_API_KEY still overrides for dev instances.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:11:47 -04:00
Chris Farhood 5f75c2b81b feat(models): port model discovery to match opencode_local adapter
Replace static hardcoded fallback list with the same robust approach used
by the opencode_local adapter: runChildProcess + ensurePathInEnv, HOME fix
via os.userInfo(), 60s TTL cache, returns [] on failure instead of a stale
list. Also updates CLAUDE.md and README.md with missing fields/features.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 21:51:26 -04:00
Chris Farhood 7043e71ff6 fix(models): expose static models list so UI renders entries before listModels resolves
The UI was showing 0 models because the adapter only set listModels (async)
without a static models array. The static array is what the UI reads
synchronously to populate the selector — listModels is for refresh.

- Move the fallback list out as STATIC_MODELS (expanded to cover Opus/Sonnet/Haiku 4.x, GPT-4o/5, Gemini 2.5, Grok 4, DeepSeek)
- Set models: STATIC_MODELS on the adapter module
- Keep listK8sModels for runtime refresh from `opencode models` (with STATIC_MODELS fallback on error or empty stdout)
- Add server-adapter.test.ts asserting models is non-empty
- Add models.test.ts coverage for the empty-stdout fallback path

chore: bump version to 0.1.33

Fixes FAR-94.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-26 02:59:05 +00:00
Chris Farhood da1b55d233 test: add coverage for listK8sModels CLI fetch and fallback
chore: bump version to 0.1.32

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-26 02:07:51 +00:00
Chris Farhood 4c956cc039 chore: bump version to 0.1.31 2026-04-26 00:25:06 +00:00
Chris Farhood 1bad618b29 chore: bump version to 0.1.30
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 23:25:17 +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 693016d1ab chore: add @vitest/coverage-v8 to enable coverage reports
So we can answer "what's coverage?" without re-installing each time.
Run with: \`npx vitest run --coverage --coverage.provider=v8 --coverage.reporter=text-summary\`

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 22:09:55 +00:00
Chris Farhood c71e224b43 test: cover k8s-client 404 detection against real ApiException shape (FAR-85)
There was no test file for k8s-client.ts. Existing pvc.test.ts mocked
`getPvc` directly and never exercised the underlying isNotFound predicate,
so the v1.x ApiException `code` vs `statusCode` regression had nothing to
catch it.

Add k8s-client.test.ts that mocks @kubernetes/client-node, throws errors
shaped exactly like the real ApiException (status under `code`), and
verifies:

- getPvc returns null on code=404 (the FAR-85 case)
- getPvc still handles legacy statusCode=404 and response.statusCode=404
- getPvc re-throws non-404 errors (500, 403)
- deletePvc swallows 404, re-throws others
- createPvc forwards spec to the SDK

Confirmed the new tests fail when k8s-client.ts is reverted to the
pre-fix predicate (2 failures), and pass with the fix in place.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 22:05:48 +00:00
Chris Farhood 3daf2dd676 fix: detect 404 from @kubernetes/client-node v1.x ApiException (FAR-85)
The v1.x ApiException exposes the HTTP status as `code`, not `statusCode`.
Both `isNotFound` (k8s-client) and `isK8s404` (execute) only checked
`statusCode`/`response.statusCode`, so 404s were never recognized:

- `getPvc` re-threw the 404 instead of returning null, which bubbled up
  through `ensureAgentDbPvc` as `k8s_job_create_failed` with the raw
  "persistentvolumeclaims X not found" body — the symptom in FAR-85.
- The PVC was never actually created, because the existence check threw
  before reaching `createPvc`.

Add `code === 404` to both predicates and a regression test for `isK8s404`.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 21:53:59 +00:00
Chris Farhood af6d3e811a chore: bump version to 0.1.26
Includes PVC verification fix (FAR-84) and prior unbumped fixes
(serviceAccountName config schema, log reconnect backoff stop).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 21:35:30 +00:00
Chris Farhood a6e95c1e64 Add serviceAccountName to Kubernetes config schema
Surfaces the serviceAccountName field in the adapter UI under the
Kubernetes group. The job manifest builder already reads this field;
this change makes it configurable via the UI.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 20:05:49 +00:00
Chris Farhood 1005b6f3f2 chore: bump version to 0.1.24
Surface PVC settings (agentDbMode, agentDbStorageClass,
agentDbStorageCapacity) in adapter UI config (FAR-73).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 20:01:39 +00:00
Chris Farhood 80d18005f9 fix: wait for concurrent job to finish instead of returning permanent blocked error (FAR-61)
When multiple tasks are assigned simultaneously, only one K8s job can run
at a time (shared PVC/session guard). Previously, all other tasks received
k8s_concurrent_run_blocked immediately and stayed blocked forever.

Now the guard retries once: wait for all blocking jobs to complete via
waitForJobCompletion, then re-check before proceeding to create a new job.
If the re-check still shows a running job, the error is returned as before.

The agentCreationMutex already serializes guard-check + job-create, so
tasks naturally queue up and execute one at a time without concurrent jobs.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 11:10:27 +00:00
Chris Farhood 2bd8107f1d fix: skills not bundled and resumeLastSession ignored (FAR-56, FAR-57)
Two bugs prevented skill content from reaching K8s Job prompts, and
resumeLastSession: false was silently ignored.

Skills fix (execute.ts, FAR-57):
- Add /paperclip/.claude/skills as additional candidate to
  readPaperclipRuntimeSkillEntries — the relative candidates in
  adapter-utils don't resolve to the PVC-mounted skills home
- Read entry.source/SKILL.md instead of entry.source (which is a
  directory path); fall back to source directly for file-based entries
- Mock readPaperclipRuntimeSkillEntries in execute.test.ts to prevent
  real SKILL.md reads from delaying fake-timer registration

Session fix (job-manifest.ts, FAR-56):
- Gate --session flag on asBoolean(config.resumeLastSession, true)
  so setting resumeLastSession: false actually stops session resumption
- Default true preserves existing behaviour for agents without config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 10:11:47 +00:00
Chris Farhood 0e94e84e2c fix: grace poller checks pod liveness before giving up on log stream (FAR-52)
The K8s log client v1.x closes the follow-stream prematurely due to a
known upstream bug — causing the grace timer to fire 30 s after log
stream exit even when the container is still running.  The old behaviour
(`waitForPodTermination` with a hardcoded 120 s timeout) was too short
for agents whose opencode runs take several minutes, leading to premature
failure and issues stuck in `blocked`.

Fix: the grace poller now calls `readNamespacedPod` before resolving the
completion promise.  If the pod is still Running/Pending, it resets
`logExitTime` to defer the grace deadline.  A `graceCheckPending` guard
prevents concurrent checks.  A `graceMaxWaitMs` cap (= completionTimeoutMs
when set, 20 min otherwise) ensures we never wait forever for unlimited
jobs.  Version bumped to 0.1.21.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 01:40:21 +00:00
Chris Farhood 2625b8ffb3 fix: wait for pod termination before readPodLogs fallback (FAR-52)
The @kubernetes/client-node v1.x Log.follow stream closes prematurely
(known upstream TODO). Combined with Node.js buffering stdout to pipes,
the live log stream always returns empty. When the 30s grace timer fires
and the stream is empty, the container may still be running.

Add waitForPodTermination() to block in the empty-stdout fallback path
until the container actually exits (up to 120s), then read its complete
output with readNamespacedPodLog. This makes runs complete successfully
instead of looping indefinitely in in_progress.

Bump version to 0.1.20.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 01:21:40 +00:00
Chris Farhood bf32e81aa6 chore: bump version to 0.1.19
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 00:42:29 +00:00
Chris Farhood 0fcc6f01c1 feat: instructionsFilePath config field + skill bundle materialization
- 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>
2026-04-24 22:06:07 +00:00
Chris Farhood 3ed6e95085 feat: RBAC checks, ./cli export, adapter-utils range, models.ts
- Add secrets (create/delete/get) and persistentvolumeclaims (get) RBAC checks to testEnvironment
- Add ./cli export and picocolors dependency to package.json
- Change @paperclipai/adapter-utils peer dep to >= range
- Add src/server/models.ts with listK8sModels() returning OpenCode provider-prefixed model IDs
- Wire listModels into ServerAdapterModule (replaces static undefined models field)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-24 21:58:00 +00:00
Paperclip ea4db3ba9b chore: unscope package name to paperclip-adapter-opencode-k8s
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 10:26:43 +00:00
Chris Farhood f728db0d1a Bump version to 0.1.16
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 23:45:26 -04:00
Chris Farhood 832ead94a3 Bump version to 0.1.15 for capability flag release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:24:40 -04:00
Chris Farhood 4b6da9b17b Update adapter-utils to canary with capability flag types
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>
2026-04-15 20:20:04 -04:00
Pawla Abdul e53bcf2501 Replace local utility stubs with fork's adapter-utils imports
- 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>
2026-04-14 11:00:33 +00:00
Pawla Abdul b59f571e0b Fix peer dependency conflict by relaxing adapter-utils requirement
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>
2026-04-12 16:58:09 +00:00
Pawla Abdul 241da1d4f9 Use rich TranscriptEntry kinds in UI parser for pretty output
The UI parser was mapping all OpenCode JSONL events to plain stdout/stderr
transcript entries, causing the Paperclip UI to render everything as raw
terminal output. Updated parseStdoutLine to use structured kinds:

- text events → "assistant" (renders as chat bubbles)
- tool_use events → "tool_call" / "tool_result" (shows tool invocations)
- step_finish events → "result" (shows token/cost metrics)
- step_start events → "system" (shows step transitions)
- assistant events → "assistant" (nested content blocks)

Fixes FAR-43.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-12 15:15:19 +00:00
Pawla Abdul faa5f09984 Upgrade adapter-utils to canary with getConfigSchema type support
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>
2026-04-12 14:58:07 +00:00
Chris Farhood 2927d3e47e Bump version to 0.1.10
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 09:52:09 -04:00
Chris Farhood 1b3d4b6dfe Bump version to 0.1.9
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 09:28:33 -04:00
Chris Farhood bbd579e7e2 Bump version to 0.1.8
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 08:58:45 -04:00
Chris Farhood 66893dc286 Bump version to 0.1.7
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 08:33:25 -04:00
Chris Farhood ce666f6b54 Read all env vars from pod spec instead of static allowlist
- 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>
2026-04-12 08:31:43 -04: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
Chris Farhood 75f54b1edc Update package name to @farhoodliquor scope and refresh README
- 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>
2026-04-11 23:18:12 -04:00
Chris Farhood be7c525063 Initial commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 23:08:05 -04:00