rm git shim (#14141)

* rm git shim

Drops the terminal git/gh PATH wrapper and its settings toggle. Renames the no-marker shell-ready launch config after what it does.

Co-authored-by: Orca <help@stably.ai>

* rm git shim: clear stale state from older installs

Deletes the orphaned wrapper dir and scrubs inherited env/PATH, so a daemon that outlives the upgrade cannot keep seeding it. Drops a now-unread spawn option.

Co-authored-by: Orca <help@stably.ai>

* rm git shim: cover the daemon and headless paths

Scrub after the PATH prepends (they re-read process.env on the sparse daemon env) and run the cleanup above the serve branch so remote hosts get it too. Retry a locked removal; match PATH case-insensitively.

Co-authored-by: Orca <help@stably.ai>

* rm git shim: keep the scrub final

Refuse to re-prepend a legacy entry during agent-teams PATH promotion, which runs after the scrub. Cover the removal guard.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-08-12 18:53:17 -07:00
committed by GitHub
co-authored by Orca
parent f1ee3088a5
commit c86418eaad
55 changed files with 366 additions and 2661 deletions
-1
View File
@@ -10,7 +10,6 @@ inline src/main/agent-hooks/server.test.ts
inline src/main/agent-hooks/server.ts
inline src/main/amp/hook-service.ts
inline src/main/antigravity/hook-service.ts
inline src/main/attribution/terminal-attribution.ts
inline src/main/automations/external-manager.ts
inline src/main/automations/hermes-cron-output.ts
inline src/main/browser/agent-browser-bridge.test.ts
@@ -2273,12 +2273,6 @@
"auto.components.settings.GitPane.976afc6b3e": {
"ko": "워크스페이스를 만들면 Orca가 원격 기준 브랜치를 새로고침하고, 일치하는 로컬 브랜치를 안전하게 fast-forward합니다. 예:"
},
"auto.components.settings.GitPane.b5f534717a": {
"ko": "공동 작성"
},
"auto.components.settings.GitPane.b9b5771bb1": {
"ko": "표기"
},
"auto.components.settings.GitPane.b9c011fbc2": {
"ko": "속도 제한"
},
@@ -2288,9 +2282,6 @@
"auto.components.settings.GitPane.db3a127eb1": {
"ko": "이렇게 하면 다음과 같은 명령이 유지됩니다"
},
"auto.components.settings.GitPane.e02ea23a32": {
"ko": "Orca 표기"
},
"auto.components.settings.GitPane.f35007e6e8": {
"ko": "git-username"
},
+1 -3
View File
@@ -44,7 +44,6 @@ export const KO_VALUE_OVERRIDES = {
'Issue #{{value0}}': '이슈 #{{value0}}',
'Linked issue #{{value0}}': '연결된 이슈 #{{value0}}',
'Issue # or GitHub URL': '이슈 # 또는 GitHub URL',
'Add Orca attribution to commits, PRs, and issues.': '커밋, PR 및 이슈에 Orca 속성을 추가합니다.',
'Paste an issue URL, or enter a number. Leave blank to remove the link.':
'이슈 URL을 붙여넣거나 숫자를 입력하세요. 링크를 제거하려면 비워두세요.',
'Connect Linear in Settings to search issues.': '이슈를 검색하려면 설정에서 Linear를 연결하세요.',
@@ -157,8 +156,7 @@ export const KO_VALUE_OVERRIDES = {
'저장되지 않은 Git AI Author 변경사항이 있습니다. 떠나면 폐기됩니다.',
'Discard unsaved Git AI Author changes?':
'저장되지 않은 Git AI Author 변경사항을 삭제하시겠습니까?',
'Branch naming, base refs, attribution, and Git AI Author.':
'브랜치 이름 지정, 기본 참조, 속성 및 Git AI Author.',
'Branch naming, base refs, and Git AI Author.': '브랜치 이름 지정, 기본 참조 및 Git AI Author.',
'Used by repositories that inherit global hosted-review defaults.':
'전역 호스팅 PR 기본값을 상속하는 저장소에서 사용됩니다.',
'Defaults used when the hosted-review composer opens.':
@@ -164,7 +164,6 @@ export const SEARCH_KEYWORD_OVERRIDES = {
gitlab: 'GitLab',
gitea: 'Gitea',
oauth: 'OAuth',
attribution: '归因',
skill: '技能',
skills: '技能',
phone: '手机',
@@ -25,10 +25,10 @@ describe('locale-translation-policy es PR glossary', () => {
it('rewrites PR inside longer sentences', () => {
expect(
repairEs(
'Add Orca attribution to commits, PRs, and issues.',
'Agregue la atribución de Orca a commits, relaciones públicas y problemas.'
'Split a large change into smaller PRs',
'Dividir un cambio grande en relaciones públicas más pequeños'
)
).toBe('Agregue la atribución de Orca a commits, PR y problemas.')
).toBe('Dividir un cambio grande en PR más pequeños')
expect(
repairEs(
'Open the PR details to view current reviewers.',
@@ -44,22 +44,14 @@ describe('locale-translation-policy zh round 5', () => {
locale: 'zh'
})
).toBe('显示 Orca Mobile 按钮')
expect(
repairTranslatedValue({
key: 'auto.components.settings.GitPane.e02ea23a32',
enValue: 'Orca Attribution',
localeValue: 'Orca归属',
locale: 'zh'
})
).toBe('Orca 归因')
expect(
repairTranslatedValue({
key: 'auto.hooks.useSettingsNavigationMetadata.ab4b21b58e',
enValue: 'Branch naming, base refs, attribution, and Git AI Author.',
localeValue: '分支命名、基本引用、归属和 Git AI 作者。',
enValue: 'Branch naming, base refs, and Git AI Author.',
localeValue: '分支命名、基本引用和 Git AI 作者。',
locale: 'zh'
})
).toBe('分支命名、基础引用、归因和 Git AI Author。')
).toBe('分支命名、基础引用和 Git AI Author。')
expect(
repairTranslatedValue({
key: 'auto.components.workspace.cleanup.WorkspaceCleanupDialog.1b18868569',
@@ -3,7 +3,6 @@ export const ZH_PHRASE_FIXES_ROUND5 = [
{ pattern: /Orca集成开发环境/g, replacement: 'Orca IDE', whenEnIncludes: 'Orca IDE' },
{ pattern: /Orca第一/g, replacement: 'Orca 优先', whenEnIncludes: 'Orca first' },
{ pattern: /Orca移动/g, replacement: 'Orca Mobile', whenEnIncludes: 'Orca Mobile' },
{ pattern: /Orca归属/g, replacement: 'Orca 归因', whenEnIncludes: 'Orca Attribution' },
{ pattern: /Orca标志/g, replacement: 'Orca 标志', whenEnIncludes: 'Orca logo' },
{ pattern: /喜欢Orca/g, replacement: '喜欢 Orca', whenEnIncludes: 'Enjoying Orca' },
{ pattern: /认识Orca/g, replacement: '了解 Orca', whenEnIncludes: 'Get to know Orca' },
+1 -3
View File
@@ -144,7 +144,6 @@ export const ZH_VALUE_OVERRIDES = {
'Expand Orca': '展开 Orca',
'Support Orca': '支持 Orca',
'Restarting Orca…': '正在重启 Orca…',
'Orca Attribution': 'Orca 归因',
'Show Orca Mobile Button': '显示 Orca Mobile 按钮',
'Codex Accounts': 'Codex 账户',
'Codex Account': 'Codex 账户',
@@ -186,8 +185,7 @@ export const ZH_VALUE_OVERRIDES = {
"Give agents direct access to Orca's browser so they can test pages, capture screenshots, and act on what they see.":
'让代理直接访问 Orca 的浏览器,以便测试页面、捕获屏幕截图并根据所见内容执行操作。',
'X finishes, send it the review task.”': 'X 完成后,把评审任务发给它。”',
'Branch naming, base refs, attribution, and Git AI Author.':
'分支命名、基础引用、归因和 Git AI Author。',
'Branch naming, base refs, and Git AI Author.': '分支命名、基础引用和 Git AI Author。',
'You have unsaved Git AI Author changes. Leaving will discard them.':
'您有未保存的 Git AI Author 更改。离开将丢弃它们。',
'Discard unsaved Git AI Author changes?': '放弃未保存的 Git AI Author 更改?',
@@ -1,169 +0,0 @@
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { applyTerminalAttributionEnv } from './terminal-attribution'
describe('applyTerminalAttributionEnv PATH key casing', () => {
let tmpRoot: string | null = null
afterEach(() => {
if (tmpRoot) {
rmSync(tmpRoot, { force: true, recursive: true })
tmpRoot = null
}
})
function makeTmpRoot(): string {
tmpRoot = mkdtempSync(join(tmpdir(), 'orca-attribution-path-casing-'))
return tmpRoot
}
// Why: a Windows child that inherits both spellings crashes the packaged orca.exe launcher
// with "Item has already been added. Key in dictionary: 'PATH'" (stablyai/orca#12046).
function pathEnvKeys(env: Record<string, string>): string[] {
return Object.keys(env).filter((key) => /^path$/i.test(key))
}
it('writes back the inherited Windows `Path` spelling instead of adding `PATH`', () => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const baseEnv: Record<string, string> = {
Path: 'C:\\Windows\\system32;C:\\Program Files\\Git\\cmd',
SystemRoot: 'C:\\Windows'
}
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
const win32Dir = join(userDataPath, 'orca-terminal-attribution', 'win32')
const pathEntries = baseEnv.Path.split(';')
expect(pathEnvKeys(baseEnv)).toEqual(['Path'])
expect(pathEntries[0]).toBe(win32Dir)
expect(pathEntries).toContain('C:\\Program Files\\Git\\cmd')
})
// Why: Win32 resolves a duplicated variable by taking the first match in the block, so the
// shim must land on the first-listed spelling and the shadowed one must keep its value —
// dropping it would promote it the moment anything downstream removes the live key.
it.each([
{ first: 'PATH', second: 'Path' },
{ first: 'Path', second: 'PATH' }
])(
'prepends the shim onto the first-listed `$first` and leaves `$second` untouched',
({ first, second }) => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const baseEnv: Record<string, string> = {
[first]: 'C:\\tools\\bin;C:\\Windows\\system32',
[second]: 'C:\\Windows\\system32'
}
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
const win32Dir = join(userDataPath, 'orca-terminal-attribution', 'win32')
expect(baseEnv[first].split(';')).toEqual([
win32Dir,
'C:\\tools\\bin',
'C:\\Windows\\system32'
])
expect(baseEnv[second]).toBe('C:\\Windows\\system32')
}
)
it('keeps a shadowed duplicate when the live spelling strips down to nothing', () => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const shimDir = join(userDataPath, 'orca-terminal-attribution', 'win32')
const baseEnv: Record<string, string> = { Path: shimDir, PATH: 'C:\\Windows\\system32' }
applyTerminalAttributionEnv(baseEnv, {
enabled: false,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
expect(pathEnvKeys(baseEnv)).toEqual(['PATH'])
expect(baseEnv.PATH).toBe('C:\\Windows\\system32')
})
it('strips shim entries from the inherited `Path` when attribution is disabled', () => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const shimDir = join(userDataPath, 'orca-terminal-attribution', 'win32')
const baseEnv: Record<string, string> = { Path: `${shimDir};C:\\Windows\\system32` }
applyTerminalAttributionEnv(baseEnv, {
enabled: false,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
expect(pathEnvKeys(baseEnv)).toEqual(['Path'])
expect(baseEnv.Path).toBe('C:\\Windows\\system32')
})
// Why: the daemon receives a sparse env patch with no path key and re-merges its own block
// underneath it, so guessing the spelling here hands the child both.
it.each([
{ hostKey: 'PATH', otherKey: 'Path' },
{ hostKey: 'Path', otherKey: 'PATH' }
])('adopts a host block spelt `$hostKey` on a path-less env', ({ hostKey, otherKey }) => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const inherited = { PATH: process.env.PATH, Path: process.env.Path }
delete process.env.PATH
delete process.env.Path
process.env[hostKey] = 'C:\\Windows\\system32'
try {
const baseEnv: Record<string, string> = { SystemRoot: 'C:\\Windows' }
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
expect(pathEnvKeys(baseEnv)).toEqual([hostKey])
expect(baseEnv[otherKey]).toBeUndefined()
} finally {
delete process.env[hostKey]
for (const [key, value] of Object.entries(inherited)) {
if (value !== undefined) {
process.env[key] = value
}
}
}
})
it('leaves a case-sensitive POSIX `Path` variable alone', () => {
const root = makeTmpRoot()
const baseEnv: Record<string, string> = {
PATH: '/usr/bin:/bin',
Path: '/decoy'
}
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'linux',
userDataPath: join(root, 'user-data')
})
expect(baseEnv.Path).toBe('/decoy')
expect(baseEnv.PATH.split(':')).toContain('/usr/bin')
})
})
@@ -1,100 +0,0 @@
import { spawnSync } from 'node:child_process'
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { applyTerminalAttributionEnv } from './terminal-attribution'
// Why (#12046 follow-up): `powershell.exe -File <script> ... -` fails argument
// binding with PSArgumentException before the script runs, so any command
// carrying a bare `-` must never be dispatched to the PowerShell wrapper.
describe('win32 attribution wrappers with a bare `-` argument', () => {
let tmpRoot: string | null = null
afterEach(() => {
if (tmpRoot) {
rmSync(tmpRoot, { force: true, recursive: true })
tmpRoot = null
}
})
function shimDir(): string {
tmpRoot = mkdtempSync(join(tmpdir(), 'orca-attribution-dash-'))
const userDataPath = join(tmpRoot, 'user-data')
const baseEnv: Record<string, string> = {
Path: 'C:\\Windows\\system32',
SystemRoot: 'C:\\Windows'
}
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
return join(userDataPath, 'orca-terminal-attribution', 'win32')
}
it('guards the git PowerShell dispatch behind a bare-dash check', () => {
const wrapper = readFileSync(join(shimDir(), 'git.cmd'), 'utf8')
const guardAt = wrapper.indexOf('call :orca_has_bare_dash %*')
const dispatchAt = wrapper.indexOf('powershell.exe')
expect(guardAt).toBeGreaterThan(-1)
expect(guardAt).toBeLessThan(dispatchAt)
expect(wrapper).toContain('if not errorlevel 1 goto run')
expect(wrapper).toContain(':orca_has_bare_dash')
expect(wrapper).toContain('if "%~1"=="-" exit /b 0')
})
it('guards the gh PowerShell dispatch behind a bare-dash check', () => {
const wrapper = readFileSync(join(shimDir(), 'gh.cmd'), 'utf8')
const guardAt = wrapper.indexOf('call :orca_has_bare_dash %*')
const dispatchAt = wrapper.indexOf('powershell.exe')
expect(guardAt).toBeGreaterThan(-1)
expect(guardAt).toBeLessThan(dispatchAt)
expect(wrapper).toContain(':orca_has_bare_dash')
})
const itWindows = process.platform === 'win32' ? it : it.skip
// Why: the guard exists for a runtime failure, so the real regression is a
// real `git commit -F -` through the generated wrapper on a real Windows host.
itWindows('commits a message piped through `git commit -F -`', () => {
const dir = shimDir()
const gitCmd = join(dir, 'git.cmd')
const repo = mkdtempSync(join(tmpdir(), 'orca-attribution-dash-repo-'))
try {
const git = (...args: string[]): void => {
spawnSync('git', args, { cwd: repo, encoding: 'utf8' })
}
git('init', '-q', '-b', 'main')
git('config', 'user.email', 'test@example.com')
git('config', 'user.name', 'Test')
writeFileSync(join(repo, 'a.txt'), 'hello\n')
git('add', '.')
const env = {
...process.env,
ORCA_ENABLE_GIT_ATTRIBUTION: '1',
ORCA_REAL_GIT: spawnSync('where', ['git.exe'], { encoding: 'utf8' }).stdout.split(
/\r?\n/
)[0]
}
const result = spawnSync('cmd.exe', ['/d', '/c', gitCmd, 'commit', '-F', '-'], {
cwd: repo,
env,
input: 'piped subject\n',
encoding: 'utf8'
})
expect(result.stderr).not.toContain('PSArgumentException')
expect(result.status).toBe(0)
expect(
spawnSync('git', ['log', '-1', '--format=%B'], { cwd: repo, encoding: 'utf8' }).stdout
).toContain('piped subject')
} finally {
rmSync(repo, { force: true, recursive: true })
}
})
})
@@ -1,824 +0,0 @@
import { execFileSync } from 'node:child_process'
import {
chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { applyTerminalAttributionEnv, resolveAttributionShellFamily } from './terminal-attribution'
describe('applyTerminalAttributionEnv', () => {
let tmpRoot: string | null = null
// Why: these subprocess fixtures create extensionless Bash fake commands;
// native Windows command resolution is covered by wrapper/PATH assertions below.
const posixSubprocessIt = process.platform === 'win32' ? it.skip : it
afterEach(() => {
if (tmpRoot) {
rmSync(tmpRoot, { force: true, recursive: true })
tmpRoot = null
}
})
function makeTmpRoot(): string {
tmpRoot = mkdtempSync(join(tmpdir(), 'orca-attribution-'))
return tmpRoot
}
function stripInheritedAttributionPath(pathValue: string): string {
const pathDelimiter = process.platform === 'win32' ? ';' : ':'
return pathValue
.split(pathDelimiter)
.filter((entry) => !entry.includes('orca-terminal-attribution'))
.join(pathDelimiter)
}
function cleanAttributionEnv(env?: Record<string, string>): Record<string, string> {
const base = { ...process.env }
delete base.ORCA_ENABLE_GIT_ATTRIBUTION
delete base.ORCA_GIT_COMMIT_TRAILER
delete base.ORCA_GH_PR_FOOTER
delete base.ORCA_GH_ISSUE_FOOTER
delete base.ORCA_ATTRIBUTION_SHIM_DIR
delete base.ORCA_REAL_GIT
delete base.ORCA_REAL_GH
base.PATH = stripInheritedAttributionPath(base.PATH ?? '')
const next = { ...base, ...env }
return next as Record<string, string>
}
function runGit(repo: string, args: string[], env?: Record<string, string>): string {
return execFileSync('git', args, {
cwd: repo,
encoding: 'utf8',
env: cleanAttributionEnv(env)
})
}
it('classifies Windows native and POSIX shell families for attribution shims', () => {
expect(resolveAttributionShellFamily({ platform: 'win32', shellPath: 'powershell.exe' })).toBe(
'native-windows'
)
expect(resolveAttributionShellFamily({ platform: 'win32', shellPath: 'cmd.exe' })).toBe(
'native-windows'
)
expect(
resolveAttributionShellFamily({
platform: 'win32',
shellPath: 'C:\\Program Files\\Git\\bin\\bash.exe'
})
).toBe('posix')
expect(resolveAttributionShellFamily({ platform: 'win32', shellPath: 'wsl.exe' })).toBe('posix')
expect(resolveAttributionShellFamily({ platform: 'win32', isWsl: true })).toBe('posix')
expect(resolveAttributionShellFamily({ platform: 'darwin', shellPath: '/bin/zsh' })).toBe(
undefined
)
})
posixSubprocessIt('does not amend HEAD when git commit --dry-run exits successfully', () => {
const root = makeTmpRoot()
const repo = join(root, 'repo')
mkdirSync(repo)
runGit(repo, ['init'])
runGit(repo, ['config', 'user.name', 'Orca Test'])
runGit(repo, ['config', 'user.email', 'orca-test@example.com'])
writeFileSync(join(repo, 'README.md'), 'initial\n')
runGit(repo, ['add', 'README.md'])
runGit(repo, ['commit', '-m', 'initial'])
const attributionEnv = { PATH: stripInheritedAttributionPath(process.env.PATH ?? '') }
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
const beforeHead = runGit(repo, ['rev-parse', 'HEAD']).trim()
writeFileSync(join(repo, 'second.txt'), 'second\n')
runGit(repo, ['add', 'second.txt'])
// Why: dry-run reports what would be committed but must not rewrite the
// existing HEAD just because the real git command returns success.
runGit(repo, ['commit', '--dry-run', '-m', 'second'], attributionEnv)
expect(runGit(repo, ['rev-parse', 'HEAD']).trim()).toBe(beforeHead)
expect(runGit(repo, ['log', '-1', '--format=%B'])).not.toContain('Co-authored-by: Orca')
runGit(repo, ['commit', '-m', 'second'], attributionEnv)
expect(runGit(repo, ['rev-parse', 'HEAD']).trim()).not.toBe(beforeHead)
expect(runGit(repo, ['log', '-1', '--format=%B'])).toContain(
'Co-authored-by: Orca <help@stably.ai>'
)
})
posixSubprocessIt('still adds the trailer when git commit uses --no-verify shorthand', () => {
const root = makeTmpRoot()
const repo = join(root, 'repo')
mkdirSync(repo)
runGit(repo, ['init'])
runGit(repo, ['config', 'user.name', 'Orca Test'])
runGit(repo, ['config', 'user.email', 'orca-test@example.com'])
writeFileSync(join(repo, 'README.md'), 'initial\n')
runGit(repo, ['add', 'README.md'])
const attributionEnv = { PATH: stripInheritedAttributionPath(process.env.PATH ?? '') }
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
runGit(repo, ['commit', '-n', '-m', 'initial'], attributionEnv)
expect(runGit(repo, ['log', '-1', '--format=%B'])).toContain(
'Co-authored-by: Orca <help@stably.ai>'
)
})
posixSubprocessIt('adds the trailer when git commit uses combined -am shorthand', () => {
const root = makeTmpRoot()
const repo = join(root, 'repo')
mkdirSync(repo)
runGit(repo, ['init'])
runGit(repo, ['config', 'user.name', 'Orca Test'])
runGit(repo, ['config', 'user.email', 'orca-test@example.com'])
writeFileSync(join(repo, 'README.md'), 'initial\n')
runGit(repo, ['add', 'README.md'])
runGit(repo, ['commit', '-m', 'initial'])
writeFileSync(join(repo, 'README.md'), 'changed\n')
const attributionEnv = { PATH: stripInheritedAttributionPath(process.env.PATH ?? '') }
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
runGit(repo, ['commit', '-am', 'combined message'], attributionEnv)
expect(runGit(repo, ['log', '-1', '--format=%B'])).toContain(
'Co-authored-by: Orca <help@stably.ai>'
)
})
posixSubprocessIt('adds the trailer when git commit follows global git config args', () => {
const root = makeTmpRoot()
const repo = join(root, 'repo')
mkdirSync(repo)
runGit(repo, ['init'])
runGit(repo, ['config', 'user.name', 'Orca Test'])
runGit(repo, ['config', 'user.email', 'orca-test@example.com'])
writeFileSync(join(repo, 'README.md'), 'initial\n')
runGit(repo, ['add', 'README.md'])
const attributionEnv = { PATH: stripInheritedAttributionPath(process.env.PATH ?? '') }
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
runGit(repo, ['-c', 'core.quotePath=false', 'commit', '-m', 'initial'], attributionEnv)
expect(runGit(repo, ['log', '-1', '--format=%B'])).toContain(
'Co-authored-by: Orca <help@stably.ai>'
)
})
posixSubprocessIt('adds the trailer to commit message files before git runs', () => {
const root = makeTmpRoot()
const repo = join(root, 'repo')
const messagePath = join(root, 'message.txt')
mkdirSync(repo)
runGit(repo, ['init'])
runGit(repo, ['config', 'user.name', 'Orca Test'])
runGit(repo, ['config', 'user.email', 'orca-test@example.com'])
writeFileSync(join(repo, 'README.md'), 'initial\n')
writeFileSync(messagePath, 'initial from file\n')
runGit(repo, ['add', 'README.md'])
const attributionEnv = { PATH: stripInheritedAttributionPath(process.env.PATH ?? '') }
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
runGit(repo, ['commit', '-F', messagePath], attributionEnv)
expect(runGit(repo, ['log', '-1', '--format=%B'])).toContain(
'Co-authored-by: Orca <help@stably.ai>'
)
expect(readFileSync(messagePath, 'utf8')).toBe('initial from file\n')
})
posixSubprocessIt(
'passes missing commit message files through without adding fallback message args',
() => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const argsPath = join(root, 'commit-args')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'git'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1" == "commit" ]]; then
printf '%s\\n' "$@" >"${argsPath}"
exit 9
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'git'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
expect(() =>
execFileSync('git', ['commit', '-F', join(root, 'missing-message.txt')], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
).toThrow()
expect(readFileSync(argsPath, 'utf8')).not.toContain('Co-authored-by: Orca')
}
)
posixSubprocessIt(
'passes reuse and fixup commit message modes through without attribution',
() => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const argsPath = join(root, 'commit-args')
const messagePath = join(root, 'message.txt')
mkdirSync(binDir)
writeFileSync(messagePath, 'from file\n')
writeFileSync(
join(binDir, 'git'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1" == "commit" ]]; then
printf '%s\\n' "$@" >>"${argsPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'git'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
execFileSync('git', ['commit', '-C', 'HEAD'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
execFileSync('git', ['commit', '--fixup', 'HEAD'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
execFileSync('git', ['commit', '-F', messagePath, '--fixup', 'HEAD'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(readFileSync(argsPath, 'utf8')).not.toContain('Co-authored-by: Orca')
}
)
posixSubprocessIt('adds the trailer before commit-msg hooks validate the commit', () => {
const root = makeTmpRoot()
const repo = join(root, 'repo')
mkdirSync(repo)
runGit(repo, ['init'])
runGit(repo, ['config', 'user.name', 'Orca Test'])
runGit(repo, ['config', 'user.email', 'orca-test@example.com'])
const hookPath = join(repo, '.git', 'hooks', 'commit-msg')
const hookCounterPath = join(repo, 'hook-count')
writeFileSync(
hookPath,
`#!/usr/bin/env bash
set -euo pipefail
count=0
if [[ -f "${hookCounterPath}" ]]; then
count="$(cat "${hookCounterPath}")"
fi
printf '%s\\n' "$((count + 1))" >"${hookCounterPath}"
grep -Fq 'Co-authored-by: Orca <help@stably.ai>' "$1"
`,
'utf8'
)
chmodSync(hookPath, 0o755)
writeFileSync(join(repo, 'README.md'), 'initial\n')
runGit(repo, ['add', 'README.md'])
const attributionEnv = { PATH: stripInheritedAttributionPath(process.env.PATH ?? '') }
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
runGit(repo, ['commit', '-m', 'initial'], attributionEnv)
expect(readFileSync(hookCounterPath, 'utf8').trim()).toBe('1')
expect(runGit(repo, ['log', '-1', '--format=%B'])).toContain(
'Co-authored-by: Orca <help@stably.ai>'
)
})
posixSubprocessIt('adds git attribution to the original commit command without amending', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const commitPath = join(root, 'commit-called')
const amendPath = join(root, 'amend-called')
const argsPath = join(root, 'commit-args')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'git'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2 $3" == "config --bool commit.gpgsign" ]]; then
printf '%s\\n' 'true'
exit 0
fi
if [[ "$1" == "commit" ]]; then
if [[ "\${2:-}" == "--amend" ]]; then
touch "${amendPath}"
else
printf '%s\\n' "$@" >"${argsPath}"
touch "${commitPath}"
fi
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'git'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
execFileSync('git', ['commit', '-m', 'signed commit'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(existsSync(commitPath)).toBe(true)
expect(existsSync(amendPath)).toBe(false)
expect(readFileSync(argsPath, 'utf8')).toContain('Co-authored-by: Orca <help@stably.ai>')
})
posixSubprocessIt('passes editor-based commits through without attribution', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const argsPath = join(root, 'commit-args')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'git'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1" == "commit" ]]; then
printf '%s\\n' "$@" >"${argsPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'git'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
execFileSync('git', ['commit'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(readFileSync(argsPath, 'utf8')).toBe('commit\n')
})
posixSubprocessIt('preserves interactive gh pr create without guessing which PR to edit', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const markerPath = join(root, 'gh-edit-called')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'gh'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2" == "pr create" ]]; then
printf '%s\\n' 'interactive create complete'
exit 0
fi
if [[ "$1 $2 $3 $4" == "pr view --json url" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/pull/123'
exit 0
fi
if [[ "$1 $2" == "api repos/stablyai/orca/pulls/123" && "\${3:-}" == "--jq" ]]; then
printf '%s\\n' 'Existing body'
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/pulls/123" ]]; then
touch "${markerPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'gh'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
const output = execFileSync('gh', ['pr', 'create'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(output).toBe('interactive create complete\n')
expect(existsSync(markerPath)).toBe(false)
})
posixSubprocessIt('adds gh attribution for noninteractive create output URLs', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const prMarkerPath = join(root, 'pr-edit-called')
const issueMarkerPath = join(root, 'issue-edit-called')
const patchArgsPath = join(root, 'patch-args')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'gh'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2" == "pr create" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/pull/123'
exit 0
fi
if [[ "$1 $2" == "issue create" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/issues/456'
exit 0
fi
if [[ "$1 $2" == "api repos/stablyai/orca/pulls/123" && "\${3:-}" == "--jq" ]]; then
printf '%s\\n' 'PR body'
exit 0
fi
if [[ "$1 $2" == "api repos/stablyai/orca/issues/456" && "\${3:-}" == "--jq" ]]; then
printf '%s\\n' 'Issue body'
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/pulls/123" ]]; then
printf '%s\\n' "$@" >"${patchArgsPath}"
touch "${prMarkerPath}"
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/issues/456" ]]; then
touch "${issueMarkerPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'gh'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
expect(
execFileSync('gh', ['pr', 'create', '--fill'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
).toBe('https://github.com/stablyai/orca/pull/123\n')
expect(
execFileSync('gh', ['issue', 'create', '--title', 'Issue', '--body', 'Body'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
).toBe('https://github.com/stablyai/orca/issues/456\n')
expect(existsSync(prMarkerPath)).toBe(true)
expect(existsSync(issueMarkerPath)).toBe(true)
expect(readFileSync(patchArgsPath, 'utf8')).toContain('body=@')
expect(readFileSync(patchArgsPath, 'utf8')).not.toContain('PR body')
})
posixSubprocessIt('passes gh create help through without editing existing PRs or issues', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const markerPath = join(root, 'gh-edit-called')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'gh'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2 $3" == "pr create --help" ]]; then
printf '%s\\n' 'pr help'
exit 0
fi
if [[ "$1 $2 $3" == "issue create --help" ]]; then
printf '%s\\n' 'issue help'
exit 0
fi
if [[ "$1 $2 $3 $4" == "pr view --json url" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/pull/123'
exit 0
fi
if [[ "$1 $2" == "issue list" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/issues/456'
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/pulls/123" ]]; then
touch "${markerPath}"
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/issues/456" ]]; then
touch "${markerPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'gh'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
const output = execFileSync('gh', ['pr', 'create', '--help'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(output).toBe('pr help\n')
const issueOutput = execFileSync('gh', ['issue', 'create', '--help'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(issueOutput).toBe('issue help\n')
expect(existsSync(markerPath)).toBe(false)
})
posixSubprocessIt(
'preserves interactive gh issue create without guessing which issue to edit',
() => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const markerPath = join(root, 'gh-edit-called')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'gh'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2" == "issue create" ]]; then
printf '%s\\n' 'interactive issue create complete'
exit 0
fi
if [[ "$1 $2" == "issue list" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/issues/456'
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/issues/456" ]]; then
touch "${markerPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'gh'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
const output = execFileSync('gh', ['issue', 'create'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(output).toBe('interactive issue create complete\n')
expect(existsSync(markerPath)).toBe(false)
}
)
posixSubprocessIt('skips gh attribution edits when viewing the created item fails', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
const markerPath = join(root, 'gh-edit-called')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'gh'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2" == "pr create" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/pull/123'
exit 0
fi
if [[ "$1 $2" == "api repos/stablyai/orca/pulls/123" && "\${3:-}" == "--jq" ]]; then
exit 7
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/pulls/123" ]]; then
touch "${markerPath}"
exit 0
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'gh'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
const output = execFileSync('gh', ['pr', 'create', '--fill'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(output).toBe('https://github.com/stablyai/orca/pull/123\n')
expect(existsSync(markerPath)).toBe(false)
})
posixSubprocessIt('keeps gh create successful when the attribution edit fails', () => {
const root = makeTmpRoot()
const binDir = join(root, 'bin')
mkdirSync(binDir)
writeFileSync(
join(binDir, 'gh'),
`#!/usr/bin/env bash
set -euo pipefail
if [[ "$1 $2" == "pr create" ]]; then
printf '%s\\n' 'https://github.com/stablyai/orca/pull/123'
exit 0
fi
if [[ "$1 $2" == "api repos/stablyai/orca/pulls/123" && "\${3:-}" == "--jq" ]]; then
printf '%s\\n' 'Existing body'
exit 0
fi
if [[ "$1 $2 $3 $4" == "api -X PATCH repos/stablyai/orca/pulls/123" ]]; then
exit 9
fi
exit 1
`,
'utf8'
)
chmodSync(join(binDir, 'gh'), 0o755)
const attributionEnv = {
PATH: `${binDir}:${stripInheritedAttributionPath(process.env.PATH ?? '')}`
}
applyTerminalAttributionEnv(attributionEnv, {
enabled: true,
userDataPath: join(root, 'user-data')
})
const output = execFileSync('gh', ['pr', 'create', '--fill'], {
encoding: 'utf8',
env: cleanAttributionEnv(attributionEnv)
})
expect(output).toBe('https://github.com/stablyai/orca/pull/123\n')
})
it('fails open when shim files cannot be written', () => {
const root = makeTmpRoot()
const blockedUserDataPath = join(root, 'not-a-directory')
writeFileSync(blockedUserDataPath, 'blocked\n')
const baseEnv: Record<string, string> = { PATH: '/usr/bin' }
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
userDataPath: blockedUserDataPath
})
expect(baseEnv.ORCA_ENABLE_GIT_ATTRIBUTION).toBeUndefined()
expect(baseEnv.PATH).toBe('/usr/bin')
})
it('does not duplicate shim directories when applied to an already-injected env', () => {
const root = makeTmpRoot()
const baseEnv: Record<string, string> = {
PATH: stripInheritedAttributionPath(process.env.PATH ?? '')
}
const options = { enabled: true, userDataPath: join(root, 'user-data') }
const pathDelimiter = process.platform === 'win32' ? ';' : ':'
applyTerminalAttributionEnv(baseEnv, options)
applyTerminalAttributionEnv(baseEnv, options)
const shimEntries = baseEnv.PATH.split(pathDelimiter).filter((entry) =>
entry.includes('orca-terminal-attribution')
)
expect(new Set(shimEntries).size).toBe(shimEntries.length)
})
it('puts only Windows shims on PATH for native Windows shells', () => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const baseEnv: Record<string, string> = { PATH: 'C:\\Git\\cmd;C:\\Windows\\System32' }
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'win32',
shellFamily: 'native-windows',
userDataPath
})
const posixDir = join(userDataPath, 'orca-terminal-attribution', 'posix')
const win32Dir = join(userDataPath, 'orca-terminal-attribution', 'win32')
const pathEntries = baseEnv.PATH.split(';')
expect(pathEntries[0]).toBe(win32Dir)
expect(pathEntries).not.toContain(posixDir)
expect(baseEnv.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(existsSync(join(win32Dir, 'git.cmd'))).toBe(true)
})
it('keeps POSIX shims first for Windows Git Bash and WSL shells', () => {
const root = makeTmpRoot()
const userDataPath = join(root, 'user-data')
const baseEnv: Record<string, string> = { PATH: 'C:\\Program Files\\Git\\cmd;C:\\Windows' }
applyTerminalAttributionEnv(baseEnv, {
enabled: true,
platform: 'win32',
shellFamily: 'posix',
userDataPath
})
const posixDir = join(userDataPath, 'orca-terminal-attribution', 'posix')
const win32Dir = join(userDataPath, 'orca-terminal-attribution', 'win32')
const pathEntries = baseEnv.PATH.split(';')
expect(pathEntries[0]).toBe(posixDir)
expect(pathEntries).not.toContain(win32Dir)
expect(baseEnv.ORCA_ATTRIBUTION_SHIM_DIR).toBe(posixDir)
expect(existsSync(join(posixDir, 'git'))).toBe(true)
})
it('writes PowerShell wrappers without raw-template backslash escapes', () => {
const root = makeTmpRoot()
applyTerminalAttributionEnv(
{ PATH: stripInheritedAttributionPath(process.env.PATH ?? '') },
{ enabled: true, userDataPath: join(root, 'user-data') }
)
const shimDir = join(root, 'user-data', 'orca-terminal-attribution', 'win32')
const gitWrapper = readFileSync(join(shimDir, 'git-wrapper.ps1'), 'utf8')
const ghWrapper = readFileSync(join(shimDir, 'gh-wrapper.ps1'), 'utf8')
expect(gitWrapper).toContain('Test-ExplicitCommitMessage')
expect(gitWrapper).toContain('"`r`n`r`n"')
expect(ghWrapper).toContain('$body.TrimEnd("`r", "`n")')
expect(ghWrapper).toContain('"`r`n`r`n"')
expect(gitWrapper).not.toContain('"\\`r"')
expect(ghWrapper).not.toContain('"\\`r"')
})
})
File diff suppressed because it is too large Load Diff
@@ -167,7 +167,6 @@ function createSettings(overrides: TestSettingsOverrides = {}): GlobalSettings {
compactWorktreeCards: false,
terminalWindowsShell: 'powershell.exe',
terminalWindowsPowerShellImplementation: 'powershell.exe',
enableGitHubAttribution: true,
...overrides,
diffWordWrap: overrides.diffWordWrap ?? false,
localWindowsRuntimeDefault: overrides.localWindowsRuntimeDefault ?? { kind: 'windows-host' },
-1
View File
@@ -159,7 +159,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
compactWorktreeCards: false,
terminalWindowsShell: 'powershell.exe',
terminalWindowsPowerShellImplementation: 'powershell.exe',
enableGitHubAttribution: true,
...overrides,
diffWordWrap: overrides.diffWordWrap ?? false,
localWindowsRuntimeDefault: overrides.localWindowsRuntimeDefault ?? { kind: 'windows-host' },
+3 -5
View File
@@ -80,7 +80,6 @@ import { PREVIOUS_DAEMON_PROTOCOL_VERSIONS, PROTOCOL_VERSION } from './types'
import { TERMINAL_GIT_CREDENTIAL_GUARD_POLICY_ENV } from '../../shared/terminal-git-credential-guard'
const ORCA_SHELL_WRAPPER_ENV = [
'ORCA_ATTRIBUTION_SHIM_DIR',
'ORCA_OPENCODE_CONFIG_DIR',
'ORCA_MIMOCODE_HOME',
'ORCA_PI_CODING_AGENT_DIR',
@@ -1760,7 +1759,7 @@ describe('createPtySubprocess', () => {
expect(spawnEnv.MY_VAR).toBe('test-value')
})
it('uses shell wrapper when attribution shims must survive shell startup', () => {
it('uses shell wrapper when managed env must survive shell startup', () => {
const proc = mockPtyProcess()
spawnMock.mockReturnValue(proc)
const platform = Object.getOwnPropertyDescriptor(process, 'platform')
@@ -1773,7 +1772,7 @@ describe('createPtySubprocess', () => {
rows: 24,
env: {
SHELL: '/bin/zsh',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/orca-terminal-attribution/posix'
ORCA_OPENCODE_CONFIG_DIR: '/tmp/orca-opencode-config'
}
})
} finally {
@@ -2165,7 +2164,7 @@ describe('createPtySubprocess', () => {
PATH: '/tmp/orca-agent-teams-bin:/usr/bin',
ORCA_AGENT_TEAMS_TEAM_ID: 'team-test'
},
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['TERM_PROGRAM']
})
const lastCall = spawnMock.mock.calls.at(-1)!
@@ -2173,7 +2172,6 @@ describe('createPtySubprocess', () => {
expect(lastCall[2].env.TERM).toBe('screen-256color')
expect(lastCall[2].env.PATH.split(':')[0]).toBe('/tmp/orca-agent-teams-bin')
expect(lastCall[2].env.TERM_PROGRAM).toBeUndefined()
expect(lastCall[2].env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
})
it('collapses its own env merge onto the requested Windows `Path` spelling', () => {
+3 -4
View File
@@ -6,7 +6,7 @@ import { delimiter, win32 as pathWin32 } from 'node:path'
import type { SubprocessHandle } from './session'
import { DaemonProtocolError } from './types'
import {
getAttributionShellLaunchConfig,
getMarkerlessShellLaunchConfig,
getShellReadyLaunchConfig,
resolvePtyShellPath
} from './shell-ready'
@@ -800,18 +800,17 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl
// Why: payload-bearing Codex startup text can be dropped by rc-file noise; plain Codex stays markerless for the startup-speed path.
shellLaunch = shouldWaitForShellReady
? getShellReadyLaunchConfig(shellPath)
: getAttributionShellLaunchConfig(shellPath)
: getMarkerlessShellLaunchConfig(shellPath)
} else if (opts.command) {
shellLaunch = getShellReadyLaunchConfig(shellPath)
} else {
shellLaunch =
env.ORCA_ATTRIBUTION_SHIM_DIR ||
env.ORCA_OPENCODE_CONFIG_DIR ||
env.ORCA_MIMOCODE_HOME ||
env.ORCA_OMP_STATUS_EXTENSION ||
env.ORCA_CODEX_HOME ||
env.ORCA_AGENT_TEAMS_SHIM_DIR
? getAttributionShellLaunchConfig(shellPath)
? getMarkerlessShellLaunchConfig(shellPath)
: null
}
if (shellLaunch) {
+3 -3
View File
@@ -258,10 +258,10 @@ describePosix('daemon shell-ready launch config', () => {
expect(init).toContain('functions -e __orca_shell_ready_marker')
})
it('keeps attribution-only fish spawns unwrapped', async () => {
const { getAttributionShellLaunchConfig } = await importFreshShellReady()
it('keeps markerless fish spawns unwrapped', async () => {
const { getMarkerlessShellLaunchConfig } = await importFreshShellReady()
const config = getAttributionShellLaunchConfig('/opt/homebrew/bin/fish')
const config = getMarkerlessShellLaunchConfig('/opt/homebrew/bin/fish')
expect(config).toEqual({ args: null, env: {}, supportsReadyMarker: false })
})
+2 -26
View File
@@ -103,14 +103,6 @@ fi
# treat each embedded newline as Enter and mangle the prompt into PS2
# continuation. Modern readline defaults this on; force it for the rest.
[[ $- == *i* ]] && bind 'set enable-bracketed-paste on' 2>/dev/null
__orca_restore_attribution_path() {
[[ -n "\${ORCA_ATTRIBUTION_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_ATTRIBUTION_SHIM_DIR}"|"\${ORCA_ATTRIBUTION_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_ATTRIBUTION_SHIM_DIR}:$PATH"
}
__orca_restore_attribution_path
__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
@@ -224,14 +216,6 @@ ${getZshStartupFileSourceBlock({
interactiveOnly: true,
skipWhenHomeIsCurrentZdotdir: true
})}
__orca_restore_attribution_path() {
[[ -n "\${ORCA_ATTRIBUTION_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_ATTRIBUTION_SHIM_DIR}"|"\${ORCA_ATTRIBUTION_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_ATTRIBUTION_SHIM_DIR}:$PATH"
}
[[ ! -o login ]] && __orca_restore_attribution_path
__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
@@ -288,14 +272,6 @@ ${getZshStartupFileSourceBlock({ fileName: '.zprofile' })}
const zshRc = getDaemonZshShellReadyRcfileContent()
const zshLogin = `# Orca daemon zsh shell-ready wrapper
${getZshStartupFileSourceBlock({ fileName: '.zlogin', interactiveOnly: true })}
__orca_restore_attribution_path() {
[[ -n "\${ORCA_ATTRIBUTION_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_ATTRIBUTION_SHIM_DIR}"|"\${ORCA_ATTRIBUTION_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_ATTRIBUTION_SHIM_DIR}:$PATH"
}
__orca_restore_attribution_path
__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
@@ -419,7 +395,7 @@ function getWrappedShellLaunchConfig(
}
}
// Why: mirrors local-pty-shell-ready.ts; attribution-only fish stays unwrapped.
// Why: mirrors local-pty-shell-ready.ts; markerless fish stays unwrapped.
if (shellName === 'fish' && options.emitReadyMarker) {
return {
args: ['-l', '-C', getFishShellReadyInitCommand(SHELL_READY_MARKER)],
@@ -439,6 +415,6 @@ export function getShellReadyLaunchConfig(shellPath: string): ShellLaunchConfig
return getWrappedShellLaunchConfig(shellPath, { emitReadyMarker: true })
}
export function getAttributionShellLaunchConfig(shellPath: string): ShellLaunchConfig {
export function getMarkerlessShellLaunchConfig(shellPath: string): ShellLaunchConfig {
return getWrappedShellLaunchConfig(shellPath, { emitReadyMarker: false })
}
+3
View File
@@ -185,6 +185,7 @@ import {
logStartupMilestone
} from './startup/startup-diagnostics'
import { ensureWindowsUserDataAclGrant } from './startup/windows-user-data-acl'
import { removeLegacyTerminalShimDir } from './pty/legacy-terminal-shim-dir'
import { shouldQuitWhenAllWindowsClosed } from './startup/window-all-closed-quit-policy'
import {
createServeDesktopActivationGate,
@@ -2170,6 +2171,8 @@ void app.whenReady().then(async () => {
const activeOrcaProfile = ensureActiveOrcaProfile()
store = new Store({ dataFile: activeOrcaProfile.dataFile })
// Why: must precede PTY handler registration and run in headless serve too, which returns before openMainWindow.
removeLegacyTerminalShimDir(app.getPath('userData'))
const windowsShellPathHydration = createWindowsShellPathHydration()
configureWindowsHostGitEnvironmentReadiness(
process.platform === 'win32' ? windowsShellPathHydration.whenReady : null
+75 -128
View File
@@ -26,14 +26,6 @@ const expectedOmpStatusExtension = posix.join(
'omp-managed-status-extension',
'orca-agent-status.ts'
)
function expectedAttributionShimDir(): string {
return join(
'/tmp/orca-user-data',
'orca-terminal-attribution',
process.platform === 'win32' ? 'win32' : 'posix'
)
}
const {
handleMock,
onMock,
@@ -1873,7 +1865,6 @@ describe('registerPtyHandlers', () => {
launchContext?: { workspacePath?: string; launchAgent?: TuiAgent }
) => string | null,
getSettings?: () => {
enableGitHubAttribution?: boolean
agentStatusHooksEnabled?: boolean
httpProxyUrl?: string
httpProxyBypassRules?: string
@@ -1971,7 +1962,6 @@ describe('registerPtyHandlers', () => {
isPackaged: true,
userDataPath: '/tmp/orca-user-data',
selectedCodexHomePath: null,
githubAttributionEnabled: false,
agentStatusHooksEnabled: false,
isWsl: context?.isWsl,
wslDistro: context?.wslDistro
@@ -3135,59 +3125,6 @@ describe('registerPtyHandlers', () => {
expect(env.ORCA_CLAUDE_AGENT_STATUS_SETTINGS).toBeUndefined()
})
it('prepends local git/gh attribution shims when attribution is enabled', async () => {
const env = await spawnAndGetEnv(undefined, undefined, undefined, () => ({
enableGitHubAttribution: true
}))
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBe('1')
expect(env.ORCA_GIT_COMMIT_TRAILER).toBe('Co-authored-by: Orca <help@stably.ai>')
expect(env.ORCA_GH_PR_FOOTER).toBe('Made with [Orca](https://github.com/stablyai/orca) 🐋')
expect(env.ORCA_GH_ISSUE_FOOTER).toBe('Made with [Orca](https://github.com/stablyai/orca) 🐋')
expect(env.PATH).toContain(expectedAttributionShimDir())
})
it('skips git/gh attribution shims when attribution is disabled', async () => {
const env = await spawnAndGetEnv(undefined, undefined, undefined, () => ({
enableGitHubAttribution: false
}))
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBeUndefined()
expect(env.ORCA_GIT_COMMIT_TRAILER).toBeUndefined()
expect(env.ORCA_GH_PR_FOOTER).toBeUndefined()
expect(env.ORCA_GH_ISSUE_FOOTER).toBeUndefined()
expect(env.PATH ?? '').not.toContain(expectedAttributionShimDir())
})
it('prepends git/gh attribution shims for daemon-backed local PTYs', async () => {
const daemonSpawn = vi.fn(async (options) => ({ id: 'daemon-pty', pid: 123, ...options }))
setLocalPtyProvider({
spawn: daemonSpawn,
write: vi.fn(),
resize: vi.fn(),
kill: vi.fn(),
shutdown: vi.fn(),
onData: vi.fn(() => vi.fn()),
onExit: vi.fn(() => vi.fn()),
listProcesses: vi.fn(async () => []),
getForegroundProcess: vi.fn(async () => null)
} as never)
handlers.clear()
registerPtyHandlers(mainWindow as never, undefined, undefined, (() => ({
enableGitHubAttribution: true
})) as never)
await handlers.get('pty:spawn')!(null, {
cols: 80,
rows: 24,
env: {}
})
const env = daemonSpawn.mock.calls.at(-1)![0].env
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBe('1')
expect(env.PATH).toContain(expectedAttributionShimDir())
})
it('overrides ambient CODEX_HOME with the Orca-managed home for system default', async () => {
const env = await spawnAndGetEnv(
undefined,
@@ -3489,7 +3426,6 @@ describe('registerPtyHandlers', () => {
launchContext?: { workspacePath?: string; launchAgent?: TuiAgent }
) => string | null,
getSettings?: () => {
enableGitHubAttribution?: boolean
httpProxyUrl?: string
httpProxyBypassRules?: string
},
@@ -3551,7 +3487,6 @@ describe('registerPtyHandlers', () => {
launchContext?: { workspacePath?: string; launchAgent?: TuiAgent }
) => string | null,
getSettings?: () => {
enableGitHubAttribution?: boolean
httpProxyUrl?: string
httpProxyBypassRules?: string
},
@@ -4876,33 +4811,38 @@ describe('registerPtyHandlers', () => {
onPtyData: vi.fn()
}
handlers.clear()
registerPtyHandlers(mainWindow as never, runtime as never, undefined, (() => ({
enableGitHubAttribution: true
})) as never)
registerPtyHandlers(mainWindow as never, runtime as never)
const controller = runtime.setPtyController.mock.calls[0]?.[0] as RuntimeSpawnController
await controller.spawn({
cols: 80,
rows: 24,
worktreeId: 'wt-runtime',
command: 'claude',
env: {
PATH: `/tmp/orca-agent-teams-bin${delimiter}/usr/bin`,
ORCA_AGENT_TEAMS_TEAM_ID: 'team-test',
TERM_PROGRAM: 'Orca',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/stale-attribution'
},
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
})
// Why: dev mode makes buildPtyHostEnv prepend its own CLI shim on every platform, so
// the promotion below has something to beat instead of passing trivially.
const { app } = await import('electron')
const mockedApp = app as unknown as { isPackaged: boolean }
const prevPackaged = mockedApp.isPackaged
mockedApp.isPackaged = false
try {
await controller.spawn({
cols: 80,
rows: 24,
worktreeId: 'wt-runtime',
command: 'claude',
env: {
PATH: `/tmp/orca-agent-teams-bin${delimiter}/usr/bin`,
ORCA_AGENT_TEAMS_TEAM_ID: 'team-test',
TERM_PROGRAM: 'Orca'
},
envToDelete: ['TERM_PROGRAM']
})
} finally {
mockedApp.isPackaged = prevPackaged
}
const spawnOptions = daemonSpawn.mock.calls.at(-1)?.[0] as DaemonSpawnCall
expect(spawnOptions.env.PATH.split(delimiter)[0]).toBe('/tmp/orca-agent-teams-bin')
expect(spawnOptions.env.PATH).toContain(expectedAttributionShimDir())
const spawnedPath = spawnOptions.env.PATH.split(delimiter)
expect(spawnedPath[0]).toBe('/tmp/orca-agent-teams-bin')
expect(spawnedPath.some((entry) => entry.includes(join('cli', 'bin')))).toBe(true)
expect(spawnOptions.env.TERM_PROGRAM).toBeUndefined()
expect(spawnOptions.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(spawnOptions.envToDelete).toEqual(
expect.arrayContaining(['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR'])
)
expect(spawnOptions.envToDelete).toEqual(expect.arrayContaining(['TERM_PROGRAM']))
})
it('strips inherited agent-hook endpoint env from development daemon PTYs', async () => {
@@ -4922,38 +4862,38 @@ describe('registerPtyHandlers', () => {
}
})
it('prepends attribution shims on the daemon path', async () => {
const env = await daemonSpawnAndGetEnv({}, undefined, () => ({
enableGitHubAttribution: true
}))
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBe('1')
expect(env.PATH).toContain(expectedAttributionShimDir())
})
it('keeps the Agent Teams tmux shim ahead of host PATH shims on daemon pty:spawn', async () => {
const spawnOptions = await daemonSpawnAndGetOptions(
{
PATH: `/tmp/orca-agent-teams-bin${delimiter}/usr/bin`,
ORCA_AGENT_TEAMS_TEAM_ID: 'team-test',
TERM_PROGRAM: 'Orca',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/stale-attribution'
},
undefined,
() => ({ enableGitHubAttribution: true }),
undefined,
{
command: 'claude',
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
}
)
// Why: dev mode makes buildPtyHostEnv prepend its own CLI shim on every platform, so
// the promotion below has something to beat instead of passing trivially.
const { app } = await import('electron')
const mockedApp = app as unknown as { isPackaged: boolean }
const prevPackaged = mockedApp.isPackaged
mockedApp.isPackaged = false
let spawnOptions: Awaited<ReturnType<typeof daemonSpawnAndGetOptions>>
try {
spawnOptions = await daemonSpawnAndGetOptions(
{
PATH: `/tmp/orca-agent-teams-bin${delimiter}/usr/bin`,
ORCA_AGENT_TEAMS_TEAM_ID: 'team-test',
TERM_PROGRAM: 'Orca'
},
undefined,
undefined,
undefined,
{
command: 'claude',
envToDelete: ['TERM_PROGRAM']
}
)
} finally {
mockedApp.isPackaged = prevPackaged
}
expect(spawnOptions.env.PATH.split(delimiter)[0]).toBe('/tmp/orca-agent-teams-bin')
expect(spawnOptions.env.PATH).toContain(expectedAttributionShimDir())
const spawnedPath = spawnOptions.env.PATH.split(delimiter)
expect(spawnedPath[0]).toBe('/tmp/orca-agent-teams-bin')
expect(spawnedPath.some((entry) => entry.includes(join('cli', 'bin')))).toBe(true)
expect(spawnOptions.env.TERM_PROGRAM).toBeUndefined()
expect(spawnOptions.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(spawnOptions.envToDelete).toEqual(
expect.arrayContaining(['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR'])
)
expect(spawnOptions.envToDelete).toEqual(expect.arrayContaining(['TERM_PROGRAM']))
})
it('injects dev-mode ORCA_USER_DATA_PATH + dev CLI PATH on the daemon path', async () => {
@@ -4989,6 +4929,24 @@ describe('registerPtyHandlers', () => {
}
})
it('drops a legacy shim PATH entry inherited from the host process on the daemon path', async () => {
// Why: the daemon path passes a sparse env, so the prepends re-read PATH from
// process.env — the scrub must outlive that fallback (pre-upgrade host or parent pane).
const { app } = await import('electron')
const mockedApp = app as unknown as { isPackaged: boolean }
const prev = mockedApp.isPackaged
mockedApp.isPackaged = false
try {
const env = await daemonSpawnAndGetEnv({}, undefined, undefined, {
PATH: `/tmp/orca-user-data/orca-terminal-attribution/posix${delimiter}/system/bin`
})
expect(env.PATH).not.toContain('orca-terminal-attribution')
expect(env.PATH).toContain('/system/bin')
} finally {
mockedApp.isPackaged = prev
}
})
it('defers indexed Git prompt guards from the daemon wire environment', async () => {
const env = await daemonSpawnAndGetEnv(
{
@@ -5168,14 +5126,6 @@ describe('registerPtyHandlers', () => {
expect(env.ORCA_PI_SOURCE_AGENT_DIR).toBe('/ambient/pi/agent')
})
it('skips attribution shims on the daemon path when the setting is disabled', async () => {
const env = await daemonSpawnAndGetEnv({ PATH: '/usr/bin' }, undefined, () => ({
enableGitHubAttribution: false
}))
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBeUndefined()
expect(env.PATH ?? '').not.toContain(expectedAttributionShimDir())
})
it('does not mutate the caller-provided args.env on the daemon path', async () => {
// Why: the handler clones baseEnv so IPC-provided env stays pristine; a regression would leak Orca host env back into the renderer's reused copy.
const daemonSpawn = setupDaemonAdapter()
@@ -5353,7 +5303,6 @@ describe('registerPtyHandlers', () => {
// Why: host-local vars must be absent over SSH (they point at the local host/disk) — shipping them is useless or a credential leak.
expect(env.ORCA_AGENT_HOOK_PORT).toBeUndefined()
expect(env.ORCA_AGENT_HOOK_TOKEN).toBeUndefined()
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBeUndefined()
expect(env.OPENCODE_CONFIG_DIR).toBeUndefined()
expect(env.ORCA_OPENCODE_CONFIG_DIR).toBeUndefined()
expect(env.ORCA_OPENCODE_SOURCE_CONFIG_DIR).toBeUndefined()
@@ -8187,8 +8136,7 @@ describe('registerPtyHandlers', () => {
TMUX: '/tmp/orca-claude-agent-teams/team-stale,0,1',
ORCA_AGENT_TEAMS_TEAM_ID: 'team-stale',
ORCA_AGENT_TEAMS_TOKEN: 'stale-token',
TERM_PROGRAM: 'Orca',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/stale-attribution'
TERM_PROGRAM: 'Orca'
},
launchConfig: {
agentCommand: 'claude --teammate-mode auto',
@@ -8221,7 +8169,6 @@ describe('registerPtyHandlers', () => {
})
expect(spawnOptions.env.PATH.split(delimiter)[0]).toBe('/tmp/fresh-agent-teams')
expect(spawnOptions.env.TERM_PROGRAM).toBeUndefined()
expect(spawnOptions.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(result.launchConfig?.agentEnv).toMatchObject({
CLAUDE_PROFILE: 'captured',
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
+11 -32
View File
@@ -110,11 +110,11 @@ import {
markClaudePtyExited,
markClaudePtySpawned
} from '../claude-accounts/live-pty-gate'
import {
applyTerminalAttributionEnv,
resolveAttributionShellFamily
} from '../attribution/terminal-attribution'
import { ensureLinuxTerminalOrcaCliShimDir } from '../cli/linux-terminal-orca-cli-shim'
import {
isLegacyTerminalShimPathEntry,
stripLegacyTerminalShimEnv
} from '../pty/legacy-terminal-shim-dir'
import { registerPty, unregisterPty } from '../memory/pty-registry'
import { advertisedUrlWatcher } from '../ports/advertised-url-watcher'
import { track } from '../telemetry/client'
@@ -1123,14 +1123,12 @@ export type BuildPtyHostEnvOptions = {
* and strip only an inherited Orca-owned override so nested Orca panes do not
* leak the parent's managed home. A user-set CODEX_HOME is preserved. */
stripInheritedOrcaCodexHome?: boolean
githubAttributionEnabled: boolean
/** Launch command the renderer chose (e.g. 'pi', 'omp', 'claude'); resolves the per-agent
* extension target for Pi/OMP. Undefined for bare shells defaults to Pi. NEVER infer from
* disk presence (cross-agent shadowing when both dirs exist). */
launchCommand?: string
/** Trusted agent identity for wrapped commands that cannot be recognized from text. */
launchAgent?: TuiAgent
shellPath?: string
isWsl?: boolean
/** Distro for WSL spawns (null = Windows default distro); drives the WSL hook relay + endpoint repoint. Only read when isWsl. */
wslDistro?: string | null
@@ -1158,7 +1156,9 @@ function promoteAgentTeamsShimPath(
return
}
const shimPath = firstPathEntry(requestedPath)
if (!shimPath) {
// Why: requestedPath is captured before buildPtyHostEnv scrubs, so a legacy entry that
// reached the front would be re-prepended here and outlive the scrub.
if (!shimPath || isLegacyTerminalShimPathEntry(shimPath)) {
return
}
const currentPathKey = env.PATH !== undefined || env.Path === undefined ? 'PATH' : 'Path'
@@ -1892,22 +1892,9 @@ export function buildPtyHostEnv(
: bundledCliBin
}
// Why: PATH shims keep GitHub attribution scoped to Orca's own PTYs without rewriting user git config.
if (!opts.githubAttributionEnabled) {
delete baseEnv.ORCA_ENABLE_GIT_ATTRIBUTION
delete baseEnv.ORCA_GIT_COMMIT_TRAILER
delete baseEnv.ORCA_GH_PR_FOOTER
delete baseEnv.ORCA_GH_ISSUE_FOOTER
delete baseEnv.ORCA_ATTRIBUTION_SHIM_DIR
}
applyTerminalAttributionEnv(baseEnv, {
enabled: opts.githubAttributionEnabled,
userDataPath: opts.userDataPath,
shellFamily: resolveAttributionShellFamily({
shellPath: opts.shellPath,
isWsl: opts.isWsl
})
})
// Why: must run after the prepends above — they re-read PATH from the unscrubbed
// process.env when baseEnv carries none, which is the daemon path's normal shape.
stripLegacyTerminalShimEnv(baseEnv, process.platform)
return baseEnv
}
@@ -2454,10 +2441,8 @@ export function registerPtyHandlers(
skipCodexHomeEnv,
settings: getSettings?.()
}),
githubAttributionEnabled: getSettings?.()?.enableGitHubAttribution ?? false,
launchCommand: ctx?.command,
launchAgent: ctx?.launchAgent,
shellPath: ctx?.shellPath,
isWsl: ctx?.isWsl,
wslDistro: ctx?.wslDistro ?? null,
agentStatusHooksEnabled: isAgentStatusHooksEnabled(getSettings?.()),
@@ -4683,10 +4668,8 @@ export function registerPtyHandlers(
selectedCodexHomePath,
skipCodexHomeEnv,
stripInheritedOrcaCodexHome,
githubAttributionEnabled: getSettings?.()?.enableGitHubAttribution ?? false,
launchCommand,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
shellPath: daemonShellOverride ?? process.env.COMSPEC,
isWsl: shouldSkipCodexHomeEnvForWindowsShell(daemonShellOverride, cwd),
wslDistro: codexSelectionTarget.runtime === 'wsl' ? expectedWslDistro : null,
agentStatusHooksEnabled: isAgentStatusHooksEnabled(getSettings?.()),
@@ -6198,9 +6181,7 @@ export function registerPtyHandlers(
const requestedAgentTeamsPath = baseEnv?.ORCA_AGENT_TEAMS_TEAM_ID
? baseEnv[resolvePathEnvKey(baseEnv, process.platform)]
: undefined
const agentTeamsEnvToDelete = shouldRefreshAgentTeamsEnv
? ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
: undefined
const agentTeamsEnvToDelete = shouldRefreshAgentTeamsEnv ? ['TERM_PROGRAM'] : undefined
if (baseEnv && stablePaneKey) {
baseEnv.ORCA_PANE_KEY = stablePaneKey
if (typeof args.tabId === 'string') {
@@ -6339,10 +6320,8 @@ export function registerPtyHandlers(
selectedCodexHomePath,
skipCodexHomeEnv,
stripInheritedOrcaCodexHome,
githubAttributionEnabled: getSettings?.()?.enableGitHubAttribution ?? false,
launchCommand,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
shellPath: effectiveShellOverride ?? process.env.COMSPEC,
isWsl: shouldSkipCodexHomeEnvForWindowsShell(effectiveShellOverride, cwd),
wslDistro: codexSelectionTarget.runtime === 'wsl' ? expectedWslDistro : null,
agentStatusHooksEnabled: isAgentStatusHooksEnabled(getSettings?.()),
@@ -710,8 +710,8 @@ describe('LocalPtyProvider', () => {
provider.configure({
buildSpawnEnv: (_id, env) => {
env.TERM_PROGRAM = 'Orca'
env.ORCA_ATTRIBUTION_SHIM_DIR = '/tmp/orca-attribution'
env.PATH = `/tmp/orca-attribution:${env.PATH ?? ''}`
env.ORCA_STALE_TEST_ENV = '/tmp/orca-stale'
env.PATH = `/tmp/orca-stale:${env.PATH ?? ''}`
return env
}
})
@@ -724,7 +724,7 @@ describe('LocalPtyProvider', () => {
PATH: '/tmp/orca-agent-teams-bin:/usr/bin',
ORCA_AGENT_TEAMS_TEAM_ID: 'team-test'
},
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['TERM_PROGRAM', 'ORCA_STALE_TEST_ENV']
})
const spawnCall = spawnMock.mock.calls.at(-1)!
@@ -732,7 +732,7 @@ describe('LocalPtyProvider', () => {
expect(spawnCall[2].env.TERM).toBe('screen-256color')
expect(spawnCall[2].env.PATH.split(':')[0]).toBe('/tmp/orca-agent-teams-bin')
expect(spawnCall[2].env.TERM_PROGRAM).toBeUndefined()
expect(spawnCall[2].env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(spawnCall[2].env.ORCA_STALE_TEST_ENV).toBeUndefined()
})
it('drops stale inherited Git config indices behind a smaller explicit count', async () => {
@@ -842,9 +842,9 @@ describe('LocalPtyProvider', () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
provider.configure({
buildSpawnEnv: (_id, env) => {
// Why: attribution collapses Windows PATH onto `Path` and prepends its own shim dir.
// Why: host env collapses Windows PATH onto `Path` and prepends its own shim dir.
delete env.PATH
env.Path = `/tmp/orca-attribution:${env.Path ?? ''}`
env.Path = `/tmp/orca-stale:${env.Path ?? ''}`
return env
}
})
+5 -6
View File
@@ -27,7 +27,7 @@ import {
} from './local-pty-utils'
import { prepareMacosTccLoginShell } from './macos-tcc-login-shell'
import {
getAttributionShellLaunchConfig,
getMarkerlessShellLaunchConfig,
getShellReadyLaunchConfig,
createShellReadyScanState,
drainShellReadyHeldBytes,
@@ -791,7 +791,6 @@ export class LocalPtyProvider implements IPtyProvider {
if (!wslInfo && process.platform !== 'win32') {
// Why: OpenCode/Codex PATH restoration and OMP's status wrapper need shell-ready code after user startup files run.
const needsNoMarkerWrapper =
finalEnv.ORCA_ATTRIBUTION_SHIM_DIR ||
finalEnv.ORCA_OPENCODE_CONFIG_DIR ||
finalEnv.ORCA_MIMOCODE_HOME ||
finalEnv.ORCA_OMP_STATUS_EXTENSION ||
@@ -808,16 +807,16 @@ export class LocalPtyProvider implements IPtyProvider {
getFallbackShellReadyConfig = (shell) =>
shouldWaitForShellReady
? getShellReadyLaunchConfig(shell)
: getAttributionShellLaunchConfig(shell)
: getMarkerlessShellLaunchConfig(shell)
shellLaunch = shouldWaitForShellReady
? getShellReadyLaunchConfig(shellPath)
: getAttributionShellLaunchConfig(shellPath)
: getMarkerlessShellLaunchConfig(shellPath)
} else if (args.command) {
getFallbackShellReadyConfig = (shell) => getShellReadyLaunchConfig(shell)
shellLaunch = getShellReadyLaunchConfig(shellPath)
} else if (needsNoMarkerWrapper) {
getFallbackShellReadyConfig = (shell) => getAttributionShellLaunchConfig(shell)
shellLaunch = getAttributionShellLaunchConfig(shellPath)
getFallbackShellReadyConfig = (shell) => getMarkerlessShellLaunchConfig(shell)
shellLaunch = getMarkerlessShellLaunchConfig(shellPath)
} else {
getFallbackShellReadyConfig = undefined
}
@@ -380,10 +380,10 @@ describePosix('local PTY shell-ready launch config', () => {
expect(init).toContain('functions -e __orca_shell_ready_marker')
})
it('keeps attribution-only fish spawns unwrapped', async () => {
const { getAttributionShellLaunchConfig } = await importFreshLocalPtyShellReady()
it('keeps markerless fish spawns unwrapped', async () => {
const { getMarkerlessShellLaunchConfig } = await importFreshLocalPtyShellReady()
const config = getAttributionShellLaunchConfig('/opt/homebrew/bin/fish')
const config = getMarkerlessShellLaunchConfig('/opt/homebrew/bin/fish')
expect(config).toEqual({ args: null, env: {}, supportsReadyMarker: false })
})
@@ -816,8 +816,6 @@ path=(/custom/bin $path)
}
delete cleanEnv.ZDOTDIR
delete cleanEnv.ORCA_ORIG_ZDOTDIR
// Why: this test isolates zsh top-level path scoping, not attribution shim ordering.
delete cleanEnv.ORCA_ATTRIBUTION_SHIM_DIR
cleanEnv.ZDOTDIR = config.env.ZDOTDIR // Point to Orca wrapper dir
const result = spawnSync(
@@ -856,7 +854,6 @@ path=(/custom/bin $path)
}
delete cleanEnv.ZDOTDIR
delete cleanEnv.ORCA_ORIG_ZDOTDIR
delete cleanEnv.ORCA_ATTRIBUTION_SHIM_DIR
delete cleanEnv.USER_ZSHRC_LOADED
cleanEnv.ZDOTDIR = join(movedUserData, 'shell-ready', 'zsh')
@@ -904,7 +901,6 @@ path=(/custom/bin $path)
}
delete cleanEnv.ZDOTDIR
delete cleanEnv.ORCA_ORIG_ZDOTDIR
delete cleanEnv.ORCA_ATTRIBUTION_SHIM_DIR
delete cleanEnv.USER_ZSHRC_LOADED
cleanEnv.ZDOTDIR = join(nonAsciiUserData, 'shell-ready', 'zsh')
+2 -26
View File
@@ -110,14 +110,6 @@ fi
# Why: preserve bash's normal login-shell contract. Many users already source
# ~/.bashrc from ~/.bash_profile; forcing ~/.bashrc again here would duplicate
# PATH edits, hooks, and prompt init in Orca startup-command shells.
__orca_restore_attribution_path() {
[[ -n "\${ORCA_ATTRIBUTION_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_ATTRIBUTION_SHIM_DIR}"|"\${ORCA_ATTRIBUTION_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_ATTRIBUTION_SHIM_DIR}:$PATH"
}
__orca_restore_attribution_path
__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
@@ -226,14 +218,6 @@ ${getZshStartupFileSourceBlock({
interactiveOnly: true,
skipWhenHomeIsCurrentZdotdir: true
})}
__orca_restore_attribution_path() {
[[ -n "\${ORCA_ATTRIBUTION_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_ATTRIBUTION_SHIM_DIR}"|"\${ORCA_ATTRIBUTION_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_ATTRIBUTION_SHIM_DIR}:$PATH"
}
[[ ! -o login ]] && __orca_restore_attribution_path
__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
@@ -287,14 +271,6 @@ ${getZshStartupFileSourceBlock({ fileName: '.zprofile' })}
const zshRc = getZshShellReadyRcfileContent()
const zshLogin = `# Orca zsh shell-ready wrapper
${getZshStartupFileSourceBlock({ fileName: '.zlogin', interactiveOnly: true })}
__orca_restore_attribution_path() {
[[ -n "\${ORCA_ATTRIBUTION_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
"\${ORCA_ATTRIBUTION_SHIM_DIR}"|"\${ORCA_ATTRIBUTION_SHIM_DIR}:"*) return 0 ;;
esac
export PATH="\${ORCA_ATTRIBUTION_SHIM_DIR}:$PATH"
}
__orca_restore_attribution_path
__orca_restore_agent_teams_path() {
[[ -n "\${ORCA_AGENT_TEAMS_SHIM_DIR:-}" ]] || return 0
case "$PATH" in
@@ -402,7 +378,7 @@ function getWrappedShellLaunchConfig(
}
}
// Why: mirrors daemon/shell-ready.ts; attribution-only fish stays unwrapped.
// Why: mirrors daemon/shell-ready.ts; markerless fish stays unwrapped.
if (shellName === 'fish' && options.emitReadyMarker) {
return {
args: ['-l', '-C', getFishShellReadyInitCommand(SHELL_READY_MARKER_ESCAPED)],
@@ -422,7 +398,7 @@ export function getShellReadyLaunchConfig(shellPath: string): ShellReadyLaunchCo
return getWrappedShellLaunchConfig(shellPath, { emitReadyMarker: true })
}
export function getAttributionShellLaunchConfig(shellPath: string): ShellReadyLaunchConfig {
export function getMarkerlessShellLaunchConfig(shellPath: string): ShellReadyLaunchConfig {
return getWrappedShellLaunchConfig(shellPath, { emitReadyMarker: false })
}
+3 -3
View File
@@ -427,7 +427,7 @@ describe('SshPtyProvider', () => {
it('preserves explicit TERM and forwards final env deletions to the relay', async () => {
mux.request.mockResolvedValue({ id: 'pty-env-precedence' })
const envToDelete = ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
const envToDelete = ['TERM_PROGRAM', 'ORCA_STALE_TEST_ENV']
await provider.spawn({
cols: 120,
@@ -435,7 +435,7 @@ describe('SshPtyProvider', () => {
env: {
TERM: 'screen-256color',
TERM_PROGRAM: 'stale-terminal',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/stale-attribution'
ORCA_STALE_TEST_ENV: '/tmp/stale-env'
},
envToDelete
})
@@ -452,7 +452,7 @@ describe('SshPtyProvider', () => {
})
const spawnCall = mux.request.mock.calls.find((call) => call[0] === 'pty.spawn')
expect(spawnCall?.[1]?.env).not.toHaveProperty('TERM_PROGRAM')
expect(spawnCall?.[1]?.env).not.toHaveProperty('ORCA_ATTRIBUTION_SHIM_DIR')
expect(spawnCall?.[1]?.env).not.toHaveProperty('ORCA_STALE_TEST_ENV')
})
it('forwards provider command delivery to the relay', async () => {
@@ -0,0 +1,124 @@
import { describe, expect, it, beforeEach } from 'vitest'
import { chmodSync, existsSync, mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
__resetLegacyTerminalShimRemovalForTests,
removeLegacyTerminalShimDir,
stripLegacyTerminalShimEnv
} from './legacy-terminal-shim-dir'
// Why: the failure case is produced with a read-only parent dir, which Windows ignores and root bypasses.
const itOnPosixNonRoot = process.platform === 'win32' || process.getuid?.() === 0 ? it.skip : it
describe('legacy terminal shim removal', () => {
beforeEach(() => {
__resetLegacyTerminalShimRemovalForTests()
})
it('deletes the orphaned wrapper directory left by older installs', () => {
const userData = mkdtempSync(join(tmpdir(), 'orca-legacy-shim-'))
const shimDir = join(userData, 'orca-terminal-attribution', 'posix')
mkdirSync(shimDir, { recursive: true })
writeFileSync(join(shimDir, 'git'), '#!/usr/bin/env bash\n')
removeLegacyTerminalShimDir(userData)
expect(existsSync(join(userData, 'orca-terminal-attribution'))).toBe(false)
expect(existsSync(userData)).toBe(true)
})
it('does not throw when nothing is left to remove', () => {
const userData = mkdtempSync(join(tmpdir(), 'orca-legacy-shim-'))
expect(() => removeLegacyTerminalShimDir(userData)).not.toThrow()
})
itOnPosixNonRoot('stays retryable when the removal fails, and latches once it succeeds', () => {
const userData = mkdtempSync(join(tmpdir(), 'orca-legacy-shim-'))
const legacyRoot = join(userData, 'orca-terminal-attribution')
mkdirSync(join(legacyRoot, 'posix'), { recursive: true })
writeFileSync(join(legacyRoot, 'posix', 'git'), '#!/usr/bin/env bash\n')
// Why: a read-only parent makes the unlink fail the way a locked Windows wrapper does.
chmodSync(userData, 0o500)
try {
removeLegacyTerminalShimDir(userData)
expect(existsSync(legacyRoot)).toBe(true)
} finally {
chmodSync(userData, 0o700)
}
// The failed attempt must not have latched the guard.
removeLegacyTerminalShimDir(userData)
expect(existsSync(legacyRoot)).toBe(false)
// A success latches it: a directory recreated afterwards is left alone.
mkdirSync(legacyRoot, { recursive: true })
removeLegacyTerminalShimDir(userData)
expect(existsSync(legacyRoot)).toBe(true)
})
it('drops inherited shim env and its PATH entry without touching real entries', () => {
// Why: a daemon predating the removal reseeds these from its own process.env.
const env: Record<string, string> = {
PATH: `/home/u/.orca/orca-terminal-attribution/posix:/usr/local/bin:/usr/bin`,
ORCA_ENABLE_GIT_ATTRIBUTION: '1',
ORCA_GIT_COMMIT_TRAILER: 'Co-authored-by: Orca <help@stably.ai>',
ORCA_GH_PR_FOOTER: 'footer',
ORCA_GH_ISSUE_FOOTER: 'footer',
ORCA_ATTRIBUTION_SHIM_DIR: '/home/u/.orca/orca-terminal-attribution/posix',
ORCA_REAL_GIT: '/usr/bin/git',
ORCA_REAL_GH: '/usr/bin/gh',
HOME: '/home/u'
}
stripLegacyTerminalShimEnv(env, 'linux')
expect(env.PATH).toBe('/usr/local/bin:/usr/bin')
expect(env.ORCA_ENABLE_GIT_ATTRIBUTION).toBeUndefined()
expect(env.ORCA_GIT_COMMIT_TRAILER).toBeUndefined()
expect(env.ORCA_GH_PR_FOOTER).toBeUndefined()
expect(env.ORCA_GH_ISSUE_FOOTER).toBeUndefined()
expect(env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(env.ORCA_REAL_GIT).toBeUndefined()
expect(env.ORCA_REAL_GH).toBeUndefined()
expect(env.HOME).toBe('/home/u')
})
it('strips the Windows shim entry on the inherited Path spelling', () => {
const env: Record<string, string> = {
Path: `C:\\Users\\u\\AppData\\Roaming\\Orca\\orca-terminal-attribution\\win32;C:\\Windows\\System32`
}
stripLegacyTerminalShimEnv(env, 'win32')
expect(env.Path).toBe('C:\\Windows\\System32')
})
it('matches a re-cased shim entry on case-insensitive filesystems', () => {
const env: Record<string, string> = {
Path: `C:\\Users\\u\\AppData\\Roaming\\Orca\\Orca-Terminal-Attribution\\win32;C:\\Windows\\System32`
}
stripLegacyTerminalShimEnv(env, 'win32')
expect(env.Path).toBe('C:\\Windows\\System32')
})
it('keeps neighbouring directories that merely share the name prefix', () => {
const env: Record<string, string> = {
PATH: '/opt/orca-terminal-attribution:/home/u/orca-terminal-attribution-notes/bin:/usr/bin'
}
stripLegacyTerminalShimEnv(env, 'linux')
expect(env.PATH).toBe(
'/opt/orca-terminal-attribution:/home/u/orca-terminal-attribution-notes/bin:/usr/bin'
)
})
it('leaves an unrelated PATH untouched', () => {
const env: Record<string, string> = { PATH: '/usr/local/bin:/usr/bin' }
stripLegacyTerminalShimEnv(env, 'linux')
expect(env.PATH).toBe('/usr/local/bin:/usr/bin')
})
})
+71
View File
@@ -0,0 +1,71 @@
import { rmSync } from 'node:fs'
import { join } from 'node:path'
import { resolvePathEnvKey } from './windows-environment-path'
const LEGACY_SHIM_ROOT_DIR = 'orca-terminal-attribution'
const LEGACY_SHIM_ENV_KEYS = [
'ORCA_ENABLE_GIT_ATTRIBUTION',
'ORCA_GIT_COMMIT_TRAILER',
'ORCA_GH_PR_FOOTER',
'ORCA_GH_ISSUE_FOOTER',
'ORCA_ATTRIBUTION_SHIM_DIR',
'ORCA_REAL_GIT',
'ORCA_REAL_GH'
] as const
let removed = false
/** Why: a daemon that outlives an upgrade keeps seeding these from its own process.env
* (pty-subprocess reads process.env as the authoritative base), and the wrappers they
* point at are gated only on inherited env. Deleting the scripts makes both inert. */
export function removeLegacyTerminalShimDir(userDataPath: string): void {
if (removed) {
return
}
try {
// Why: a surviving pre-upgrade pane can hold the wrapper open on Windows; retry like
// the other userData removals rather than forfeiting cleanup for the whole run.
rmSync(join(userDataPath, LEGACY_SHIM_ROOT_DIR), {
recursive: true,
force: true,
maxRetries: 3,
retryDelay: 50
})
removed = true
} catch {
// Best-effort: a locked file must not block startup, and the next launch retries.
}
}
export function isLegacyTerminalShimPathEntry(entry: string): boolean {
return entry.replaceAll('\\', '/').toLowerCase().includes(`/${LEGACY_SHIM_ROOT_DIR}/`)
}
export function stripLegacyTerminalShimEnv(
env: Record<string, string>,
platform: NodeJS.Platform = process.platform
): void {
for (const key of LEGACY_SHIM_ENV_KEYS) {
delete env[key]
}
const pathKey = resolvePathEnvKey(env, platform)
const current = env[pathKey]
if (!current) {
return
}
const delimiter = platform === 'win32' ? ';' : ':'
const cleaned = current
.split(delimiter)
.filter((entry) => entry && !isLegacyTerminalShimPathEntry(entry))
.join(delimiter)
if (cleaned) {
env[pathKey] = cleaned
} else {
delete env[pathKey]
}
}
/** Test-only: the module-level once-guard would otherwise leak across cases. */
export function __resetLegacyTerminalShimRemovalForTests(): void {
removed = false
}
@@ -61,7 +61,7 @@ describe('claude agent teams shim env', () => {
expect(plan).toMatchObject({
command: "claude --teammate-mode auto 'hello'",
env: expect.objectContaining({ TMUX_PANE: '%1' }),
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['TERM_PROGRAM']
})
expect(capturedShimBin).toBe(cliPath)
}
@@ -47,7 +47,7 @@ export async function buildClaudeAgentTeamsLaunchPlan(args: {
return {
command: addClaudeTeammateModeAuto(args.command),
env,
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['TERM_PROGRAM']
}
}
@@ -117,7 +117,7 @@ export class ClaudeAgentTeamsTmuxDispatcher {
direction: splitTarget.direction,
command: parsed.positional.join(' ') || undefined,
env: paneEnv(team, fakePaneId),
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR'],
envToDelete: ['TERM_PROGRAM'],
activate: false
})
const pane: TeamPane = {
@@ -168,7 +168,7 @@ export class ClaudeAgentTeamsTmuxDispatcher {
direction: pane.splitDirection ?? 'horizontal',
command,
env: paneEnv(team, pane.fakePaneId),
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR'],
envToDelete: ['TERM_PROGRAM'],
activate: false
})
try {
+2 -4
View File
@@ -318,8 +318,7 @@ describe('RuntimeGitCommands', () => {
getRuntimeSettings: () =>
({
commitMessageAi: { enabled: true, agentId: 'codex' },
agentCmdOverrides: {},
enableGitHubAttribution: false
agentCmdOverrides: {}
}) as GlobalSettings,
getCommitMessageAgentEnvironment: () => ({
prepareForCodexLaunch: () => '/managed/codex-home'
@@ -374,8 +373,7 @@ describe('RuntimeGitCommands', () => {
getRuntimeSettings: () =>
({
commitMessageAi: { enabled: true, agentId: 'codex' },
agentCmdOverrides: {},
enableGitHubAttribution: false
agentCmdOverrides: {}
}) as GlobalSettings,
getCommitMessageAgentEnvironment: () => ({
prepareForCodexLaunch
+1 -4
View File
@@ -90,10 +90,7 @@ import type { HostedReviewProvider } from '../../shared/hosted-review'
export type ResolvedRuntimeGitWorktree = Worktree & { git: GitWorktreeInfo }
type RuntimeCommitMessageSettingsOverride = Partial<
Pick<
GlobalSettings,
'commitMessageAi' | 'sourceControlAi' | 'agentCmdOverrides' | 'enableGitHubAttribution'
>
Pick<GlobalSettings, 'commitMessageAi' | 'sourceControlAi' | 'agentCmdOverrides'>
> & {
commitMessageDiscoveryHostKey?: string
sourceControlAiResolvedParams?: ResolvedSourceControlAiGenerationParams
@@ -179,7 +179,6 @@ export const GitGenerateCommitMessage = WorktreeSelector.extend({
sourceControlAi: SourceControlAiSettings.optional(),
sourceControlAiResolvedParams: ResolvedSourceControlAiGenerationParams.optional(),
agentCmdOverrides: z.record(z.string(), z.string()).optional(),
enableGitHubAttribution: z.boolean().optional(),
commitMessageDiscoveryHostKey: z.string().optional()
})
-2
View File
@@ -530,7 +530,6 @@ describe('git RPC methods', () => {
worktree: 'id:wt-1',
commitMessageAi,
agentCmdOverrides,
enableGitHubAttribution: true,
commitMessageDiscoveryHostKey: 'runtime:env-1'
})
)
@@ -538,7 +537,6 @@ describe('git RPC methods', () => {
expect(runtime.generateRuntimeCommitMessage).toHaveBeenCalledWith('id:wt-1', {
commitMessageAi,
agentCmdOverrides,
enableGitHubAttribution: true,
commitMessageDiscoveryHostKey: 'runtime:env-1'
})
})
-6
View File
@@ -33,7 +33,6 @@ type CommitMessageGenerationOverride = {
sourceControlAi?: GlobalSettings['sourceControlAi']
sourceControlAiResolvedParams?: ResolvedSourceControlAiGenerationParams
agentCmdOverrides?: GlobalSettings['agentCmdOverrides']
enableGitHubAttribution?: boolean
commitMessageDiscoveryHostKey?: string
}
@@ -44,7 +43,6 @@ function buildCommitMessageGenerationOverride(params: {
sourceControlAi?: unknown
sourceControlAiResolvedParams?: unknown
agentCmdOverrides?: unknown
enableGitHubAttribution?: boolean
commitMessageDiscoveryHostKey?: string
}): CommitMessageGenerationOverride | undefined {
if (
@@ -52,7 +50,6 @@ function buildCommitMessageGenerationOverride(params: {
params.sourceControlAi === undefined &&
params.sourceControlAiResolvedParams === undefined &&
params.agentCmdOverrides === undefined &&
params.enableGitHubAttribution === undefined &&
params.commitMessageDiscoveryHostKey === undefined
) {
return undefined
@@ -75,9 +72,6 @@ function buildCommitMessageGenerationOverride(params: {
agentCmdOverrides: params.agentCmdOverrides as GlobalSettings['agentCmdOverrides']
}
: {}),
...(params.enableGitHubAttribution !== undefined
? { enableGitHubAttribution: params.enableGitHubAttribution }
: {}),
...(params.commitMessageDiscoveryHostKey !== undefined
? { commitMessageDiscoveryHostKey: params.commitMessageDiscoveryHostKey }
: {})
@@ -89,7 +89,6 @@ beforeEach(() => {
describe('resolveCommitMessageSettings', () => {
it('falls back when a dynamic persisted model was not discovered', () => {
const settings = getDefaultSettings('/tmp')
settings.enableGitHubAttribution = true
settings.commitMessageAi = {
enabled: true,
agentId: 'codex',
+20 -20
View File
@@ -2799,16 +2799,16 @@ describe('PtyHandler', () => {
handler.addEnvAugmenter(() => ({
TERM: 'augmenter-term',
TERM_PROGRAM: 'augmenter-terminal',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/augmenter-attribution'
ORCA_STALE_TEST_ENV: '/tmp/augmenter-stale'
}))
await dispatcher.callRequest('pty.spawn', {
env: {
TERM: 'screen-256color',
TERM_PROGRAM: 'renderer-terminal',
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/renderer-attribution'
ORCA_STALE_TEST_ENV: '/tmp/renderer-stale'
},
envToDelete: ['TERM_PROGRAM', 'ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['TERM_PROGRAM', 'ORCA_STALE_TEST_ENV']
})
const spawnEnv = mockPtySpawn.mock.calls[0][2] as {
@@ -2820,7 +2820,7 @@ describe('PtyHandler', () => {
expect(spawnEnv.env.COLORTERM).toBe('truecolor')
expect(spawnEnv.env.FORCE_HYPERLINK).toBe('1')
expect(spawnEnv.env.TERM_PROGRAM).toBeUndefined()
expect(spawnEnv.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(spawnEnv.env.ORCA_STALE_TEST_ENV).toBeUndefined()
})
it('replaces an ambient TERM=dumb when no explicit TERM is supplied', async () => {
@@ -3185,7 +3185,7 @@ describe('PtyHandler', () => {
it('normalizes an explicit empty TERM and preserves sanitized env deletions on revive', async () => {
await dispatcher.callRequest('pty.spawn', {
env: { TERM: '' },
envToDelete: ['ORCA_ATTRIBUTION_SHIM_DIR', '', 42]
envToDelete: ['ORCA_STALE_TEST_ENV', '', 42]
})
const initialEnv = mockPtySpawn.mock.calls[0][2] as {
@@ -3201,14 +3201,14 @@ describe('PtyHandler', () => {
envToDelete?: string[]
}[]
expect(serialized.explicitTerm).toBeUndefined()
expect(serialized.envToDelete).toEqual(['ORCA_ATTRIBUTION_SHIM_DIR'])
expect(serialized.envToDelete).toEqual(['ORCA_STALE_TEST_ENV'])
await handler.dispose({ waitForPhysicalExit: false })
mockPtySpawn.mockClear()
dispatcher = createMockDispatcher()
handler = new PtyHandler(dispatcher as unknown as RelayDispatcher)
handler.addEnvAugmenter(() => ({
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/revived-attribution'
ORCA_STALE_TEST_ENV: '/tmp/revived-stale'
}))
const killSpy = vi.spyOn(process, 'kill').mockImplementation(() => true)
try {
@@ -3223,7 +3223,7 @@ describe('PtyHandler', () => {
}
expect(revivedEnv.name).toBe('xterm-256color')
expect(revivedEnv.env.TERM).toBe('xterm-256color')
expect(revivedEnv.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(revivedEnv.env.ORCA_STALE_TEST_ENV).toBeUndefined()
})
it('drops legacy empty explicit TERM metadata after revive', async () => {
@@ -3235,11 +3235,11 @@ describe('PtyHandler', () => {
rows: 24,
cwd: process.cwd(),
explicitTerm: '',
envToDelete: ['ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['ORCA_STALE_TEST_ENV']
}
])
handler.addEnvAugmenter(() => ({
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/legacy-empty-attribution'
ORCA_STALE_TEST_ENV: '/tmp/legacy-empty-stale'
}))
const killSpy = vi.spyOn(process, 'kill').mockImplementation(() => true)
try {
@@ -3254,7 +3254,7 @@ describe('PtyHandler', () => {
}
expect(revivedEnv.name).toBe('xterm-256color')
expect(revivedEnv.env.TERM).toBe('xterm-256color')
expect(revivedEnv.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(revivedEnv.env.ORCA_STALE_TEST_ENV).toBeUndefined()
const serializedState = (await dispatcher.callRequest('pty.serialize', {
ids: ['pty-8']
@@ -3264,13 +3264,13 @@ describe('PtyHandler', () => {
envToDelete?: string[]
}[]
expect(serialized.explicitTerm).toBeUndefined()
expect(serialized.envToDelete).toEqual(['ORCA_ATTRIBUTION_SHIM_DIR'])
expect(serialized.envToDelete).toEqual(['ORCA_STALE_TEST_ENV'])
})
it('preserves explicit TERM and env deletions through repeated revive cycles', async () => {
await dispatcher.callRequest('pty.spawn', {
env: { TERM: 'screen-256color' },
envToDelete: ['ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['ORCA_STALE_TEST_ENV']
})
let state = (await dispatcher.callRequest('pty.serialize', { ids: ['pty-1'] })) as string
@@ -3281,7 +3281,7 @@ describe('PtyHandler', () => {
dispatcher = createMockDispatcher()
handler = new PtyHandler(dispatcher as unknown as RelayDispatcher)
handler.addEnvAugmenter(() => ({
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/first-revive'
ORCA_STALE_TEST_ENV: '/tmp/first-revive'
}))
await dispatcher.callRequest('pty.revive', { state })
@@ -3291,12 +3291,12 @@ describe('PtyHandler', () => {
}
expect(firstRevivedEnv.name).toBe('screen-256color')
expect(firstRevivedEnv.env.TERM).toBe('screen-256color')
expect(firstRevivedEnv.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(firstRevivedEnv.env.ORCA_STALE_TEST_ENV).toBeUndefined()
state = (await dispatcher.callRequest('pty.serialize', { ids: ['pty-1'] })) as string
expect(JSON.parse(state)).toMatchObject([
{
explicitTerm: 'screen-256color',
envToDelete: ['ORCA_ATTRIBUTION_SHIM_DIR']
envToDelete: ['ORCA_STALE_TEST_ENV']
}
])
@@ -3305,7 +3305,7 @@ describe('PtyHandler', () => {
dispatcher = createMockDispatcher()
handler = new PtyHandler(dispatcher as unknown as RelayDispatcher)
handler.addEnvAugmenter(() => ({
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/second-revive'
ORCA_STALE_TEST_ENV: '/tmp/second-revive'
}))
await dispatcher.callRequest('pty.revive', { state })
} finally {
@@ -3318,12 +3318,12 @@ describe('PtyHandler', () => {
}
expect(secondRevivedEnv.name).toBe('screen-256color')
expect(secondRevivedEnv.env.TERM).toBe('screen-256color')
expect(secondRevivedEnv.env.ORCA_ATTRIBUTION_SHIM_DIR).toBeUndefined()
expect(secondRevivedEnv.env.ORCA_STALE_TEST_ENV).toBeUndefined()
})
it('revives legacy serialized entries with default TERM and no env deletions', async () => {
handler.addEnvAugmenter(() => ({
ORCA_ATTRIBUTION_SHIM_DIR: '/tmp/legacy-attribution'
ORCA_STALE_TEST_ENV: '/tmp/legacy-stale'
}))
const state = JSON.stringify([
{
@@ -3343,7 +3343,7 @@ describe('PtyHandler', () => {
const revivedEnv = mockPtySpawn.mock.calls[0][2] as { env: Record<string, string> }
expect(revivedEnv.env.TERM).toBe('xterm-256color')
expect(revivedEnv.env.ORCA_ATTRIBUTION_SHIM_DIR).toBe('/tmp/legacy-attribution')
expect(revivedEnv.env.ORCA_STALE_TEST_ENV).toBe('/tmp/legacy-stale')
})
it('revive preserves attach identity metadata without exporting hook identity env', async () => {
@@ -322,55 +322,6 @@ export function GitPane({
branchPromptDiscardSignal={branchPromptDiscardSignal}
settingsSearchQuery={searchQuery}
/>
) : null,
matchesSettingsSearch(searchQuery, {
title: translate('auto.components.settings.GitPane.e02ea23a32', 'Orca Attribution'),
description: translate(
'auto.components.settings.GitPane.d2eede4c54',
'Add Orca attribution to commits, PRs, and issues.'
),
keywords: [
translate('auto.components.settings.GitPane.32dca11189', 'github'),
translate('auto.components.settings.GitPane.895d3f70b8', 'gh'),
translate('auto.components.settings.GitPane.b4ef5428a7', 'pr'),
translate('auto.components.settings.GitPane.afada55042', 'issue'),
translate('auto.components.settings.GitPane.9838c921ed', 'co-author'),
translate('auto.components.settings.GitPane.b5f534717a', 'coauthored'),
translate('auto.components.settings.GitPane.b9b5771bb1', 'attribution'),
translate('auto.components.settings.GitPane.e71ce09c42', 'orca')
]
}) ? (
<SearchableSetting
key="github-attribution"
title={translate('auto.components.settings.GitPane.e02ea23a32', 'Orca Attribution')}
description={translate(
'auto.components.settings.GitPane.d2eede4c54',
'Add Orca attribution to commits, PRs, and issues.'
)}
keywords={['github', 'gh', 'pr', 'issue', 'co-author', 'coauthored', 'attribution', 'orca']}
className="flex items-center justify-between gap-4 py-2"
>
<div className="space-y-0.5">
<Label>
{translate('auto.components.settings.GitPane.e02ea23a32', 'Orca Attribution')}
</Label>
<p className="text-xs text-muted-foreground">
{translate(
'auto.components.settings.GitPane.d2eede4c54',
'Add Orca attribution to commits, PRs, and issues.'
)}
</p>
</div>
<Switch
aria-label={translate('auto.components.settings.GitPane.e02ea23a32', 'Orca Attribution')}
checked={settings.enableGitHubAttribution}
onCheckedChange={(checked) =>
updateSettings({
enableGitHubAttribution: checked
})
}
/>
</SearchableSetting>
) : null
].filter(Boolean)
@@ -1464,7 +1464,7 @@ function Settings(): React.JSX.Element {
)}
description={translate(
'auto.components.settings.Settings.cfa34f4465',
'Branch naming, base refs, attribution, and Git AI Author.'
'Branch naming, base refs, and Git AI Author.'
)}
searchEntries={getSectionSearchEntries('git')}
forceVisible={hasUnsavedSourceControlAiPromptChanges}
@@ -121,22 +121,5 @@ export const getGitPaneSearchEntries = createLocalizedCatalog(() => [
)
]
},
...getAutoRenameBranchSearchEntries(),
{
title: translate('auto.components.settings.git.search.bc7d9f69ce', 'Orca Attribution'),
description: translate(
'auto.components.settings.git.search.118c23484b',
'Add Orca attribution to commits, PRs, and issues.'
),
keywords: [
...translateSearchKeyword('auto.components.settings.git.search.d088806071', 'github'),
...translateSearchKeyword('auto.components.settings.git.search.16f53f7323', 'gh'),
...translateSearchKeyword('auto.components.settings.git.search.6bdea421bb', 'pr'),
...translateSearchKeyword('auto.components.settings.git.search.af0a144bfb', 'issue'),
...translateSearchKeyword('auto.components.settings.git.search.61f9f5d1fc', 'co-author'),
...translateSearchKeyword('auto.components.settings.git.search.8461c908ae', 'coauthored'),
...translateSearchKeyword('auto.components.settings.git.search.1b93c1143c', 'attribution'),
...translateSearchKeyword('auto.components.settings.git.search.61eab13403', 'orca')
]
}
...getAutoRenameBranchSearchEntries()
])
@@ -362,7 +362,7 @@ export function buildSettingsNavigationMetadata({
),
description: translate(
'auto.hooks.useSettingsNavigationMetadata.ab4b21b58e',
'Branch naming, base refs, attribution, and Git AI Author.'
'Branch naming, base refs, and Git AI Author.'
),
icon: GitBranch,
// Why: Git AI Author is rendered inside Git, so shared
+2 -18
View File
@@ -868,7 +868,7 @@
"a9fb10afca": "Terminal",
"5235c215ca": "Choose which task providers appear in the Tasks page and sidebar.",
"85f4fd7710": "Task Sources",
"ab4b21b58e": "Branch naming, base refs, attribution, and Git AI Author.",
"ab4b21b58e": "Branch naming, base refs, and Git AI Author.",
"09607cb0fe": "Git & Source Control",
"33a5e1d597": "Connect GitHub, GitLab, Linear, and source-hosting services.",
"2b043783ef": "Integrations",
@@ -6357,14 +6357,6 @@
"808f9a726e": "No prefix will be applied"
},
"GitPane": {
"d2eede4c54": "Add Orca attribution to commits, PRs, and issues.",
"e02ea23a32": "Orca Attribution",
"e71ce09c42": "orca",
"b9b5771bb1": "attribution",
"b5f534717a": "coauthored",
"9838c921ed": "co-author",
"afada55042": "issue",
"b4ef5428a7": "pr",
"895d3f70b8": "gh",
"32dca11189": "github",
"c4f610d057": "Current GitLab CLI REST rate-limit headers when available.",
@@ -7277,7 +7269,7 @@
"3de4bbb841": "Terminal",
"dd72ed437a": "Choose which task providers appear in the Tasks page and sidebar.",
"11faa2f7dd": "Task Sources",
"cfa34f4465": "Branch naming, base refs, attribution, and Git AI Author.",
"cfa34f4465": "Branch naming, base refs, and Git AI Author.",
"70100f94c7": "Git & Source Control",
"b07041697f": "Connect GitHub, GitLab, Linear, and source-hosting services.",
"c9ca101a3b": "Integrations",
@@ -8663,16 +8655,8 @@
},
"git": {
"search": {
"61eab13403": "orca",
"1b93c1143c": "attribution",
"8461c908ae": "coauthored",
"61f9f5d1fc": "co-author",
"af0a144bfb": "issue",
"6bdea421bb": "pr",
"16f53f7323": "gh",
"d088806071": "github",
"118c23484b": "Add Orca attribution to commits, PRs, and issues.",
"bc7d9f69ce": "Orca Attribution",
"40f9b815fd": "api budget",
"b7e52124c7": "rate limit",
"ead733645f": "glab",
+2 -18
View File
@@ -786,7 +786,7 @@
"a9fb10afca": "Terminal",
"5235c215ca": "Elige qué proveedores de tareas aparecen en la página Tareas y en la barra lateral.",
"85f4fd7710": "Fuentes de tareas",
"ab4b21b58e": "Nombres de ramas, refs base, atribución y Git AI Author.",
"ab4b21b58e": "Nombres de rama, refs base y Git AI Author.",
"09607cb0fe": "Git y Source Control",
"33a5e1d597": "Conecta GitHub, GitLab, Linear y servicios de hosting de código.",
"2b043783ef": "Integraciones",
@@ -5993,14 +5993,6 @@
"808f9a726e": "No se aplicará ningún prefijo"
},
"GitPane": {
"d2eede4c54": "Agrega la atribución de Orca a commits, PR e issues.",
"e02ea23a32": "Atribución de Orca",
"e71ce09c42": "orca",
"b9b5771bb1": "atribución",
"b5f534717a": "coautor",
"9838c921ed": "coautor",
"afada55042": "issue",
"b4ef5428a7": "pr",
"895d3f70b8": "gh",
"32dca11189": "github",
"c4f610d057": "Encabezados actuales de límite de tasa REST de GitLab CLI cuando estén disponibles.",
@@ -6889,7 +6881,7 @@
"3de4bbb841": "Terminal",
"dd72ed437a": "Elige qué proveedores de tareas aparecen en la página Tareas y en la barra lateral.",
"11faa2f7dd": "Fuentes de tareas",
"cfa34f4465": "Nombres de rama, refs base, atribución y Git AI Author.",
"cfa34f4465": "Nombres de rama, refs base y Git AI Author.",
"70100f94c7": "Git y control de código fuente",
"b07041697f": "Conecta GitHub, GitLab, Linear y servicios de alojamiento de código fuente.",
"c9ca101a3b": "Integraciones",
@@ -8267,16 +8259,8 @@
},
"git": {
"search": {
"61eab13403": "orca",
"1b93c1143c": "atribución",
"8461c908ae": "coautor",
"61f9f5d1fc": "coautor",
"af0a144bfb": "issue",
"6bdea421bb": "PR",
"16f53f7323": "gh",
"d088806071": "github",
"118c23484b": "Agrega la atribución de Orca a commits, PR e issues.",
"bc7d9f69ce": "Atribución de Orca",
"40f9b815fd": "presupuesto API",
"b7e52124c7": "límite de tasa",
"ead733645f": "glab",
+2 -18
View File
@@ -786,7 +786,7 @@
"a9fb10afca": "ターミナル",
"5235c215ca": "「タスク」ページとサイドバーに表示するタスクプロバイダーを選択します。",
"85f4fd7710": "タスクソース",
"ab4b21b58e": "ブランチの名前、基本参照、帰属、および Git AI 作成者。",
"ab4b21b58e": "ブランチの名前、基本参照、および Git AI 作成者。",
"09607cb0fe": "Git とソース管理",
"33a5e1d597": "GitHub、GitLab、Linear、およびソースホスティングサービスを接続します。",
"2b043783ef": "連携",
@@ -6015,14 +6015,6 @@
"808f9a726e": "プレフィックスは適用されません"
},
"GitPane": {
"d2eede4c54": "Orca の帰属をコミット、PR、Issue に追加します。",
"e02ea23a32": "Orca の帰属",
"e71ce09c42": "orca",
"b9b5771bb1": "帰属",
"b5f534717a": "共著",
"9838c921ed": "共著者",
"afada55042": "Issue",
"b4ef5428a7": "PR",
"895d3f70b8": "gh",
"32dca11189": "GitHub",
"c4f610d057": "現在の GitLab CLI REST レート制限ヘッダー (利用可能な場合)。",
@@ -6911,7 +6903,7 @@
"3de4bbb841": "ターミナル",
"dd72ed437a": "「タスク」ページとサイドバーに表示するタスクプロバイダーを選択します。",
"11faa2f7dd": "タスクソース",
"cfa34f4465": "ブランチの名前、基本参照、帰属、および Git AI 作成者。",
"cfa34f4465": "ブランチの名前、基本参照、および Git AI 作成者。",
"70100f94c7": "Git とソース管理",
"b07041697f": "GitHub、GitLab、Linear、およびソースホスティングサービスを接続します。",
"c9ca101a3b": "連携",
@@ -8289,16 +8281,8 @@
},
"git": {
"search": {
"61eab13403": "orca",
"1b93c1143c": "帰属",
"8461c908ae": "共著",
"61f9f5d1fc": "共著者",
"af0a144bfb": "Issue",
"6bdea421bb": "PR",
"16f53f7323": "gh",
"d088806071": "GitHub",
"118c23484b": "Orca の帰属をコミット、PR、Issue に追加します。",
"bc7d9f69ce": "Orca の帰属",
"40f9b815fd": "APIの予算",
"b7e52124c7": "レート制限",
"ead733645f": "glab",
+2 -18
View File
@@ -786,7 +786,7 @@
"a9fb10afca": "터미널",
"5235c215ca": "작업 페이지와 사이드바에 표시할 작업 제공자를 선택합니다.",
"85f4fd7710": "작업 소스",
"ab4b21b58e": "브랜치 이름 지정, 기본 참조, 속성 및 Git AI Author.",
"ab4b21b58e": "브랜치 이름 지정, 기본 참조 및 Git AI Author.",
"09607cb0fe": "Git 및 소스 제어",
"33a5e1d597": "GitHub, GitLab, Linear 및 소스 호스팅 서비스를 연결하세요.",
"2b043783ef": "연동",
@@ -5978,14 +5978,6 @@
"808f9a726e": "접두사가 적용되지 않습니다"
},
"GitPane": {
"d2eede4c54": "commits, PR 및 이슈에 Orca 속성을 추가합니다.",
"e02ea23a32": "Orca 표기",
"e71ce09c42": "orca",
"b9b5771bb1": "표기",
"b5f534717a": "공동 작성",
"9838c921ed": "공저자",
"afada55042": "이슈",
"b4ef5428a7": "PR",
"895d3f70b8": "gh",
"32dca11189": "GitHub",
"c4f610d057": "사용 가능한 경우 현재 GitLab CLI REST 속도 제한 헤더입니다.",
@@ -6874,7 +6866,7 @@
"3de4bbb841": "터미널",
"dd72ed437a": "작업 페이지와 사이드바에 표시할 작업 제공자를 선택합니다.",
"11faa2f7dd": "작업 소스",
"cfa34f4465": "브랜치 이름 지정, 기본 참조, 속성 및 Git AI Author.",
"cfa34f4465": "브랜치 이름 지정, 기본 참조 및 Git AI Author.",
"70100f94c7": "Git 및 소스 제어",
"b07041697f": "GitHub, GitLab, Linear 및 소스 호스팅 서비스를 연결하세요.",
"c9ca101a3b": "연동",
@@ -8252,16 +8244,8 @@
},
"git": {
"search": {
"61eab13403": "orca",
"1b93c1143c": "속성",
"8461c908ae": "공동 집필",
"61f9f5d1fc": "공저자",
"af0a144bfb": "이슈",
"6bdea421bb": "PR",
"16f53f7323": "gh",
"d088806071": "GitHub",
"118c23484b": "commits, PR 및 이슈에 Orca 속성을 추가합니다.",
"bc7d9f69ce": "Orca 속성",
"40f9b815fd": "API 예산",
"b7e52124c7": "속도 제한",
"ead733645f": "glab",
+2 -18
View File
@@ -786,7 +786,7 @@
"a9fb10afca": "终端",
"5235c215ca": "选择在“任务”页面和侧栏中显示的任务提供者。",
"85f4fd7710": "任务来源",
"ab4b21b58e": "分支命名、基础引用、署名和 Git AI Author。",
"ab4b21b58e": "分支命名、基础引用和 Git AI Author。",
"09607cb0fe": "Git 和源代码控制",
"33a5e1d597": "连接 GitHub、GitLab、Linear 和源托管服务。",
"2b043783ef": "集成",
@@ -5990,14 +5990,6 @@
"808f9a726e": "不会应用前缀"
},
"GitPane": {
"d2eede4c54": "将 Orca 署名添加到 commits、PR 和议题。",
"e02ea23a32": "Orca 署名",
"e71ce09c42": "orca",
"b9b5771bb1": "署名",
"b5f534717a": "合着的",
"9838c921ed": "共同作者",
"afada55042": "议题",
"b4ef5428a7": "PR",
"895d3f70b8": "gh",
"32dca11189": "github",
"c4f610d057": "当前 GitLab CLI REST 速率限制标头(如果可用)。",
@@ -6886,7 +6878,7 @@
"3de4bbb841": "终端",
"dd72ed437a": "选择在“任务”页面和侧栏中显示的任务提供者。",
"11faa2f7dd": "任务来源",
"cfa34f4465": "分支命名、基础引用、署名和 Git AI Author。",
"cfa34f4465": "分支命名、基础引用和 Git AI Author。",
"70100f94c7": "Git 和源代码控制",
"b07041697f": "连接 GitHub、GitLab、Linear 和源托管服务。",
"c9ca101a3b": "集成",
@@ -8264,16 +8256,8 @@
},
"git": {
"search": {
"61eab13403": "orca",
"1b93c1143c": "署名",
"8461c908ae": "合着的",
"61f9f5d1fc": "共同作者",
"af0a144bfb": "议题",
"6bdea421bb": "PR",
"16f53f7323": "gh",
"d088806071": "GitHub",
"118c23484b": "将 Orca 署名添加到 commits、PR 和议题。",
"bc7d9f69ce": "Orca 署名",
"40f9b815fd": "API预算",
"b7e52124c7": "速率限制",
"ead733645f": "光亮的",
@@ -641,8 +641,7 @@ describe('runtime git client', () => {
settings: {
activeRuntimeEnvironmentId: 'env-1',
commitMessageAi,
agentCmdOverrides,
enableGitHubAttribution: true
agentCmdOverrides
},
worktreeId: 'wt-1',
worktreePath: '/repo'
@@ -655,7 +654,6 @@ describe('runtime git client', () => {
worktree: 'id:wt-1',
commitMessageAi,
agentCmdOverrides,
enableGitHubAttribution: true,
commitMessageDiscoveryHostKey: 'runtime:env-1'
},
timeoutMs: 75_000
+2 -15
View File
@@ -49,12 +49,7 @@ export type RuntimePullRequestGenerationInput = {
}
type RuntimeGitSettings = Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> &
Partial<
Pick<
GlobalSettings,
'commitMessageAi' | 'sourceControlAi' | 'agentCmdOverrides' | 'enableGitHubAttribution'
>
>
Partial<Pick<GlobalSettings, 'commitMessageAi' | 'sourceControlAi' | 'agentCmdOverrides'>>
type RuntimeDiscoverCommitMessageModelsResult =
| {
@@ -96,12 +91,7 @@ export type RuntimeGeneratePullRequestFieldsOverrides = RuntimeGenerateCommitMes
function getRuntimeCommitMessageSettings(
settings: RuntimeGitSettings | null | undefined,
connectionId?: string
): Partial<
Pick<
GlobalSettings,
'commitMessageAi' | 'sourceControlAi' | 'agentCmdOverrides' | 'enableGitHubAttribution'
>
> & {
): Partial<Pick<GlobalSettings, 'commitMessageAi' | 'sourceControlAi' | 'agentCmdOverrides'>> & {
commitMessageDiscoveryHostKey?: string
} {
if (!settings) {
@@ -118,9 +108,6 @@ function getRuntimeCommitMessageSettings(
...(settings.agentCmdOverrides !== undefined
? { agentCmdOverrides: settings.agentCmdOverrides }
: {}),
...(settings.enableGitHubAttribution !== undefined
? { enableGitHubAttribution: settings.enableGitHubAttribution }
: {}),
commitMessageDiscoveryHostKey: getCommitMessageModelDiscoveryHostKeyForScope(scope)
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ Rules:
- First line: imperative mood, <= 72 chars, no trailing period.
- Optional body: blank line, then wrapped at 72 chars explaining WHY.
- Output ONLY the commit message - no preamble, no code fences, no quotes.
- Do not include "Co-authored-by" trailers - Orca appends them after generation when configured.
- Do not include "Co-authored-by" or other git trailers.
Staged diff:
\`\`\`diff
-1
View File
@@ -182,7 +182,6 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
autoRenameBranchFromWorkDefaultedOn: true,
branchPrefix: 'git-username',
branchPrefixCustom: '',
enableGitHubAttribution: false,
theme: 'system',
leftSidebarAppearanceMode: 'default',
leftSidebarTintColor: DEFAULT_LEFT_SIDEBAR_TINT_COLOR,
@@ -76,7 +76,6 @@ function makeSettings(): GlobalSettings {
localBaseRefSuggestionDismissed: false,
branchPrefix: 'none',
branchPrefixCustom: '',
enableGitHubAttribution: false,
theme: 'system',
appFontFamily: 'Geist',
editorAutoSave: false,
-6
View File
@@ -1,6 +0,0 @@
// Why: single source of truth for the commit trailer Orca appends when the
// "Orca Attribution" toggle (`enableGitHubAttribution`) is on. Used by both
// the terminal git/gh shim and the AI commit-message generator so the two
// code paths agree on the exact string.
export const ORCA_GIT_COMMIT_TRAILER = 'Co-authored-by: Orca <help@stably.ai>'
-1
View File
@@ -2799,7 +2799,6 @@ export type GlobalSettings = {
autoRenameBranchFromWorkDefaultedOn?: boolean
branchPrefix: BranchPrefixStrategy
branchPrefixCustom: string
enableGitHubAttribution: boolean
theme: 'system' | 'dark' | 'light'
/** Controls the left sidebar surface without changing terminal brightness. */
leftSidebarAppearanceMode: LeftSidebarAppearanceMode
-1
View File
@@ -80,7 +80,6 @@ function makeSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings {
localBaseRefSuggestionDismissed: false,
branchPrefix: 'none',
branchPrefixCustom: '',
enableGitHubAttribution: false,
theme: 'system',
appFontFamily: 'Geist',
editorAutoSave: false,