Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9a44a64
fix: enforce runtime safety and cancellation
Aug 1, 2026
1bd5562
feat: read legacy session formats safely
Aug 1, 2026
502a16e
feat: distinguish model steps from user turns
Aug 1, 2026
e128e20
Merge branch 'codex/codex-alignment-plan' into codex/runtime-safety
Aug 1, 2026
0b64b7b
Merge branch 'codex/runtime-safety' into codex/session-compat
Aug 1, 2026
17cec62
Merge branch 'codex/session-compat' into codex/lifecycle-events
Aug 1, 2026
a123484
feat: add shared runtime host boundary
Aug 1, 2026
2e32697
feat: normalize sessions to canonical v1
Aug 1, 2026
38196c9
feat: define experimental runtime protocol
Aug 1, 2026
7a51ce0
docs: decide desktop runtime packaging
Aug 1, 2026
b3b7d82
feat: add experimental app server
Aug 1, 2026
bd151b8
chore: sync protocol workspace lockfile
Aug 1, 2026
519f8f0
Merge branch 'codex/desktop-runtime-adr' into codex/app-server
Aug 1, 2026
0ae2f5c
feat: package and supervise desktop app server
Aug 1, 2026
75d4b57
ci: prepare desktop sidecar placeholder
Aug 1, 2026
1367119
ci: stub desktop bundle resources for Rust checks
Aug 1, 2026
580f9c8
feat: add interactive app server protocol
Aug 1, 2026
033f7e6
feat: join protocol threads with canonical sessions
Aug 1, 2026
e838092
feat: move desktop runtime behind app server
Aug 1, 2026
10c7354
test: automate desktop protocol journey
Aug 1, 2026
a43c08e
ci: build workspace before desktop journey
Aug 1, 2026
27d1a82
refactor: share app server protocol client
Aug 1, 2026
b81da04
feat: move LSP behind app server
Aug 1, 2026
76a8528
feat: move VS Code behind app server
Aug 1, 2026
e80cbac
feat: add trust-aware config diagnostics
Aug 1, 2026
c0c7ed6
feat: surface config diagnostics in clients
Aug 1, 2026
f80bfe9
feat: compose app server runtime context
Aug 1, 2026
7d9a5d6
feat: compose MCP and plugin runtime resources
Aug 1, 2026
a23e8c3
feat: require exact project hook review
Aug 1, 2026
a7bcd40
feat: add redacted structured tracing
Aug 1, 2026
a60f084
feat: centralize workspace diff review
Aug 1, 2026
abb1de0
fix: use collision-resistant ipc ids
Aug 1, 2026
360a214
feat: persist structured review findings
Aug 1, 2026
a9dcdd3
feat: add canonical review action lifecycle
Aug 1, 2026
27b3d10
feat: add conflict-safe review revert
Aug 1, 2026
654829f
refactor: remove legacy desktop runtime facades
Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ jobs:
- name: Build
run: pnpm build

- name: Package VS Code extension
if: runner.os == 'Linux'
run: pnpm --filter deepcode package --out "${RUNNER_TEMP}/deepcode.vsix"

link-check:
name: Docs link check
runs-on: ubuntu-latest
Expand All @@ -80,6 +84,34 @@ jobs:
- name: Verify current documentation
run: node scripts/check-docs.mjs

desktop-preview:
name: Desktop protocol journey
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Chromium
run: pnpm --filter @deepcode/desktop exec playwright install --with-deps chromium
- name: Exercise the desktop protocol fixture
run: pnpm --filter @deepcode/desktop test:e2e
- name: Upload browser diagnostics
if: failure()
uses: actions/upload-artifact@v4
with:
name: desktop-playwright-report
path: |
apps/desktop/playwright-report
apps/desktop/test-results
if-no-files-found: ignore
retention-days: 7

