Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
b76a11b
fix(state): stabilize fork migration parent links
cyq1017 May 31, 2026
1855033
test(state): cover same-second migration idempotency
Hmbown Jun 1, 2026
3df0189
feat(config): load typed ask permissions file
greyfreedom May 31, 2026
69cff93
docs(config): use exact path in permissions example
Hmbown Jun 1, 2026
4979190
feat(tools): add byte-level schema canonicalize for prefix-cache stab…
Jun 1, 2026
cb4f660
fix(tui): contain Windows shell process trees
aboimpinto Jun 1, 2026
382635e
fix(tui): harden Windows job cleanup
aboimpinto Jun 1, 2026
998af56
chore(release): harden deepseek-tui deprecation path
Hmbown Jun 1, 2026
bc34cd1
fix(tui): hold subagent cap until status reconciles
cyq1017 Jun 1, 2026
a09af20
feat(web_search): enable parallel execution for read-only search tool
Implementist Jun 1, 2026
14ea072
fix(tui): close Windows job before output joins
aboimpinto Jun 1, 2026
54a9399
test(tui): cover Windows job cleanup fallbacks
aboimpinto Jun 1, 2026
2c256d7
fix(tui): close Windows job before foreground joins
aboimpinto Jun 1, 2026
242899d
feat: run ToolCallBefore hooks before tool execution
aboimpinto May 27, 2026
796e95c
fix: address PR #2511 review comments
aboimpinto Jun 1, 2026
2622db4
fix: cargo fmt formatting for lint compliance
aboimpinto Jun 1, 2026
cc923d6
fix: address greptile review comments — remove double-firing, wrap bl…
aboimpinto Jun 1, 2026
77b57bd
fix: initialize hook_executor for fresh sessions to fix greptile P1 r…
aboimpinto Jun 1, 2026
2ca2927
fix: clippy needless_return and fmt compliance
aboimpinto Jun 1, 2026
bb64018
feat(tui): add configurable auto-compact threshold
Hmbown Jun 1, 2026
472cd44
fix(tui): expose auto-compact threshold in config view
Hmbown Jun 1, 2026
4ff9bba
fix(tui): keep config scope column visible
Hmbown Jun 1, 2026
7dfec0e
fix(config): honor workspace shell opt-in
cyq1017 Jun 1, 2026
1d8cbbd
fix(config): normalize windows workspace paths
cyq1017 Jun 1, 2026
3d5edfe
test(config): cover legacy workspace shell opt-in
Hmbown Jun 1, 2026
588e54f
fix(mcp): surface invalid stdio output
cyq1017 Jun 1, 2026
c81d1c2
test(mcp): cover invalid stdio preview redaction
Hmbown Jun 1, 2026
a976758
fix(tui): hint mention depth cap on misses
cyq1017 Jun 1, 2026
29f5766
fix(tui): narrow mention depth hint
cyq1017 Jun 1, 2026
e2201b8
fix(tui): read Wayland clipboard via wl-paste
cyq1017 Jun 1, 2026
9f33c4d
fix(tui): suppress wl-paste trailing newline
Hmbown Jun 1, 2026
1605d8d
fix(sandbox): allow tty device in seatbelt profile
cyq1017 Jun 1, 2026
46de1a9
fix(tui): refresh prompt on model switch
cyq1017 Jun 1, 2026
bc7f98a
fix(tui): refresh prompt on mode changes
Hmbown Jun 1, 2026
3b5727f
fix(tui): prefer codewhale settings path
cyq1017 Jun 1, 2026
0eb2ff5
fix(tui): isolate settings path fallback tests
cyq1017 Jun 1, 2026
91c5bb6
fix(tui): keep tui prefs under codewhale home
Hmbown Jun 1, 2026
c52769e
feat(tools): add parallel verifier ensemble
Hmbown Jun 1, 2026
57c10c7
fix(tui): compact tool-call UI and context
Hmbown Jun 1, 2026
d71cba6
test(tui): wait for background shell completion
cyq1017 Jun 1, 2026
eba019a
test(tui): align activity labels with semantic tools
Hmbown Jun 2, 2026
7c06cf5
fix(tui): guide bug reports toward failure causes
cyq1017 Jun 1, 2026
5a909ee
docs(providers): clarify local model tool calls
cyq1017 Jun 1, 2026
5f3cc3c
docs(rebrand): clarify state migration paths
cyq1017 Jun 1, 2026
3db7b40
docs(rebrand): address migration review notes
cyq1017 Jun 1, 2026
9bd08c2
docs(tui): document statusline footer items
cyq1017 Jun 1, 2026
ae2000b
docs(tui): align statusline customization limits
Hmbown Jun 2, 2026
fda2141
docs: clarify shell tool mode availability
cyq1017 Jun 1, 2026
9e93269
docs: polish mode availability table
cyq1017 Jun 1, 2026
cbd6239
docs: mark agent shell tools approval-gated
Hmbown Jun 2, 2026
908a25d
test(mcp): close stale-session mock responses cleanly
cyq1017 Jun 2, 2026
73cd721
feat(tui): add mention browser completions
cyq1017 Jun 1, 2026
f185d46
fix(tui): expose mention behavior in config
cyq1017 Jun 1, 2026
c81cdab
feat(tui): add bang shell command shortcut
reidliu41 Jun 2, 2026
5becfda
fix(tui): use effective model window in context inspector
cyq1017 Jun 1, 2026
c148b00
fix(npm): prefer binary version output
cyq1017 Jun 2, 2026
650d1a6
fix(subagent): guard truncated tool calls
cyq1017 Jun 1, 2026
537afcf
fix(subagent): cap truncated response retries
cyq1017 Jun 2, 2026
6144d64
fix(config): report legacy config migration
cyq1017 Jun 2, 2026
eff4e99
test(config): stabilize migration home on windows
cyq1017 Jun 2, 2026
0842b3f
test(config): use real legacy home on windows
cyq1017 Jun 2, 2026
556e0b4
fix(tui): use theme colors in sidebar panels instead of hardcoded pal…
Jun 1, 2026
b1cc344
fix(tui): force full repaint on theme switch to prevent stale sidebar…
Jun 1, 2026
baba81c
fix(tui): show session timestamps in listings
cyq1017 Jun 2, 2026
a41a382
docs(runtime): outline receipt export boundary
cyq1017 Jun 2, 2026
c92f3c3
feat(tui): expose current model in turn metadata
cyq1017 Jun 1, 2026
c2c36cc
feat: add NSIS installer and classroom admin checklist
May 25, 2026
e6de6f4
fix: address Gemini code review feedback
May 25, 2026
63b7c18
fix(release): ship NSIS installer artifact
Hmbown Jun 2, 2026
41edcd5
chore(release): bump local version to 0.8.50
Hmbown Jun 2, 2026
88f34fc
fix(tui): protect multiline drafts on arrow navigation
Hmbown Jun 2, 2026
eedeb52
fix(agent): pass through explicit AtlasCloud model ids
Hmbown Jun 2, 2026
4a09197
fix(tui): bound foreground shell reader drains
Hmbown Jun 2, 2026
b122b58
refs(#2264): Phase 2 — wire FrozenPrefix::verify() into turn_loop
encyc Jun 1, 2026
c9e4c8b
fix: clarify comment, avoid per-turn tool clone on happy path
encyc Jun 1, 2026
d58613a
test(client): add plan mode toggle byte-stability invariant test
Jun 1, 2026
139b542
test(ci): add Cache Guard CI test for prefix-cache stability
Jun 1, 2026
8532dcc
feat: add Xiaomi MiMo speech support
xyuai Jun 2, 2026
5f497e0
fix: harden Xiaomi MiMo speech flow
xyuai Jun 2, 2026
e763b44
docs(changelog): credit new harvests for v0.8.50 (#2514, #2519, #2503…
Hmbown Jun 2, 2026
e99ee5e
chore(release): sync tui crate CHANGELOG for version drift gate
Hmbown Jun 2, 2026
ddae758
fix: resolve clippy warnings in harvested PRs (needless-borrow, is_mu…
Hmbown Jun 2, 2026
6ab77ea
feat(i18n): localize all queue command messages across 7 locales
gordonlu Jun 2, 2026
19d5579
fix: avoid Instant overflow in turn_liveness tests on Windows
gordonlu Jun 2, 2026
478d45f
fmt: cargo fmt
gordonlu Jun 2, 2026
2501709
fix: restore two-line draft header layout
gordonlu Jun 2, 2026
cc60129
feat(i18n): add FanoutCounts MessageId, wire into FanoutCard stats line
gordonlu Jun 2, 2026
f48e398
Revert "fix: restore two-line draft header layout"
gordonlu Jun 2, 2026
97c615c
chore: add contribution gate workflows
nightt5879 Jun 2, 2026
dcf8350
fix: harden contribution gate bypasses
nightt5879 Jun 2, 2026
5059076
fix: read contribution allowlist from default branch
nightt5879 Jun 2, 2026
c8c20e0
fix: remove dead issue gate guard
nightt5879 Jun 2, 2026
dfe1884
fix: add contribution gate dry run mode
nightt5879 Jun 2, 2026
ea7fc47
fix: paginate pending allowlist PR lookup
nightt5879 Jun 2, 2026
b819423
docs(changelog): credit i18n and CI workflow harvests (#2568, #2566, …
Hmbown Jun 2, 2026
471a58f
chore(release): sync tui CHANGELOG after i18n/CI harvests
Hmbown Jun 2, 2026
9a3c545
fix(tui): move Paste to first position in right-click context menu
Hmbown Jun 2, 2026
f2df1d5
docs(changelog): note paste-first context menu fix
Hmbown Jun 2, 2026
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
11 changes: 11 additions & 0 deletions .github/APPROVED_CONTRIBUTORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Scoped contribution-gate allowlist.
#
# Maintainers and collaborators bypass the gate automatically. Use this file
# for external contributors who are allowed through the automated front door.
# Seed active contributors here before switching the gate workflows to enforce mode.
#
# Supported entries:
# pr:username
# issue:username
# all:username
all:hmbown
218 changes: 218 additions & 0 deletions .github/workflows/approve-contributor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: Approve gated contributor

on:
issue_comment:
types: [created]

permissions:
contents: write
issues: write
pull-requests: write

concurrency:
group: contribution-gate-approval
cancel-in-progress: false

jobs:
approve:
runs-on: ubuntu-latest
steps:
- name: Open allowlist update PR
uses: actions/github-script@v7
with:
script: |
const comment = context.payload.comment;
const issue = context.payload.issue;
const owner = context.repo.owner;
const repo = context.repo.repo;
const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const command = (comment.body || '').trim().toLowerCase();
const scopeByCommand = new Map([
['/lgtm', 'pr'],
['lgtm', 'pr'],
['/lgtmi', 'issue'],
['lgtmi', 'issue'],
]);
const scope = scopeByCommand.get(command);

if (!scope) return;
if (!privileged.has(comment.author_association)) return;
if (scope === 'pr' && !issue.pull_request) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: '`/lgtm` grants PR access and must be used on a pull request. Use `/lgtmi` to grant issue access.',
});
return;
}
if (scope === 'issue' && issue.pull_request) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: '`/lgtmi` grants issue access and must be used on an issue. Use `/lgtm` to grant PR access.',
});
return;
}

