mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
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:
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 })
|
||||
})
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>'
|
||||
@@ -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
|
||||
|
||||
@@ -80,7 +80,6 @@ function makeSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings {
|
||||
localBaseRefSuggestionDismissed: false,
|
||||
branchPrefix: 'none',
|
||||
branchPrefixCustom: '',
|
||||
enableGitHubAttribution: false,
|
||||
theme: 'system',
|
||||
appFontFamily: 'Geist',
|
||||
editorAutoSave: false,
|
||||
|
||||
Reference in New Issue
Block a user