desktop-rust:
name: Desktop Rust check + test
runs-on: macos-latest
Expand All @@ -88,6 +120,17 @@ jobs:
- uses: actions/checkout@v6
- name: Show Rust toolchain
run: rustc --version && cargo --version
# Tauri validates every externalBin path in its build script. This job
# only compiles/tests Rust and never executes or packages the sidecar, so
# use a target-correct placeholder instead of copying a 100+ MB runtime.
- name: Prepare Tauri sidecar placeholder
run: |
target="$(rustc -vV | sed -n 's/^host: //p')"
runtime="apps/desktop/src-tauri/binaries/deepcode-runtime-${target}"
mkdir -p "$(dirname "$runtime")"
touch "$runtime"
mkdir -p apps/server/dist-sidecar
touch apps/server/dist-sidecar/app-server.cjs
- name: Check and test Tauri backend
run: |
cargo check --manifest-path apps/desktop/src-tauri/Cargo.toml --locked
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ jobs:
- name: pnpm install
run: pnpm install --frozen-lockfile

- name: Prepare pinned Node sidecar runtime
env:
NODE_SIDECAR_VERSION: 22.23.1
NODE_SIDECAR_SHA256: ef28d8fab2c0e4314522d4bb1b7173270aa3937e93b92cb7de79c112ac1fa953
run: |
archive="node-v${NODE_SIDECAR_VERSION}-darwin-arm64.tar.xz"
curl --fail --location --retry 3 \
"https://nodejs.org/dist/v${NODE_SIDECAR_VERSION}/${archive}" \
--output "$RUNNER_TEMP/$archive"
echo "${NODE_SIDECAR_SHA256} $RUNNER_TEMP/$archive" | shasum -a 256 --check
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
echo "DEEPCODE_NODE_RUNTIME=$RUNNER_TEMP/node-v${NODE_SIDECAR_VERSION}-darwin-arm64/bin/node" >> "$GITHUB_ENV"

- name: Set version
run: |
cd apps/desktop
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ yarn-error.log
# Test outputs
coverage/
.nyc_output/
apps/desktop/playwright-report/
apps/desktop/test-results/

# Electron build outputs (pre-Tauri pivot — kept for historical artifacts)
apps/desktop/release/
Expand All @@ -39,6 +41,8 @@ apps/desktop/dist-electron/
# Tauri build outputs (Rust target dir is large)
apps/desktop/src-tauri/target/
apps/desktop/src-tauri/gen/
apps/desktop/src-tauri/binaries/
apps/server/dist-sidecar/
# Note: Cargo.lock IS committed (best practice for applications)

# Release artifacts — too large for git; CI uploads to GitHub Releases instead
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Mac 客户端(v1 即将发布):拖入 Applications → 首启完成 onboar
| [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md) | 整体开发方案 v0.5(1500+ 行 / §3 模块 / §6 里程碑) |
| [docs/VISUAL_DESIGN.html](docs/VISUAL_DESIGN.html) | 视觉设计 v0.4(11 屏 mockup) |
| [docs/security-model.md](docs/security-model.md) | 威胁模型 + 防御层 + 攻击向量测试 + 已知缺口 |
| [docs/design/session-format-v1.md](docs/design/session-format-v1.md) | 统一 session JSONL、旧格式迁移与 writer ownership |
| [docs/design/sandbox-plan-worktree.md](docs/design/sandbox-plan-worktree.md) | sandbox × plan mode × worktree 关系矩阵 |
| [docs/design/plugin-security.md](docs/design/plugin-security.md) | plugin 信任 ladder + sandbox 子进程 |
| [docs/design/effort-levels.md](docs/design/effort-levels.md) | 5 档 effort 到 DeepSeek API 参数映射 |
Expand All @@ -77,7 +78,7 @@ packages/
apps/
cli/ # deepcode-cli — Node.js CLI (npm publishable)
desktop/ # @deepcode/desktop — Tauri 2 + React Mac client
vscode/ # @deepcode/vscode — VS Code extension (v1.1)
vscode/ # deepcode — VS Code extension (app-server protocol client)
lsp/ # @deepcode/lsp — LSP bridge for Neovim/Emacs/Sublime (v1.1)
docs/
design/ # internal design docs
Expand Down
1 change: 1 addition & 0 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"start": "node ./dist/cli.js"
},
"dependencies": {
"@deepcode/app-server": "workspace:*",
"@deepcode/core": "workspace:*",
"@deepcode/shared-ui": "workspace:*"
},
Expand Down
55 changes: 52 additions & 3 deletions apps/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
// Spec: docs/DEVELOPMENT_PLAN.md §5 / §5a
// M2: onboarding + REPL + slash commands + settings + permissions matcher.

