Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .copilot-schema-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.71-2
1.0.73
6 changes: 5 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ When porting features or investigating behavior:

1. **Primary reference**: [nodejs implementation](https://github.com/github/copilot-sdk/tree/main/nodejs) (JavaScript/TypeScript)
2. **Secondary reference**: [python implementation](https://github.com/github/copilot-sdk/tree/main/python) for additional clarity
3. **Local upstream checkout**: The upstream repo is available at `../copilot-sdk` (relative to this repo).
3. **Local upstream checkout**: Resolve the upstream repo with
`bash .github/skills/update-upstream/scripts/resolve-upstream.sh`. This
works from normal checkouts and linked worktrees; set
`COPILOT_SDK_UPSTREAM` when the checkout is not beside the primary
`copilot-sdk-clojure` checkout.
4. **CLI runtime**: The CLI itself is useful for understanding
protocol behavior, but the **SDK source of truth** is always the Node.js SDK, not the CLI protocol types.

Expand Down
61 changes: 38 additions & 23 deletions .github/skills/update-upstream/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: update-upstream
description: Sync the Clojure Copilot SDK with upstream copilot-sdk changes. Runs update.sh, performs gap analysis against Node.js and Python SDKs, ports changes with red/green TDD, runs full CI (E2E tests + examples), gets parallel multi-model code reviews, updates docs, and creates a PR. Use when syncing with new upstream releases or checking for unported changes.
compatibility: Requires copilot CLI authenticated, gh CLI, clojure CLI, bb (babashka). Upstream repo at ../copilot-sdk.
description: Use when syncing the Clojure Copilot SDK with upstream releases or checking for unported Node.js and Python SDK changes.
compatibility: Requires authenticated copilot and gh CLIs, Clojure CLI, bb, and a local github/copilot-sdk checkout beside the primary checkout or specified by COPILOT_SDK_UPSTREAM.
---

# Update Upstream Skill
Expand All @@ -14,19 +14,33 @@ Sync the copilot-sdk-clojure project with upstream [github/copilot-sdk](https://

### Phase 1: Discovery

1. **Sync local `main` first.** Recently-merged PRs may have already
ported some upstream changes, and your feature branch should sit on
top of the latest `main` to avoid duplicate work and rebase conflicts
later:
1. **Refresh `origin/main` without leaving the current worktree branch.**
Recently merged PRs may have already ported upstream changes:
```
git checkout main && git pull --ff-only origin main
git fetch origin main
git rev-list --left-right --count HEAD...origin/main
```
If `main` cannot fast-forward, stop and let the maintainer resolve.
2. Run `./update.sh` from the repo root to pull the latest upstream and list releases.
Never check out `main` inside a linked worktree; the primary checkout may
already have it checked out. If the current branch is only behind, run
`git merge --ff-only origin/main`. If it has diverged, use a fresh project
session from the default branch when available; otherwise ask the
maintainer before integrating `origin/main`.
2. Resolve and fetch the upstream checkout using the tracked, worktree-safe
helper:
```
UPSTREAM_REPO="$(bash .github/skills/update-upstream/scripts/resolve-upstream.sh)" &&
git -C "$UPSTREAM_REPO" fetch --prune --tags origin
```
The helper derives the primary checkout from Git's common directory, so it
works from both normal checkouts and linked worktrees. Set
`COPILOT_SDK_UPSTREAM` to override the sibling checkout location. Shell
tool calls do not share environment, so resolve `UPSTREAM_REPO` again in
each call or chain dependent commands together.
3. Check the current Clojure SDK version in `build.clj` (format: `UPSTREAM.CLJ_PATCH` — see AGENTS.md § Version Management).
4. List upstream commits since our last synced version:
```
cd ../copilot-sdk && git log --oneline <last-tag>..HEAD -- nodejs/
UPSTREAM_REPO="$(bash .github/skills/update-upstream/scripts/resolve-upstream.sh)" &&
git -C "$UPSTREAM_REPO" log --oneline <last-tag>..origin/main -- nodejs/
```
5. For each commit, classify:
- **Port** — Code changes to `nodejs/src/` (types, client, session, generated)
Expand All @@ -36,8 +50,8 @@ Sync the copilot-sdk-clojure project with upstream [github/copilot-sdk](https://

Launch three parallel explore agents to build a comprehensive inventory. Use the file mapping in `references/PROJECT.md` to locate the right files.

1. **Node.js SDK** — Read upstream files listed in references/PROJECT.md (types.ts, client.ts, session.ts, index.ts, generated/). Catalog all public types, methods, event types, and event data fields.
2. **Python SDK** — Read `python/copilot/client.py`, `session.py`, `__init__.py`, `generated/`. Note behavioral differences from Node.js.
1. **Node.js SDK** — Resolve `$UPSTREAM_REPO`, then read the upstream files listed in references/PROJECT.md (types.ts, client.ts, session.ts, index.ts, generated/). Catalog all public types, methods, event types, and event data fields.
2. **Python SDK** — Resolve `$UPSTREAM_REPO`, then read `python/copilot/client.py`, `session.py`, `__init__.py`, and `generated/`. Note behavioral differences from Node.js.
3. **Clojure SDK** — Read all `src/github/copilot_sdk/*.clj`. Catalog public functions, specs, event sets, wire conversion.

Compare inventories to identify gaps:
Expand Down Expand Up @@ -111,21 +125,20 @@ At minimum:

### Phase 8: PR Creation

1. **Confirm `main` is current before branching.** Run
`git fetch origin main && git checkout main && git pull --ff-only` if
you haven't refreshed since Phase 1. A stale local `main` causes
rebase conflicts later, especially when prior sync PRs squash-merge.
2. Create a feature branch: `git checkout -b upstream-sync/v<version>`
1. **Confirm the current worktree branch is based on current `origin/main`.**
Run `git fetch origin main` and inspect
`git rev-list --left-right --count HEAD...origin/main`. Do not check out
local `main`.
2. Keep using the project session's existing branch. If running outside a
project worktree and still on the default branch, create a feature branch
with the app-native branch tool when available.
3. Commit changes in logical commits to make them easy to review commit by commit and with descriptive message and `Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>`
4. Push and create PR with `gh pr create`
5. PR body should include: summary, changes list, validation results, review findings table

If the maintainer asks you to rebase a stale branch onto fresh `main`,
expect that previous round-N sync commits on your branch may already be
present in `origin/main` under squash-merge SHAs. Use `git rebase --skip`
for any commit whose patch is already upstream — Git will print
"patch contents already upstream" for the others and drop them
automatically.
If the branch becomes stale after it has commits, do not rewrite history by
default. Prefer a fresh project session from current `main`, or ask the
maintainer before using an additive merge.

### Phase 9: Reflecting on code review feedback.

Expand Down Expand Up @@ -176,4 +189,6 @@ Real recurring traps when porting upstream changes:

6. **`session.create` and `session.resume` build wire params in two separate functions — keep shared sub-shapes in a named helper.** `build-create-session-params` and `build-resume-session-params` both emit tool defs, system message, provider, MCP servers, custom agents, and commands. A new field on any shape sent by both must be added to both builders, or it ships on create and silently vanishes on resume. Funnel each shared sub-shape through one `*->wire` helper (e.g. `tool-def->wire`, `util/mcp-servers->wire`) rather than duplicating a `cond->` inline.

7. **Sibling repositories must be resolved from Git's common directory, not the worktree root.** In a linked worktree, `../copilot-sdk` points inside the worktree container rather than beside the primary checkout. Always use `scripts/resolve-upstream.sh`; never hard-code an absolute path or derive the sibling from `git rev-parse --show-toplevel`.

For the mechanics of camelCase ↔ kebab-case conversion (including the `?`-suffix rule), see the cheat sheet in `references/PROJECT.md`.
17 changes: 16 additions & 1 deletion .github/skills/update-upstream/references/PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ This reference supplements `AGENTS.md` (the canonical project reference) with sy

For project structure, testing commands, version format, changelog conventions, and code quality expectations, see `AGENTS.md`.

## Upstream Checkout

Resolve the local upstream checkout from any normal checkout or linked
worktree:

```bash
UPSTREAM_REPO="$(bash .github/skills/update-upstream/scripts/resolve-upstream.sh)"
```

The helper finds the primary `copilot-sdk-clojure` checkout through Git's
common directory, then resolves its `copilot-sdk` sibling. Set
`COPILOT_SDK_UPSTREAM` when the upstream checkout lives elsewhere. Re-resolve
the variable in each shell tool call because shell environments do not
persist between calls.

## Upstream ↔ Clojure File Mapping

When syncing, map upstream changes to the corresponding Clojure files:

### Upstream (../copilot-sdk)
### Upstream (`$UPSTREAM_REPO`)

| Upstream File | Contains |
|---------------|----------|
Expand Down
46 changes: 46 additions & 0 deletions .github/skills/update-upstream/scripts/resolve-upstream.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash

set -euo pipefail

if (( $# != 0 )); then
echo "usage: $0" >&2
exit 2
fi

common_git_dir="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || {
echo "error: run this script from the copilot-sdk-clojure repository" >&2
exit 1
}

if [[ -n "${COPILOT_SDK_UPSTREAM:-}" ]]; then
candidate="${COPILOT_SDK_UPSTREAM}"
else
primary_checkout="$(dirname "${common_git_dir}")"
candidate="$(dirname "${primary_checkout}")/copilot-sdk"
fi

upstream_root="$(git -C "${candidate}" rev-parse --show-toplevel 2>/dev/null)" || {
echo "error: upstream github/copilot-sdk checkout not found at ${candidate}" >&2
echo "clone it beside the primary copilot-sdk-clojure checkout or set COPILOT_SDK_UPSTREAM" >&2
exit 1
}

origin_url="$(git -C "${upstream_root}" remote get-url origin 2>/dev/null)" || {
echo "error: upstream checkout has no origin remote: ${upstream_root}" >&2
exit 1
}

case "${origin_url}" in
git@github.com:github/copilot-sdk.git | \
ssh://git@github.com/github/copilot-sdk.git | \
https://github.com/github/copilot-sdk | \
https://github.com/github/copilot-sdk.git)
;;
*)
echo "error: expected origin to be github/copilot-sdk, found ${origin_url}" >&2
echo "set COPILOT_SDK_UPSTREAM to a checkout with the canonical origin remote" >&2
exit 1
;;
esac

printf '%s\n' "${upstream_root}"
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. This change

## [Unreleased]

### Changed (agent workflow)
- **Worktree-safe upstream sync** — the repo-local `update-upstream` skill now
uses a tracked helper to resolve the sibling `github/copilot-sdk` checkout
through Git's common directory, and no longer switches to `main`, creates a
second branch, or depends on an ignored root-level `update.sh`.

### Added (v1.0.7 sync)
- **Opaque tool-definition metadata** — port of upstream
[PR #1864](https://github.com/github/copilot-sdk/pull/1864). `define-tool` and
Expand Down Expand Up @@ -33,13 +39,39 @@ All notable changes to this project will be documented in this file. This change
`"medium"`, `"high"`, or `"xhigh"`. Session create and resume send it as the
exact `reasoningEffort` wire field. When absent, the field is omitted and does
not inherit the session-level `:reasoning-effort`.
- **Strongly typed PascalCase `:exp-assignments` contract** — port of
[upstream PR #2033](https://github.com/github/copilot-sdk/pull/2033). Session
create and resume configs validate the complete `CopilotExpAssignmentResponse`
shape and forward its string-keyed PascalCase fields unchanged.
- **`:on-agent-stop` session hook** — port of
[upstream PR #2054](https://github.com/github/copilot-sdk/pull/2054). Session
`:hooks` accept `:on-agent-stop` for runtime `agentStop` callbacks, using the
existing allow/block hook decision contract.
- **Schema regen from 1.0.71-2 through 1.0.73** — port of upstream package bumps
[PR #2035](https://github.com/github/copilot-sdk/pull/2035) and
[PR #2055](https://github.com/github/copilot-sdk/pull/2055). Regenerated wire
specs and coercions. The curated public event sets now include
`:copilot/assistant.server_tool_progress`,
`:copilot/session.managed_settings_enforced`,
`:copilot/session.managed_settings_resolved`, and
`:copilot/tool_search.activated`; `assistant.turn_retry` and
`model.call_start` remain generated internal-only.

### Fixed
- **Hook invocation response envelopes** — `hooks.invoke` now returns the
canonical `HookInvokeResponse` wire shape, wrapping non-nil handler values
under `output`, omitting `output` for nil values, and preserving opaque MCP
metadata within the nested hook output. Unknown session IDs now return an
RPC error instead of a successful nil result.
- **Custom-agent MCP server IDs** —
[issue #158](https://github.com/copilot-community-sdk/copilot-sdk-clojure/issues/158).
Nested `:mcp-servers` now use the same wire serializer as session-level MCP
servers on both session create and resume, preserving keyword and string
server IDs while converting each server config to the runtime wire shape.
- **Variant-local generated validation** — the Clojure schema generator now
scopes same-named properties with different schemas to each data variant,
preserving `abort`'s closed `reason` enum while `assistant.turn_retry` accepts
open strings.

## [1.0.7-preview.2.1] - 2026-07-15
### Added
Expand Down
Loading