const path = '.github/APPROVED_CONTRIBUTORS';
const targetLogin = issue.user.login;
const normalizedLogin = targetLogin.toLowerCase();
const entry = `${scope}:${normalizedLogin}`;
const branchSlug = normalizedLogin.replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'contributor';

const defaultContent = [
'# Scoped contribution-gate allowlist.',
'#',
'# Maintainers and collaborators bypass the gate automatically. Use this file',
'# for external contributors who are allowed through the automated front door.',
'# Seed active contributors here before switching the gate workflows to enforce mode.',
'#',
'# Supported entries:',
'# pr:username',
'# issue:username',
'# all:username',
'',
].join('\n');

function parseAllowlist(content) {
return new Set(
content
.split(/\r?\n/)
.map(line => line.replace(/#.*/, '').trim().toLowerCase())
.filter(Boolean)
);
}

const { data: repoData } = await github.rest.repos.get({ owner, repo });
const defaultBranch = repoData.default_branch;
const { data: baseRef } = await github.rest.git.getRef({
owner,
repo,
ref: `heads/${defaultBranch}`,
});
const baseSha = baseRef.object.sha;
const { data: baseCommit } = await github.rest.git.getCommit({
owner,
repo,
commit_sha: baseSha,
});

let content = defaultContent;
try {
const { data } = await github.rest.repos.getContent({
owner,
repo,
path,
ref: defaultBranch,
});
if (!Array.isArray(data) && data.type === 'file') {
content = Buffer.from(data.content, data.encoding || 'base64').toString('utf8');
}
} catch (error) {
if (error.status !== 404) throw error;
}

const existing = parseAllowlist(content);
if (existing.has(entry) || existing.has(`all:${normalizedLogin}`)) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `@${targetLogin} is already approved for ${scope} contributions in \`${path}\`.`,
});
return;
}

