feat(providers): add GitHub Copilot as a community provider#2
Merged
joaobmonteiro merged 7 commits intodevfrom Apr 20, 2026
Merged
feat(providers): add GitHub Copilot as a community provider#2joaobmonteiro merged 7 commits intodevfrom
joaobmonteiro merged 7 commits intodevfrom
Conversation
Introduces `@github/copilot-sdk` (public preview, pinned 0.2.2) as the
second community provider, following the Phase 2 contract established by
Pi: a new provider drops into `packages/providers/src/community/<id>/`
and wires in with a one-line addition to `registerCommunityProviders()`.
The SDK spawns the `copilot` CLI binary over JSON-RPC, so each
`sendQuery()` constructs a fresh `CopilotClient` (cwd is pinned at
construction time, which is required to support different worktrees)
and tears it down in a `finally`. The SDK is event-emitter based; we
reuse the AsyncQueue pattern from Pi's event-bridge to bridge events
into Archon's async-generator contract.
v1 capabilities are intentionally conservative:
- sessionResume (SDK exposes listSessions / resumeSession)
- effortControl (SDK supports reasoningEffort low|medium|high|xhigh)
- envInjection (per-codebase env vars passed to spawned CLI)
Everything else (mcp, toolRestrictions, skills, structuredOutput, hooks,
sandbox, costControl, fallbackModel, thinkingControl) starts false;
flipping a flag requires wiring the corresponding plumbing first so the
dag-executor warnings stay honest.
New:
- packages/providers/src/community/copilot/ (9 source files + 6 tests)
Modified:
- packages/providers/package.json: pin @github/copilot-sdk@0.2.2 exact,
add ./community/copilot export, extend test script
- packages/providers/src/types.ts: CopilotProviderDefaults interface
- packages/providers/src/registry.ts: registerCopilotProvider() call
- packages/providers/src/index.ts: public re-exports
- packages/providers/src/registry.test.ts: capability + registration
+ collision-with-pi tests
- packages/core/src/config/config-types.ts: re-export defaults type
(no intersection change — community providers live behind the
generic [string] index)
- .env.example: COPILOT_GITHUB_TOKEN / COPILOT_CLI_PATH hints
- docs-web/.../ai-assistants.md: full Copilot section
Auth: delegates to Copilot CLI's credential resolution
(`gh auth login` / `COPILOT_GITHUB_TOKEN` / `assistants.copilot.githubToken`).
Permission callback is hard-wired to `approveAll` in v1 — matches the
trust model already enforced by worktree isolation.
SDK pinned exact rather than a range because `0.2.x` is explicitly in
public preview with breaking-change warnings; bumps should be deliberate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop-in reproducible dev environment. `devbox shell` gives contributors bun, nodejs, git, gh, jq, postgresql-client, python3, and uv at pinned versions without polluting the host. `bun` is pinned to `1.3.11` to match the production Dockerfile, so local validation mirrors CI. - devbox.json: package pins + shell scripts (setup/validate/dev/test) - .gitignore: ignore the local `.devbox/` profile directory (devbox.json and devbox.lock stay tracked so pins are reproducible) - CONTRIBUTING.md: short "Reproducible toolchain via Devbox" blurb in Getting Started No behavior change — contributors who prefer their own toolchain are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`bun install --frozen-lockfile` (used by Dockerfile) requires the lockfile to match package.json. Adds the SDK and its transitive deps (@github/copilot platform-binary subpackages, vscode-jsonrpc, zod). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Loop nodes silently drop per-node provider/model/effort overrides (schema strips them, dispatcher tries to read them → always falls back to workflow-level provider). 2. Copilot provider can't locate its own bundled native CLI when the runtime is Bun — forces users to hand-wire a fragile node_modules path to work around the Node 24+ JS loader. Both include repro, root cause, and suggested fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The DAG node schema transform silently stripped `provider` and `model` when constructing a LoopNode, even though the dag-executor's loop dispatcher (dag-node.ts 2351-2391) reads both fields to pick the per-iteration AI provider and model. End result: `provider: copilot` on a loop node was a no-op — every iteration ran on the workflow-level default. - Include `provider`, `model`, and `shared` (retry) in the loop branch of the transform. Other aiOnly fields (effort, mcp, hooks, etc.) stay stripped because the loop executor doesn't consume them per-node — that matches LOOP_NODE_AI_FIELDS, which already documents this intent. - Let the existing superRefine compatibility check (`isModelCompatible`) run against loop nodes too, so parse-time errors surface earlier than the runtime check in the dispatcher. - Extend the existing "model/provider on loop nodes doesn't warn" test to also assert the fields round-trip through the schema — the assertion was missing, which is why the bug hid for so long. Fixes BUG.md#1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`@github/copilot-sdk`'s default cliPath points at the JS loader in
`@github/copilot/npm-loader.js`, which is spawned with node. In the
stock archon Docker image `node` is Bun's node-compat shim, and the
loader's bundled entry imports `node:sea` (Node 20+) — so the CLI dies
at parse time before the SDK can use it.
The platform-specific native binary
(`@github/copilot-{platform}-{arch}/copilot`) ships as a peer install
and runs without any node runtime. It's been there all along; archon
just never looked for it.
- New `cli-resolver.ts` module with `resolveCopilotCliPath()` —
three-tier fallback (config cliPath → COPILOT_CLI_PATH env →
native-binary probe via createRequire). Falls through to `undefined`
when the platform package isn't installed, so the SDK's existing
default resolution still runs.
- Provider uses the new resolver instead of the manual two-tier chain.
- Unit tests cover config/env precedence and the
fileExists-returns-false fallback.
Also reflow BUG.md (prettier) — no content changes.
Fixes BUG.md#2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`@github/copilot-sdk` emits every typed session event with the payload
wrapped under a `data` property (see `dist/generated/session-events.d.ts`).
archon's event-bridge reads at the top level, so every tool event decays
to `toolName: "unknown"` / `toolInput: {}` in the Web UI, and session
resume/token telemetry never gets populated.
- Add `unwrapEventData` — single point that pulls `event.data` with
defensive fallbacks.
- Replace `extractToolEventFields` with separate start/complete extractors:
- start: `data.toolName` + `data.arguments` + `data.toolCallId`.
- complete: `data.toolCallId` + `data.success` (→ isError) +
`data.result.detailedContent ?? data.result.content` (SDK's result is
an object, not a string).
- Build a per-session `toolCallId → toolName` map on start events so the
`tool_result` chunk reports the real tool name. Completion events only
carry the call id.
- Subscribe to `session.start` for `sessionId` (terminal `session.idle`
doesn't carry it) and to `assistant.usage` for per-turn token counts.
`session.idle` becomes a plain "turn over" marker.
- Update bridge + provider tests to match the real wrapped shape
(`{ type, data: {...} }`). The two previously-failing tests in
event-bridge.test.ts are fixed along the way — they were failing
because they asserted against the flat shape that never matched the
real SDK.
- Pin `Array<T>` → `T[]` and silence a pre-existing
`no-base-to-string` warning surfaced by lint on the edited file.
Fixes BUG.md#3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joaobmonteiro
pushed a commit
that referenced
this pull request
Apr 26, 2026
* feat: Add Telegram MarkdownV2 formatting for AI responses - Add telegramify-markdown package to convert GitHub-flavored markdown to Telegram's MarkdownV2 format - Create telegram-markdown utility with: - convertToTelegramMarkdown(): Main conversion function - stripMarkdown(): Fallback for plain text - escapeMarkdownV2(): Manual escaping helper - Update TelegramAdapter to: - Format short messages with MarkdownV2 - Split long messages by paragraphs for better formatting - Fallback to stripped plain text when MarkdownV2 parsing fails - Add comprehensive tests for markdown conversion Transforms AI responses from raw markdown (## headers, **bold**) to properly formatted Telegram messages with bold text, code blocks, etc. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * fix: Correct telegram adapter test mocks and assertions - Add missing stripMarkdown mock to telegram-markdown module mock - Fix paragraph splitting test to use double newlines (\n\n) matching actual implementation behavior - Use smaller paragraph sizes (3000 chars) to stay within MAX_LENGTH Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
joaobmonteiro
pushed a commit
that referenced
this pull request
Apr 26, 2026
When users request multiple issues to be fixed (e.g., "fix issues #1, #2, and coleam00#3"), the skill now clearly instructs Claude to run each workflow separately rather than combining them into a single command.
joaobmonteiro
pushed a commit
that referenced
this pull request
Apr 26, 2026
* Restructure ~/.archon/ to project-centric layout Consolidate per-project files under ~/.archon/workspaces/owner/repo/ with dedicated source/, worktrees/, artifacts/, and logs/ subdirectories. Key changes: - Add project-centric path utilities (getProjectRoot, getProjectSourcePath, etc.) - Clone repos into workspaces/owner/repo/source/ instead of workspaces/owner/repo/ - Register local repos with symlink from source/ to actual path - Move worktree base under project directory (avoid double-nesting owner/repo) - Externalize artifacts via $ARTIFACTS_DIR variable (out of git repos) - Externalize logs via logDir parameter (logger accepts dir directly) - CLI auto-registers unregistered repos for all workflow runs - Update all command templates to use $ARTIFACTS_DIR - Strict owner/repo parsing (reject nested slashes) * Fix symlink creation for empty source dirs and readonly SQLite results Two bugs found during end-to-end testing: 1. createProjectSourceSymlink returned early for empty source/ directories created by ensureProjectStructure, preventing symlink creation for registered repos. Now checks if directory is empty before skipping. 2. registerRepoAtPath tried to assign properties on frozen SQLite result objects (getCodebaseCommands returns readonly parsed JSON in Bun's SQLite). Fixed by spreading into a mutable copy. * Address PR review: empty catches, dynamic imports, duplicate DB lookup - Critical #2-3: Replace empty catches on rm() with ENOENT-specific catches in createProjectSourceSymlink and cloneRepository. Permission errors and other failures now propagate instead of being swallowed. - Important coleam00#4: Consolidate resolveArtifactsDir and resolveLogDir into a single resolveProjectPaths function that queries the database once instead of twice per workflow start. - Important coleam00#6: Replace dynamic imports (readlink, rm) with static imports in archon-paths.ts and clone.ts. * Add tests for project-centric path resolution and symlink creation - Test resolveProjectPaths with valid codebase (project-scoped vs fallback) - Test $ARTIFACTS_DIR substitution in workflow command prompts - Test ensureProjectStructure creates all 4 subdirectories, idempotent - Test createProjectSourceSymlink: create, no-op, conflict, clone case - Test getWorktreeBase/isProjectScopedWorktreeBase with project-scoped paths * Add path traversal guard to parseOwnerRepo Reject path traversal characters (.., special chars, spaces) in owner/repo segments. Inputs flow from user-facing web API (POST /api/codebases) through parseOwnerRepo to filesystem path construction via join(), making this a real attack surface, not a theoretical one. * Delegate local paths in /clone to registerRepository (coleam00#383) When /clone receives a local filesystem path (starting with /, ~, or .), delegate to registerRepository instead of treating it as a URL. This ensures the codebase name comes from the git remote (e.g. Wirasm/kild) rather than filesystem path segments (e.g. mine/kild). See coleam00#383 for the broader clone/register identity rethink.
joaobmonteiro
pushed a commit
that referenced
this pull request
Apr 26, 2026
…coleam00#1263) * fix(bundled-defaults): auto-generate import list, emit inline strings Root-cause fix for bundle drift (15 commands + 7 workflows previously missing from binary distributions) and a prerequisite for packaging @archon/workflows as a Node-loadable SDK. The hand-maintained `bundled-defaults.ts` import list is replaced by `scripts/generate-bundled-defaults.ts`, which walks `.archon/{commands,workflows}/defaults/` and emits a generated source file with inline string literals. `bundled-defaults.ts` becomes a thin facade that re-exports the generated records and keeps the `isBinaryBuild()` helper. Inline strings (via JSON.stringify) replace Bun's `import X from '...' with { type: 'text' }` attributes. The binary build still embeds the data at compile time, but the module now loads under Node too — removing SDK blocker #2. - Generator: `scripts/generate-bundled-defaults.ts` (+ `--check` mode for CI) - `package.json`: `generate:bundled`, `check:bundled`; wired into `validate` - `build-binaries.sh`: regenerates defaults before compile - Test: `bundle completeness` now derives expected set from on-disk files - All 56 defaults (36 commands + 20 workflows) now in the bundle * fix(bundled-defaults): address PR review feedback Review: coleam00#1263 (comment) Generator: - Guard against .yaml/.yml name collisions (previously silent overwrite) - Add early access() check with actionable error when run from wrong cwd - Type top-level catch as unknown; print only message for Error instances - Drop redundant /* eslint-disable */ emission (global ignore covers it) - Fix misleading CI-mechanism claim in header comment - Collapse dead `if (!ext) continue` guard into a single typed pass Scripts get real type-checking + linting: - New scripts/tsconfig.json extending root config - type-check now includes scripts/ via `tsc --noEmit -p scripts/tsconfig.json` - Drop `scripts/**` from eslint ignores; add to projectService file scope Tests: - Inline listNames helper (Rule of Three) - Drop redundant toBeDefined/typeof assertions; the Record<string, string> type plus length > 50 already cover them - Add content-fidelity round-trip assertion (defense against generator content bugs, not just key-set drift) Facade comment: drop dead reference to .claude/rules/dx-quirks.md. CI: wire `bun run check:bundled` into .github/workflows/test.yml so the header's CI-verification claim is truthful. Docs: CLAUDE.md step count four→five; add contributor bullet about `bun run generate:bundled` in the Defaults section and CONTRIBUTING.md. * chore(e2e): bump Codex model to gpt-5.2 gpt-5.1-codex-mini is deprecated and unavailable on ChatGPT-account Codex auth. Plain gpt-5.2 works. Verified end-to-end: - e2e-codex-smoke: structured output returns {category:'math'} - e2e-mixed-providers: claude+codex both return expected tokens
joaobmonteiro
pushed a commit
that referenced
this pull request
Apr 26, 2026
…gent) (coleam00#1270) * feat(providers): add Pi community provider (@mariozechner/pi-coding-agent) Introduces Pi as the first community provider under the Phase 2 registry, registered with builtIn: false. Wraps Pi's full coding-agent harness the same way ClaudeProvider wraps @anthropic-ai/claude-agent-sdk and CodexProvider wraps @openai/codex-sdk. - PiProvider implements IAgentProvider; fresh AgentSession per sendQuery call - AsyncQueue bridges Pi's callback-based session.subscribe() to Archon's AsyncGenerator<MessageChunk> contract - Server-safe: AuthStorage.inMemory + SessionManager.inMemory + SettingsManager.inMemory + DefaultResourceLoader with all no* flags — no filesystem access, no cross-request state - API key seeded per-call from options.env → process.env fallback - Model refs: '<pi-provider-id>/<model-id>' (e.g. google/gemini-2.5-pro, openrouter/qwen/qwen3-coder) with syntactic compatibility check - registerPiProvider() wired at CLI, server, and config-loader entrypoints, kept separate from registerBuiltinProviders() since builtIn: false is load-bearing for the community-provider validation story - All 12 capability flags declared false in v1 — dag-executor warnings fire honestly for any unmapped nodeConfig field - 58 new tests covering event mapping, async-queue semantics, model-ref parsing, defensive config parsing, registry integration Supported Pi providers (v1): anthropic, openai, google, groq, mistral, cerebras, xai, openrouter, huggingface. Extend PI_PROVIDER_ENV_VARS as needed. Out of scope (v1): session resume, MCP, hooks, skills mapping, thinking level mapping, structured output, OAuth flows, model catalog validation. These remain false on PI_CAPABILITIES until intentionally wired. * feat(providers/pi): read ~/.pi/agent/auth.json for OAuth + api_key passthrough Replaces the v1 env-var-only auth flow with AuthStorage.create(), which reads ~/.pi/agent/auth.json. This transparently picks up credentials the user has populated via `pi` → `/login` (OAuth subscriptions: Claude Pro/Max, ChatGPT Plus, GitHub Copilot, Gemini CLI, Antigravity) or by editing the file directly. Env-var behavior preserved: when ANTHROPIC_API_KEY / GEMINI_API_KEY / etc. is set (in process.env or per-request options.env), the adapter calls setRuntimeApiKey which is priority #1 in Pi's resolution chain. Auth.json entries are priority #2-coleam00#3. Pi's internal env-var fallback remains priority coleam00#4 as a safety net. Archon does not implement OAuth flows itself — it only rides on creds the user created via the Pi CLI. OAuth refresh still happens inside Pi (auth-storage.ts:369-413) under a file lock; concurrent refreshes between the Pi CLI and Archon are race-safe by Pi's own design. - Fail-fast error now mentions both the env-var path and `pi /login` - 2 new tests: OAuth cred from auth.json; env var wins over auth.json - 12 existing tests still pass (env-var-only path unchanged) CI compatibility: no auth.json in CI, no change — env-var (secrets) flows through Pi's getEnvApiKey fallback identically to v1. * test(e2e): add Pi provider smoke test workflow Mirrors e2e-claude-smoke.yaml: single prompt node + bash assert. Targets `anthropic/claude-haiku-4-5` via `provider: pi`; works in CI (ANTHROPIC_API_KEY secret) and locally (user's `pi /login` OAuth). Verified locally with an Anthropic OAuth subscription — full run takes ~4s from session_started to assert PASS, exercising the async-queue bridge and agent_end → result-chunk assembly under real Pi event timing. Not yet wired into .github/workflows/e2e-smoke.yml — separate PR once this lands, to keep the Pi provider PR minimal. * feat(providers/pi): v2 — thinkingLevel, tool restrictions, systemPrompt Extends the Pi adapter with three node-level translations, flipping the corresponding capability flags from false → true so the dag-executor no longer emits warnings for these fields on Pi nodes. 1. effort / thinking → Pi thinkingLevel (options-translator.ts) - Archon EffortLevel enum: low|medium|high|max (from packages/workflows/src/schemas/dag-node.ts). `max` maps to Pi's `xhigh` since Archon's enum lacks it. - Pi-native strings (minimal, xhigh, off) also accepted for programmatic callers bypassing the schema. - `off` on either field → no thinkingLevel (Pi's implicit off). - Claude-shape object `thinking: {type:'enabled', budget_tokens:N}` yields a system warning and is not applied. 2. allowed_tools / denied_tools → filtered Pi built-in tools - Supports all 7 Pi tools: read, bash, edit, write, grep, find, ls. - Case-insensitive normalization. - Empty `allowed_tools: []` means no tools (LLM-only), matching e2e-claude-smoke's idiom. - Unknown names (Claude-specific like `WebFetch`) collected and surfaced as a system warning; ignored tools don't fail the run. 3. systemPrompt (AgentRequestOptions + nodeConfig.systemPrompt) - Threaded through `DefaultResourceLoader({systemPrompt})`; Pi's default prompt is replaced entirely. Request-level wins over node-level. Capability flag changes: - thinkingControl: false → true - effortControl: false → true - toolRestrictions: false → true Package delta: - +1 direct dep: @sinclair/typebox (Pi types reference it; adding as direct dep resolves the TS portable-type error). - +1 test file: options-translator.test.ts (19 tests, 100% coverage). - provider.test.ts extended with 11 new tests covering all three paths. - registry.test.ts updated: capability assertion reflects new flags. Live-verified: `bun run cli workflow run e2e-pi-smoke --no-worktree` succeeds in 1.2s with thinkingLevel=low, toolCount=0. Smoke YAML updated to use `effort: low` (schema-valid) + `allowed_tools: []` (LLM-only). * test(e2e): add comprehensive Pi smoke covering every CI-compatible node type Exercises every node type Archon supports under `provider: pi`, except `approval:` (pauses for human input, incompatible with CI): 1. prompt — inline AI prompt 2. command — named command file (uses e2e-echo-command.md) 3. loop — bounded iterative AI prompt (max_iterations: 2) 4. bash — shell script with JSON output 5. script — bun runtime (echo-args.js) 6. script — uv / Python runtime (echo-py.py) Plus DAG features on top of Pi: - depends_on + $nodeId.output substitution - when: conditional with JSON dot-access - trigger_rule: all_success merge - final assert node validates every upstream output is non-empty Complements the minimal e2e-pi-smoke.yaml — that stays as the fast-path smoke for connectivity checks; this one is the broader surface coverage. Verified locally end-to-end against Anthropic OAuth (pi /login): PASS, all 9 non-final nodes produce output, assert succeeds. * feat(providers/pi): resolve Archon `skills:` names to Pi skill paths Flips capabilities.skills: false → true by translating Archon's name-based `skills:` nodeConfig (e.g. `skills: [agent-browser]`) to absolute directory paths Pi's DefaultResourceLoader can consume via additionalSkillPaths. Search order for each skill name (first match wins): 1. <cwd>/.agents/skills/<name>/ — project-local, agentskills.io 2. <cwd>/.claude/skills/<name>/ — project-local, Claude convention 3. ~/.agents/skills/<name>/ — user-global, agentskills.io 4. ~/.claude/skills/<name>/ — user-global, Claude convention A directory resolves only if it contains a SKILL.md. Unresolved names are collected and surfaced as a system-chunk warning (e.g. "Pi could not resolve skill names: foo, bar. Searched .agents/skills and .claude/skills (project + user-global)."), matching the semantic of "requested but not found" without aborting the run. Pi's buildSystemPrompt auto-appends the agentskills.io XML block for each loaded skill, so the model sees them — no separate prompt injection needed (Pi differs from Claude here; Claude wraps in an AgentDefinition with a preloaded prompt, Pi uses XML block in system prompt). Ancestor directory traversal above cwd is deliberately skipped in this pass — matches the Pi provider's cwd-bound scope and avoids ambiguity about which repo's skills win when Archon runs from a subdirectory. Bun's os.homedir() bypasses the HOME env var; the resolver uses `process.env.HOME ?? homedir()` so tests can stage a synthetic home dir. Tests: - 11 new tests in options-translator.test.ts cover project/user, .agents/ vs .claude/, project-wins-over-user, SKILL.md presence check, dedup, missing-name collection. - 2 new integration tests in provider.test.ts cover the missing-skill warning path and the "no skills configured → no additionalSkillPaths" path. - registry.test.ts updated to assert skills: true in capabilities. Live-verified locally: `.claude/skills/archon-dev/SKILL.md` resolves, pi.session_started log shows `skillCount: 1, missingSkillCount: 0`, smoke workflow passes in 1.2s. * feat(providers/pi): session resume via Pi session store Flips capabilities.sessionResume: false → true. Pi now persists sessions under ~/.pi/agent/sessions/<encoded-cwd>/<uuid>.jsonl by default — same pattern Claude and Codex use for their respective stores, same blast radius as those providers. Flow: - No resumeSessionId → SessionManager.create(cwd) (fresh, persisted) - resumeSessionId + match in SessionManager.list(cwd) → open(path) - resumeSessionId + no match → fresh session + system warning ("⚠️ Could not resume Pi session. Starting fresh conversation.") Matches Codex's resume_thread_failed fallback at packages/providers/src/codex/provider.ts:553-558. The sessionId flows back to Archon via the terminal `result` chunk — bridgeSession annotates it with session.sessionId unconditionally so Archon's orchestrator can persist it and pass it as resumeSessionId on the next turn. Same mechanism used for Claude/Codex. Cross-cwd resume (e.g. worktree switch) is deliberately not supported in this pass: list(cwd) scans only the current cwd's session dir. A workflow that changes cwd mid-run lands on a fresh session, which matches Pi's mental model. Bridge sessionId annotation uses session.sessionId, which Pi always populates (UUID) — so no special-case for inMemory sessions is needed. Factored the resolver into session-resolver.ts (5 unit tests): - no id → create - id + match → open - id + no match → create with resumeFailed: true - list() throws → resumeFailed: true (graceful) - empty-string id → treated as "no resume requested" Integration tests in provider.test.ts add 3 cases: - resume-not-found yields warning + calls create - resume-match calls open with the file path, no warning - result chunk always carries sessionId Verified live end-to-end against Anthropic OAuth: - first call → sessionId 019d...; model replies "noted" - second call with that sessionId → "resumed: true" in logs; model correctly recalls prior turn ("Crimson.") - bogus sessionId → "⚠️ Could not resume..." warning + fresh UUID * refactor(providers,core): generalize community-provider registration Addresses the community-pattern regression flagged in the PR coleam00#1270 review: a second community provider should require editing only its own directory, not seven files across providers/ + core/ + cli/ + server/. Three changes: 1. Drop typed `pi` slot from AssistantDefaultsConfig + AssistantDefaults. Community providers live behind the generic `[string]` index that `ProviderDefaultsMap` was explicitly designed to provide. The typed claude/codex slots stay — they give IDE autocomplete for built-in config access without `as` casts, which was the whole reason the intersection exists. Community providers parse their own config via Record<string, unknown> anyway, so the typed slot added no real parser safety. 2. Loop-based getDefaults + mergeAssistantDefaults. No more hardcoded `pi: {}` spreads. getDefaults() seeds from `getRegisteredProviders()`; mergeAssistantDefaults clones every slot present in `base`. Adding a new provider requires zero edits to this function. 3. New `registerCommunityProviders()` aggregator in registry.ts. Entrypoints (CLI, server, config-loader) call ONE function after `registerBuiltinProviders()` rather than one call per community provider. Adding a new community provider is now a single-line edit to registerCommunityProviders(). This makes Pi (and future community providers) actually behave like Phase 2 (coleam00#1195) advertised: drop the implementation under packages/providers/src/community/<id>/, export a `register<Id>Provider`, add one line to the aggregator. Tests: - New `registerCommunityProviders` suite (2 tests: registers pi, idempotent). - config-loader.test updated: assert built-in slots explicitly rather than exhaustive map shape. No functional change for Pi end-users. Purely structural. * fix(providers/pi,core): correctness + hygiene fixes from PR coleam00#1270 review Addresses six of the review's important findings, all within the same PR branch: 1. envInjection: false → true The provider reads requestOptions.env on every call (for API-key passthrough). Declaring the capability false caused a spurious dag-executor warning for every Pi user who configured codebase env vars — which is the MAIN auth path. Flipping to true removes the false positive. 2. toSafeAssistantDefaults: denylist → allowlist The old shape deleted `additionalDirectories`, `settingSources`, `codexBinaryPath` before sending defaults to the web UI. Any future sensitive provider field (OAuth token, absolute path, internal metadata) would silently leak via the `[key: string]: unknown` index signature. New SAFE_ASSISTANT_FIELDS map lists exactly what to expose per provider; unknown providers get an empty allowlist so the web UI sees "provider exists" but no config details. 3. AsyncQueue single-consumer invariant The type was documented single-consumer but unenforced. A second `for await` would silently race with the first over buffer + waiters. Added a synchronous guard in Symbol.asyncIterator that throws on second call — copy-paste mistakes now fail fast with a clear message instead of dropping items. 4. session.dispose() / session.abort() silent catches Both catch blocks now log at debug via a module-scoped logger so SDK regressions surface without polluting normal output. 5. Type scripted events as AgentSessionEvent in provider.test.ts Was `Record<string, unknown>` — Pi field renames would silently keep tests passing. Now typed against Pi's actual event union. 6. Leaked /tmp/pi-research/... path in provider.ts comment Local-machine path that crept in during research. Replaced with the upstream GitHub URL (matches convention at provider.ts:110). Plus review-flagged simplifications: - Extract lookupPiModel wrapper — isolates the `as unknown as` cast behind one searchable name. - Hoist QueueItem → BridgeQueueItem at module scope (export'd for test visibility; not used externally yet but enables unit testing the mapping in isolation if needed later). - getRegisteredProviderNames: remove side-effecting registration calls. `loadConfig()` already bootstraps the registry before any caller can observe this helper — the hidden coupling was misleading. Plus missing-coverage tests from the review (pr-test-analyzer): - session.prompt() rejection → error surfaces to consumer - pre-aborted signal → session.abort() called - mid-stream abort → session.abort() called - modelFallbackMessage → system chunk yielded - AsyncQueue second-consumer → throws synchronously No behavioral changes for end users beyond the envInjection warning fix. * docs: Pi provider + community-provider contributor guide Addresses the PR coleam00#1270 review's docs-impact findings: the original Pi PR had no user-facing or contributor-facing documentation, and architecture.md still referenced the pre-Phase-2 factory.ts pattern (factory.ts was deleted in coleam00#1195). 1. packages/docs-web/src/content/docs/reference/architecture.md - Replace stale factory.ts references with the registry pattern. - Update inline IAgentProvider block: add getCapabilities, add options parameter. - Rewrite MessageChunk block as the actual discriminated union (was a placeholder with optional fields that didn't match the current type). - "Adding a New AI Agent Provider" checklist now distinguishes built-in (register in registerBuiltinProviders) from community (separate guide). Links to the new contributor guide. 2. packages/docs-web/src/content/docs/contributing/adding-a-community-provider.md (new) - Step-by-step guide using Pi as the reference implementation. - Covers: directory layout, capability discipline (start false, flip one at a time), provider class skeleton, registration via aggregator, test isolation (Bun mock.module pollution), what NOT to do (no edits to AssistantDefaultsConfig, no direct registerProvider from entrypoints, no overclaiming capabilities). 3. packages/docs-web/src/content/docs/getting-started/ai-assistants.md - New "Pi (Community Provider)" section: install, OAuth + API-key table per Pi backend, model ref format, workflow examples, capability matrix showing what Pi supports (session resume, tool restrictions, effort/thinking, skills, system prompt, envInjection) and what it doesn't (MCP, hooks, structured output, cost control, fallback model, sandbox). 4. .env.example - New Pi section with commented env vars for each supported backend (ANTHROPIC_API_KEY through HUGGINGFACE_API_KEY), each paired with its Pi provider id. OAuth flow (pi /login → auth.json) is explicitly called out — Archon reads that file too. 5. CHANGELOG.md - Unreleased entry for Pi, registerCommunityProviders aggregator, and the new contributor guide.
joaobmonteiro
added a commit
that referenced
this pull request
Apr 26, 2026
feat(providers): add GitHub Copilot as a community provider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@github/copilot-sdk@0.2.2as the second community provider (after Pi), wired viaregisterCommunityProviders(). EachsendQuery()spins up a freshCopilotClientwith per-callcwd, bridges JSON-RPC events into Archon's async-generator contract (reusing Pi'sAsyncQueue), and tears down infinally.sessionResume,effortControl,envInjectiononly.mcp,toolRestrictions,skills,structuredOutput,hooks,sandbox,costControl,fallbackModel,thinkingControlare allfalseuntil the plumbing is wired.gh auth login/COPILOT_GITHUB_TOKEN/assistants.copilot.githubToken). Permission callback hard-wired toapproveAll— matches the trust model already enforced by worktree isolation.bun@1.3.11locally viadevbox.jsonto match the production Dockerfile.Bugs surfaced + fixed during E2E
provider/modeloverrides. The DAG schema transform stripped both fields fromLoopNode, soprovider: copiloton a loop was a no-op. Fix includes them in the loop branch of the transform and lets the existing model-compatibility check run against loops.cliPathpoints at@github/copilot/npm-loader.js, which importsnode:sea— dies under Bun's node-compat shim. Newcli-resolver.tsprobes@github/copilot-{platform}-{arch}/copilotviacreateRequire, with config-cliPath → env → native-binary → SDK-default fallback.data(seesession-events.d.ts); bridge read top-level, so every tool call decayed totoolName: "unknown"/toolInput: {}in the Web UI and session telemetry never populated. NewunwrapEventData+ splitextractToolStart/CompleteFields+ per-sessiontoolCallId → toolNamemap fix the ingestion.Test plan
bun run validateclean across all 10 packages (type-check + lint + format + tests, 153+ copilot/workflow tests green)hmq-backend-pipeline-copilotworkflow onagentic-worflowrepo:tool_name: "unknown"← confirmed bugbash,view,glob,report_intent) with populatedtool_inputcopilot.session_startinglog lines aligned 1:1 with loop iterations on AWF-7;planstep still runs on Claude as expectedcliPathin.archon/config.yaml(hasCliPath: truein logs comes from the native binary probe, not user config)🤖 Generated with Claude Code