import { CredentialsStore, VERSION, redact } from '@deepcode/core';
import { CredentialsStore, VERSION, diagnoseSettings, redact } from '@deepcode/core';
import { runAppServer } from '@deepcode/app-server';
import { homedir } from 'node:os';
import { resolve } from 'node:path';
import { runDiagnosticsCommand } from './diagnostics-cmd.js';
import { runHeadless } from './headless.js';
import { runMcpCommand } from './mcp-cmd.js';
import { runOnboarding } from './onboarding.js';
import { helpText, parseArgs } from './parse-args.js';
import { startRepl } from './repl.js';
import { runCronCommand, runSchedulerRun } from './scheduler.js';
import { runTrustCommand } from './trust-cmd.js';
import { TrustStore } from './trust.js';
import { runPluginsCommand, runSkillsCommand } from './list-cmd.js';
import { runSetupToken } from './setup-token.js';
import { runCompletion } from './completion.js';
import { runHooksCommand } from './hooks-cmd.js';

async function main(): Promise<number> {
const args = parseArgs(process.argv.slice(2));
Expand Down Expand Up @@ -80,12 +84,36 @@ async function main(): Promise<number> {
errOutput: process.stderr,
});
}
if (args.positional[0] === 'app-server') {
await runAppServer({
input: process.stdin,
output: process.stdout,
home: process.env.DEEPCODE_HOME ?? resolve(homedir(), '.deepcode'),
});
return 0;
}
if (args.positional[0] === 'diagnostics') {
const home = process.env.DEEPCODE_HOME ?? resolve(homedir(), '.deepcode');
return runDiagnosticsCommand(args.positional.slice(1), {
home,
cwd: process.cwd(),
output: process.stdout,
errOutput: process.stderr,
});
}
if (args.positional[0] === 'trust') {
return runTrustCommand(args.positional.slice(1), {
cwd: process.cwd(),
output: process.stdout,
});
}
if (args.positional[0] === 'hooks') {
return runHooksCommand(args.positional.slice(1), {
cwd: process.cwd(),
output: process.stdout,
errOutput: process.stderr,
});
}
if (args.positional[0] === 'setup-token') {
return runSetupToken({ token: args.positional[1] });
}
Expand Down Expand Up @@ -175,11 +203,13 @@ async function main(): Promise<number> {
}

