feat: integrate fork work onto upstream main (squashed)

Squash-merge of feat/autofix-pr-test (69 commits) onto upstream/main
with -X ours strategy (upstream as authoritative for content conflicts).

Key features brought in from fork:
- LocalMemoryRecall + VaultHttpFetch tools (end-to-end wired)
- /local-memory, /local-vault, /memory-stores, /skill-store interactive panels
- /agents-platform, /schedule, /vault command scaffolding
- /login: switch / replace / remove of workspace API key
- statusline refactor (built-in status row, /statusline as info command)
- autofix-pr command + workflow

Conflict resolutions (upstream-wins):
- 10 .js command stubs kept from upstream (alongside fork's .ts implementations)
- src/components/BuiltinStatusLine.tsx accepted upstream's deletion
  (fork's wire-up references in StatusLine.tsx will be cleaned up next)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
unraid
2026-05-08 16:47:29 +08:00
parent 73e54d4bbc
commit 8945f08708
233 changed files with 40597 additions and 341 deletions

View File

@@ -0,0 +1,45 @@
/**
* Shared mock for `node:child_process`.
*
* Usage:
* import { mock } from 'bun:test'
* import { childProcessMock, execFileMock, execFileSyncMock } from 'tests/mocks/childProcess'
* mock.module('node:child_process', () => childProcessMock)
*
* Call `execFileMock.mockImplementation(...)` or `execFileSyncMock.mockImplementation(...)`
* before each test that needs specific behavior.
*/
import { mock } from 'bun:test'
// execFile: node-style callback (cmd, args, opts?, callback)
export const execFileMock = mock(
(
_cmd: string,
_args: string[],
_optsOrCb?: unknown,
_cb?: (err: Error | null, stdout: string, stderr: string) => void,
) => {
const cb =
typeof _optsOrCb === 'function'
? (_optsOrCb as (
err: Error | null,
stdout: string,
stderr: string,
) => void)
: _cb
if (cb) cb(null, '', '')
return null
},
)
// execFileSync: synchronous (returns Buffer)
export const execFileSyncMock = mock(
(_cmd: string, _args: string[], _opts?: unknown): Buffer => {
return Buffer.from('')
},
)
export const childProcessMock = {
execFile: execFileMock,
execFileSync: execFileSyncMock,
}

91
tests/mocks/state.ts Normal file
View File

@@ -0,0 +1,91 @@
/**
* Shared partial mock for src/bootstrap/state.ts
*
* Covers the most commonly imported exports plus their transitive callers.
* Add exports here when new tests need them — never mock exports that don't exist.
*
* Usage:
* import { stateMock } from '../../../tests/mocks/state'
* mock.module('src/bootstrap/state.js', stateMock)
*/
export function stateMock() {
const noop = () => {}
return {
// Session identity
getSessionId: () => 'mock-session-id',
regenerateSessionId: noop,
getParentSessionId: () => undefined,
switchSession: noop,
onSessionSwitch: () => () => {},
// CWD / project
getOriginalCwd: () => '/mock/cwd',
getSessionProjectDir: () => null,
getProjectRoot: () => '/mock/project',
getCwdState: () => '/mock/cwd',
setCwdState: noop,
setOriginalCwd: noop,
setProjectRoot: noop,
// Direct-connect
getDirectConnectServerUrl: () => undefined,
setDirectConnectServerUrl: noop,
// Duration / cost accumulators
addToTotalDurationState: noop,
resetTotalDurationStateAndCost_FOR_TESTS_ONLY: noop,
addToTotalCostState: noop,
getTotalCostUSD: () => 0,
getTotalAPIDuration: () => 0,
getTotalDuration: () => 0,
getTotalAPIDurationWithoutRetries: () => 0,
getTotalToolDuration: () => 0,
addToToolDuration: noop,
// Turn stats
getTurnHookDurationMs: () => 0,
addToTurnHookDuration: noop,
resetTurnHookDuration: noop,
getTurnHookCount: () => 0,
getTurnToolDurationMs: () => 0,
resetTurnToolDuration: noop,
getTurnToolCount: () => 0,
getTurnClassifierDurationMs: () => 0,
addToTurnClassifierDuration: noop,
resetTurnClassifierDuration: noop,
getTurnClassifierCount: () => 0,
// Stats store
getStatsStore: () => ({}),
setStatsStore: noop,
// Interaction time
updateLastInteractionTime: noop,
flushInteractionTime: noop,
// Lines changed
addToTotalLinesChanged: noop,
getTotalLinesAdded: () => 0,
getTotalLinesRemoved: () => 0,
// Token counts
getTotalInputTokens: () => 0,
getTotalOutputTokens: () => 0,
getTotalCacheReadInputTokens: () => 0,
getTotalCacheCreationInputTokens: () => 0,
getTotalWebSearchRequests: () => 0,
getTurnOutputTokens: () => 0,
getCurrentTurnTokenBudget: () => null,
// API request state
setLastAPIRequest: noop,
getLastAPIRequest: () => null,
setLastAPIRequestMessages: noop,
getLastAPIRequestMessages: () => [],
// Various getters (add as needed)
getIsNonInteractiveSession: () => false,
getSdkAgentProgressSummariesEnabled: () => false,
addSlowOperation: noop,
}
}

View File

@@ -0,0 +1,52 @@
/**
* Shared minimal ToolUseContext stub for tool unit tests.
*
* Provides only the fields tools actually access in tests:
* - getAppState() returns a context with empty rule arrays for every source
* - toolUseId / parentMessageId / assistantMessageId / turnId can be
* overridden per test for budget tracking tests
*
* Usage:
* import { mockToolContext } from 'tests/mocks/toolContext'
* const ctx = mockToolContext({ toolUseId: 't1' })
*
* Per memory feedback "Mock dependency not subject" — this exists so each
* tool test file does not redefine the same partial stub.
*/
const emptyRules = {
user: [],
project: [],
local: [],
session: [],
cliArg: [],
}
export interface MockToolContextOptions {
toolUseId?: string
parentMessageId?: string
assistantMessageId?: string
turnId?: string
/** Override toolPermissionContext fields (e.g. mode, alwaysAllowRules). */
permissionOverrides?: Record<string, unknown>
}
export function mockToolContext(opts: MockToolContextOptions = {}): never {
return {
toolUseId: opts.toolUseId,
parentMessageId: opts.parentMessageId,
assistantMessageId: opts.assistantMessageId,
turnId: opts.turnId,
getAppState: () => ({
toolPermissionContext: {
mode: 'default',
additionalWorkingDirectories: new Set(),
alwaysAllowRules: { ...emptyRules },
alwaysDenyRules: { ...emptyRules },
alwaysAskRules: { ...emptyRules },
isBypassPermissionsModeAvailable: false,
...(opts.permissionOverrides ?? {}),
},
}),
} as never
}