const openPrs = [];
for (let page = 1; ; page++) {
const { data: pagePrs } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
per_page: 100,
page,
});
openPrs.push(...pagePrs);
if (pagePrs.length < 100) break;
}
const repoFullName = `${owner}/${repo}`.toLowerCase();
const pendingPr = openPrs.find(openPr => {
const sameRepo = (openPr.head?.repo?.full_name || '').toLowerCase() === repoFullName;
const body = openPr.body || '';
return sameRepo && body.includes(`Adds \`${entry}\` to \`${path}\`.`);
});

if (pendingPr) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `@${targetLogin} already has a pending allowlist update PR for ${scope} contributions: ${pendingPr.html_url}`,
});
return;
}

const nextContent = `${content.trimEnd()}\n${entry}\n`;
const { data: blob } = await github.rest.git.createBlob({
owner,
repo,
content: nextContent,
encoding: 'utf-8',
});
const { data: tree } = await github.rest.git.createTree({
owner,
repo,
base_tree: baseCommit.tree.sha,
tree: [
{
path,
mode: '100644',
type: 'blob',
sha: blob.sha,
},
],
});

const branchName = `contribution-gate/${scope}-${branchSlug}-${Date.now()}`;
await github.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branchName}`,
sha: baseSha,
});

const { data: commit } = await github.rest.git.createCommit({
owner,
repo,
message: `chore: approve @${targetLogin} for ${scope} contributions`,
tree: tree.sha,
parents: [baseSha],
});
await github.rest.git.updateRef({
owner,
repo,
ref: `heads/${branchName}`,
sha: commit.sha,
});

const { data: pr } = await github.rest.pulls.create({
owner,
repo,
title: `chore: approve @${targetLogin} for ${scope} contributions`,
head: branchName,
base: defaultBranch,
body: [
`Adds \`${entry}\` to \`${path}\`.`,
'',
`Requested by @${comment.user.login} in #${issue.number}.`,
].join('\n'),
});