async function doctor(): Promise<number> {
const cwd = resolve(process.cwd());
process.stdout.write(`DeepCode v${VERSION}\n`);
process.stdout.write(`Node: ${process.version}\n`);
process.stdout.write(`Platform: ${process.platform} ${process.arch}\n`);
process.stdout.write(`Home: ${homedir()}\n`);
process.stdout.write(`CWD: ${resolve(process.cwd())}\n`);
process.stdout.write(`CWD: ${cwd}\n`);
let failed = false;
try {
const store = new CredentialsStore();
const creds = await store.load();
Expand All @@ -188,7 +218,26 @@ async function doctor(): Promise<number> {
} catch (err) {
process.stdout.write(`Credentials error: ${(err as Error).message}\n`);
}
return 0;
try {
const trustStatus = await new TrustStore().statusFor(cwd);
const config = await diagnoseSettings({ cwd, trustStatus });
process.stdout.write(`Configuration trust: ${config.trustStatus}\n`);
for (const layer of config.layers) {
const status = layer.present ? (layer.trusted ? 'active' : 'untrusted') : 'missing';
process.stdout.write(`Config ${layer.layer}: ${status} (${layer.path})\n`);
}
process.stdout.write(
`Config gated: ${config.gated.length ? config.gated.join(', ') : 'none'}\n`,
);
for (const issue of config.issues) {
process.stdout.write(`Config ${issue.severity}: [${issue.code}] ${issue.message}\n`);
if (issue.severity === 'error') failed = true;
}
} catch (error) {
process.stdout.write(`Configuration error: ${(error as Error).message}\n`);
failed = true;
}
return failed ? 1 : 0;
}

main().then(
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const SUBCOMMANDS = [
'doctor',
'upgrade',
'mcp',
'app-server',
'trust',
'plugins',
'skills',
Expand Down
52 changes: 52 additions & 0 deletions apps/cli/src/diagnostics-cmd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { PassThrough } from 'node:stream';

import { afterEach, describe, expect, it } from 'vitest';

import { runDiagnosticsCommand } from './diagnostics-cmd.js';

let root: string | undefined;

afterEach(async () => {
if (root) await rm(root, { recursive: true, force: true });
root = undefined;
});

function capture(stream: PassThrough): () => string {
let value = '';
stream.setEncoding('utf8');
stream.on('data', (chunk: string) => {
value += chunk;
});
return () => value;
}

describe('runDiagnosticsCommand', () => {
it('exports through the shared app-server sanitizer', async () => {
root = await mkdtemp(join(tmpdir(), 'deepcode-diagnostics-cli-'));
const cwd = join(root, 'private-workspace-name');
const output = new PassThrough();
const errOutput = new PassThrough();
const outputText = capture(output);

await expect(
runDiagnosticsCommand(['export'], { cwd, home: root, output, errOutput }),
).resolves.toBe(0);
const path = outputText().trim().replace('Wrote redacted diagnostic bundle: ', '');
const bundle = await readFile(path, 'utf8');
expect(bundle).not.toContain('private-workspace-name');
});

it('rejects unknown diagnostics actions', async () => {
root = await mkdtemp(join(tmpdir(), 'deepcode-diagnostics-cli-'));
const output = new PassThrough();
const errOutput = new PassThrough();
const errorText = capture(errOutput);
await expect(
runDiagnosticsCommand([], { cwd: root, home: root, output, errOutput }),
).resolves.toBe(2);
expect(errorText()).toContain('diagnostics export');
});
});
35 changes: 35 additions & 0 deletions apps/cli/src/diagnostics-cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { join, resolve } from 'node:path';
import type { Writable } from 'node:stream';

import { exportDiagnosticBundle } from '@deepcode/app-server/diagnostics';
import { diagnoseSettings } from '@deepcode/core';

import { TrustStore } from './trust.js';

export interface DiagnosticsCommandOptions {
cwd: string;
home: string;
output: Writable;
errOutput: Writable;
}

export async function runDiagnosticsCommand(
args: string[],
options: DiagnosticsCommandOptions,
): Promise<number> {
if (args[0] !== 'export') {
options.errOutput.write('Usage: deepcode diagnostics export\n');
return 2;
}
const cwd = resolve(options.cwd);
const trustStatus = await new TrustStore({ directory: options.home }).statusFor(cwd);
const config = await diagnoseSettings({ cwd, directory: options.home, trustStatus });
const result = await exportDiagnosticBundle({
home: options.home,
cwd,
config,
logPath: join(options.home, 'logs', 'app-server.ndjson'),
});
options.output.write(`Wrote redacted diagnostic bundle: ${result.path}\n`);
return 0;
}
Loading
Loading