Files
platform/src/servers/sidecar/opencode/serve-env.test.ts
T
pastilhasandClaude Opus 5 a873690e27 opencode gets an identity, not just a coding agent's
three turns in a row could not look at a calendar while holding the calendar
skill, and each failure had a different cause.

FIRST: opencode's built-in `build` agent opens with "You are an AI coding agent.
Help the user accomplish software engineering tasks." asked about a calendar it
reasoned, in its own words, "this isn't a coding task, and I don't have access to
any calendar" and never called the skill tool. the index was in its context the
whole time. the plumbing was right and the identity was wrong, which is the
harder of the two to see.

so officer defines its own primary agent — mode primary, `default_agent`, and
named explicitly on session create because a default is a preference and this is
a requirement. only NEW sessions: a resumed one keeps the agent it was made with.

SECOND: setting a custom `prompt` traded one problem for the other. with the
built-in agent the index was there and the framing was wrong; with ours the
framing was right and the index was gone, so the turn went looking for .ics files
in the home directory. `prompt` replaces the assembled system text rather than
adding to it. the index is now compiled INTO the prompt by `skillsBlock()` — the
same function a claude session uses — so both halves arrive together. ours also
carries the PATH to each SKILL.md, which opencode's own block does not: a path
can be opened with the file tools every harness already has.

WHAT IS NOT FIXED, AND IS NOT THIS FILE'S FAULT. a third attempt still failed,
and a diagnostic turn reported system instructions beginning "Tool infrastructure
You can call" — not ours — and named a skill (`frontend-design`) that exists
nowhere in this install. the only provider this machine can reach is opencode's
own hosted gateway on free models; there is no auth.json here. so either the
gateway supplies its own system prompt, or a free model is confabulating about
its instructions. those are different problems and neither can be told apart
without a model whose credential we own.

verified: the agent is registered and its prompt is 4,717 characters containing
"Calendar and contacts" and the SKILL.md paths; the serve's credential works (a
tool process fetched the owner's collections); 10 skills discovered. NOT verified:
that any of it reaches the model on this box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-27 12:44:34 +00:00

34 lines
1.8 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { serveConfigContent, affordancesPath } from './serve-env';
// The config shape is a contract with the OpenCode binary, and the cost of a wrong key name is SILENCE:
// no error, no warning, just an agent that never sees a skill. So the keys are asserted literally.
describe('serveConfigContent', () => {
test('names skills.paths and instructions exactly', () => {
const parsed = JSON.parse(serveConfigContent(['/a/skills', '/b/skills'], ['/tmp/aff.md'], 'PROMPT'));
expect(parsed.skills).toEqual({ paths: ['/a/skills', '/b/skills'] });
expect(parsed.instructions).toEqual(['/tmp/aff.md']);
// The agent is what a session is created with; a config without it silently falls back to
// OpenCode's coding-agent framing, which is the bug this whole file exists to stop.
expect(parsed.default_agent).toBe('officer');
expect(parsed.agent.officer.prompt).toBe('PROMPT');
expect(parsed.agent.officer.mode).toBe('primary');
});
// An empty array is not the same as an absent key: `skills: {paths: []}` is a claim that there are none,
// and merged over a config that sets some it would erase them.
test('omits a key rather than sending it empty', () => {
expect(JSON.parse(serveConfigContent([], [], 'P')).skills).toBeUndefined();
expect(JSON.parse(serveConfigContent(['/a'], [], 'P')).skills).toEqual({ paths: ['/a'] });
});
test('is valid JSON for an env var — no newlines to be mangled by a shell', () => {
expect(serveConfigContent(['/a/skills'], ['/tmp/aff.md'], 'P')).not.toContain('\n');
});
test('the affordances file sits beside the serve state, not in a home', () => {
expect(affordancesPath('/data/opencode_server')).toBe('/data/opencode_server/officer-affordances.md');
});
});