await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: `Created allowlist update PR: ${pr.html_url}`,
});
1 change: 0 additions & 1 deletion .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ on:
paths:
- 'Cargo.toml'
- 'npm/codewhale/package.json'
- 'npm/deepseek-tui/package.json'
workflow_dispatch:

permissions:
Expand Down
99 changes: 99 additions & 0 deletions .github/workflows/issue-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Contribution gate - issues

on:
issues:
types: [opened, reopened]

permissions:
contents: read
issues: write

env:
# Keep new gates observable first. Switch to "enforce" only after maintainers
# have seeded active contributors and reviewed the dry-run signal.
CONTRIBUTION_GATE_MODE: dry-run

jobs:
gate:
runs-on: ubuntu-latest
steps:
- name: Gate unapproved external issues
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const owner = context.repo.owner;
const repo = context.repo.repo;
const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const gateMode = (process.env.CONTRIBUTION_GATE_MODE || 'dry-run').trim().toLowerCase();
const enforceGate = gateMode === 'enforce';

if (!['dry-run', 'enforce'].includes(gateMode)) {
core.warning(`Unknown CONTRIBUTION_GATE_MODE "${gateMode}"; defaulting to dry-run.`);
}

if (privileged.has(issue.author_association)) return;
if (issue.user.login === 'github-actions[bot]') return;

function parseAllowlist(content) {
return new Set(
content
.split(/\r?\n/)
.map(line => line.replace(/#.*/, '').trim().toLowerCase())
.filter(Boolean)
);
}

async function readAllowlist() {
try {
const { data } = await github.rest.repos.getContent({
owner,
repo,
path: '.github/APPROVED_CONTRIBUTORS',
ref: context.payload.repository.default_branch,
});
if (Array.isArray(data) || data.type !== 'file') return new Set();
return parseAllowlist(
Buffer.from(data.content, data.encoding || 'base64').toString('utf8')
);
} catch (error) {
if (error.status === 404) return new Set();
throw error;
}
}

const allowlist = await readAllowlist();
const login = issue.user.login.toLowerCase();
if (
allowlist.has(`all:${login}`) ||
allowlist.has(`issue:${login}`)
) {
return;
}

const gateMessage = enforceGate
? 'This repository currently uses a maintainer-managed contribution gate, so issues from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` are closed automatically.'
: 'This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this issue is staying open. When enforcement is enabled, issues from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` will be closed automatically.';

await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: [
`Thanks @${issue.user.login} for the report.`,
'',
gateMessage,
'',
'Please read `CONTRIBUTING.md` for the expected issue shape. A maintainer can grant issue access by commenting `/lgtmi` on an issue.',
].join('\n'),
});

if (!enforceGate) return;

await github.rest.issues.update({
owner,
repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned',
});
Loading
Loading