Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
不许兼容、兜底旧代码
每次执行完以后都要补充测试文件确保实际行为与预期相符
所有的测试文件只能写在现有的test文件夹下
修改过程中发现错误,如果是本次范围就修改,否则要在最后指出
当前的设计不能假设单会话的,而应该假设多会话场景
修改过程中发现错误,如果是本次范围就修改(包括测试),否则要在最后指出
在用户的最新的一条消息除非有显式命令(执行方案、修改代码等)要求修改代码,否则绝对不改代码,之前要求修改的指令全部不算数,别再根据之前的上下文或者当前不确定的指令猜是不是要直接修改代码了
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
不允许假设“这是未来需要扩展的”,所以现在就不做,应该贴合用户的实际要求
禁止局部短视实现:不允许仅为了“当前调用能跑通”而写死临时逻辑、硬编码、破坏原有接口契约、或绕过已有模块
不允许总是有阶段性计划,分阶段完成很容易导致过程产生一堆没用的死代码
不许兼容、兜底旧代码
每次执行完以后都要补充测试文件确保实际行为与预期相符
所有的测试文件只能写在现有的test文件夹下
修改过程中发现错误,如果是本次范围就修改,否则要在最后指出
当前的设计不能假设单会话的,而应该假设多会话场景
修改过程中发现错误,如果是本次范围就修改(包括测试),否则要在最后指出
在用户的最新的一条消息除非有显式命令(执行方案、修改代码等)要求修改代码,否则绝对不改代码,之前要求修改的指令全部不算数,别再根据之前的上下文或者当前不确定的指令猜是不是要直接修改代码了
1 change: 0 additions & 1 deletion packages/codingcode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"./client/direct-clients": "./src/client/direct/index.ts",
"./checkpoint/checkpoint-service": "./src/checkpoint/checkpoint-service.ts",
"./checkpoint/shadow-git": "./src/checkpoint/shadow-git.ts",
"./checkpoint/ledger": "./src/checkpoint/ledger.ts",
"./checkpoint/bootstrap": "./src/checkpoint/bootstrap.ts",
"./subagent/registry": "./src/subagent/registry.ts",
"./subagent/loader": "./src/subagent/loader.ts",
Expand Down
6 changes: 4 additions & 2 deletions packages/codingcode/src/agent/agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Effect } from 'effect';
import { z } from 'zod';
import { appendFileSync } from 'fs';

Check warning on line 3 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'appendFileSync' is defined but never used. Allowed unused vars must match /^_/u
import type { Message, ToolCall } from '../core/types.js';
import { AgentError } from '../core/error.js';
import { Result } from '../core/result.js';
Expand All @@ -20,7 +20,7 @@
import { McpService } from '../mcp/index.js';
import { loadMemoryForPrompt, flushSessionToMemory } from '../memory/index.js';
import { createLogger } from '@codingcode/infra/logger';
import type { AgentProfile } from '../subagent/registry.js';

Check warning on line 23 in packages/codingcode/src/agent/agent.ts

View workflow job for this annotation

GitHub Actions / lint

'AgentProfile' is defined but never used. Allowed unused vars must match /^_/u
import { resolveSubagentEnabled, resolveAgentDisabled } from '../subagent/registry.js';
import type { ToolVisibilityPolicy } from '../tools/types.js';
import { ProjectRuntimeService } from '../runtime/project-runtime.js';
Expand Down Expand Up @@ -306,6 +306,7 @@

// Check abort signal
if (opts.abortSignal?.aborted) {
checkpoint.snapshotFinal(projectPath, state.sessionId, state.currentTurnId);
yield { _tag: 'Error', error: new AgentError('AGENT_ABORTED', 'cancelled') };
await Effect.runPromise(
hooks.emit('agent.turn.end', {
Expand Down Expand Up @@ -465,7 +466,7 @@
if (stopContinuations >= maxStopContinuations) {
yield {
_tag: 'Error',
error: new AgentError('STOP_LOOP', 'max stop continuations exceeded'),
error: new AgentError('AGENT_LOOP_DETECTED', 'max stop continuations exceeded'),
};
await Effect.runPromise(
hooks.emit('agent.turn.end', {
Expand All @@ -477,7 +478,7 @@
flushSessionToMemory(state.sessionId, llm).catch((e) =>
logger.error('memory flush failed:', e)
);
return Result.err(new AgentError('STOP_LOOP', 'max stop continuations exceeded'));
return Result.err(new AgentError('AGENT_LOOP_DETECTED', 'max stop continuations exceeded'));
}
stopContinuations++;
const injection = stopDecision.injection ?? '(continue)';
Expand Down Expand Up @@ -557,6 +558,7 @@

// If abort fired during tool execution, terminate immediately
if (opts.abortSignal?.aborted) {
checkpoint.snapshotFinal(projectPath, state.sessionId, state.currentTurnId);
yield { _tag: 'Error', error: new AgentError('AGENT_ABORTED', 'cancelled') };
await Effect.runPromise(
hooks.emit('agent.turn.end', {
Expand Down
39 changes: 0 additions & 39 deletions packages/codingcode/src/agent/build-tools.ts

This file was deleted.

26 changes: 13 additions & 13 deletions packages/codingcode/src/approval/async-confirm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ interface PendingEntry {
sessionId: string;
}

const pending = new Map<string, PendingEntry>();
const emitters = new Map<
const pendingConfirmations = new Map<string, PendingEntry>();
const approvalEmitters = new Map<
string,
(id: string, tool: string, args: Record<string, unknown>) => void
>();
Expand All @@ -16,30 +16,30 @@ export function registerEmitter(
sessionId: string,
fn: (id: string, tool: string, args: Record<string, unknown>) => void
): void {
emitters.set(sessionId, fn);
approvalEmitters.set(sessionId, fn);
}

export function delegateEmitter(childSessionId: string, parentSessionId: string): void {
const parentFn = emitters.get(parentSessionId);
const parentFn = approvalEmitters.get(parentSessionId);
if (parentFn) {
emitters.set(childSessionId, parentFn);
approvalEmitters.set(childSessionId, parentFn);
}
}

export function unregisterEmitter(sessionId: string): void {
emitters.delete(sessionId);
approvalEmitters.delete(sessionId);
}

export function hasEmitter(sessionId: string): boolean {
return emitters.has(sessionId);
return approvalEmitters.has(sessionId);
}

export class ApprovalWaitService extends Effect.Service<ApprovalWaitService>()('ApprovalWait', {
effect: Effect.succeed({
waitForConfirm: (id: string, sessionId: string): Effect.Effect<ConfirmResult> =>
Effect.gen(function* () {
const d = yield* Deferred.make<ConfirmResult, never>();
pending.set(id, { deferred: d, sessionId });
pendingConfirmations.set(id, { deferred: d, sessionId });
return yield* Deferred.await(d);
}),

Expand All @@ -49,21 +49,21 @@ export class ApprovalWaitService extends Effect.Service<ApprovalWaitService>()('
result: ConfirmResult
): Effect.Effect<boolean> =>
Effect.sync(() => {
const entry = pending.get(id);
const entry = pendingConfirmations.get(id);
if (!entry) return false;
pending.delete(id);
pendingConfirmations.delete(id);
Deferred.unsafeDone(entry.deferred, Effect.succeed(result));
return true;
}),

getPending: (sessionId?: string): Effect.Effect<string[]> =>
Effect.sync(() => {
if (sessionId) {
return Array.from(pending.entries())
return Array.from(pendingConfirmations.entries())
.filter(([_, e]) => e.sessionId === sessionId)
.map(([id]) => id);
}
return Array.from(pending.keys());
return Array.from(pendingConfirmations.keys());
}),

emitApprovalRequest: (
Expand All @@ -73,7 +73,7 @@ export class ApprovalWaitService extends Effect.Service<ApprovalWaitService>()('
args: Record<string, unknown>
): Effect.Effect<void> =>
Effect.sync(() => {
emitters.get(sessionId)?.(id, tool, args);
approvalEmitters.get(sessionId)?.(id, tool, args);
}),
}),
}) {}
4 changes: 2 additions & 2 deletions packages/codingcode/src/approval/confirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export function userConfirmAsync(
args: Record<string, unknown>,
waitSvc: ApprovalWaitService,
sessionId: string,
callId?: string
callId: string
): Effect.Effect<ConfirmResult> {
return Effect.gen(function* () {
const id = callId!;
const id = callId;

yield* waitSvc.emitApprovalRequest(sessionId, id, tool, args);

Expand Down
8 changes: 4 additions & 4 deletions packages/codingcode/src/approval/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Effect } from 'effect';
import { HookService } from '../hooks/registry.js';
import type { PermissionMode, PermissionRule, ApprovalDecision } from './types.js';
import { createRuleEngine, type RuleEngine } from './rule-engine.js';
import { DEFAULT_DENY_RULES, READONLY_TOOL_NAMES, DESTRUCTIVE_TOOL_NAMES } from './presets.js';
import { DEFAULT_DENY_RULES, READONLY_TOOL_NAMES, DANGEROUS_TOOL_NAMES } from './presets.js';
import { runPipeline, type PipelineHooks } from './pipeline.js';
import { ApprovalWaitService, hasEmitter } from './async-confirm.js';

Expand All @@ -22,8 +22,8 @@ export class ApprovalService extends Effect.Service<ApprovalService>()('Approval
const hooks = yield* HookService;
const approvalWait = yield* ApprovalWaitService;
const ruleEngine: RuleEngine = createRuleEngine(DEFAULT_DENY_RULES);
const destructiveTools = new Set(DANGEROUS_TOOL_NAMES);
const readonlyTools = new Set(READONLY_TOOL_NAMES);
const destructiveTools = new Set(DESTRUCTIVE_TOOL_NAMES);

function buildPipelineHooks(): PipelineHooks {
return {
Expand Down Expand Up @@ -100,7 +100,7 @@ export class ApprovalService extends Effect.Service<ApprovalService>()('Approval
}
}
if (opts?.readonly) {
for (const toolName of DESTRUCTIVE_TOOL_NAMES) {
for (const toolName of DANGEROUS_TOOL_NAMES) {
nextEngine.addRule({
id: `readonly-${toolName}`,
action: 'deny' as const,
Expand Down Expand Up @@ -176,7 +176,7 @@ export class ApprovalService extends Effect.Service<ApprovalService>()('Approval
}
}
if (opts?.readonly) {
const denyRules: PermissionRule[] = DESTRUCTIVE_TOOL_NAMES.map((toolName) => ({
const denyRules: PermissionRule[] = DANGEROUS_TOOL_NAMES.map((toolName) => ({
id: `readonly-${toolName}`,
action: 'deny' as const,
toolPattern: toolName,
Expand Down
16 changes: 8 additions & 8 deletions packages/codingcode/src/approval/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
const result = opts.ruleEngine.evaluate(request.tool, request.input);
if (result) {
layers.push(LAYER_NAMES[0]);
const final = yield* layer6Audit(request, result, layers, opts);
const final = yield* recordAuditAndReturn(request, result, layers, opts);
return final;
}
}
Expand All @@ -74,7 +74,7 @@
source: 'readonly-whitelist',
};
layers.push(LAYER_NAMES[1]);
const final = yield* layer6Audit(request, result, layers, opts);
const final = yield* recordAuditAndReturn(request, result, layers, opts);
return final;
}
}
Expand All @@ -89,7 +89,7 @@
);
if (modeResult) {
layers.push(LAYER_NAMES[2]);
const final = yield* layer6Audit(request, modeResult, layers, opts);
const final = yield* recordAuditAndReturn(request, modeResult, layers, opts);
return final;
}
}
Expand All @@ -108,12 +108,12 @@
reason: hookResult.reason ?? 'Denied by PreToolUse hook',
source: 'hook',
};
const final = yield* layer6Audit(request, result, layers, opts);
const final = yield* recordAuditAndReturn(request, result, layers, opts);
return final;
}
if (hookResult.decision === 'allow') {
const result: ApprovalDecision = { type: 'allow', source: 'hook' };
const final = yield* layer6Audit(request, result, layers, opts);
const final = yield* recordAuditAndReturn(request, result, layers, opts);
return final;
}
// 'ask' or no decision → continue to user confirmation
Expand All @@ -133,7 +133,7 @@
reason: 'Approval required but no UI available',
source: 'system',
};
const final = yield* layer6Audit(request, result, layers, opts);
const final = yield* recordAuditAndReturn(request, result, layers, opts);
return final;
}

Expand All @@ -142,7 +142,7 @@
request.input,
opts.asyncConfirmService,
opts.sessionId,
opts.callId

Check failure on line 145 in packages/codingcode/src/approval/pipeline.ts

View workflow job for this annotation

GitHub Actions / typecheck

Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
);

let result: ApprovalDecision;
Expand All @@ -163,7 +163,7 @@
break;
}

const final = yield* layer6Audit(request, result, layers, opts);
const final = yield* recordAuditAndReturn(request, result, layers, opts);
return final;
}
});
Expand Down Expand Up @@ -204,7 +204,7 @@
}
}

function layer6Audit(
function recordAuditAndReturn(
request: ToolCallRequest,
decision: ApprovalDecision,
passedLayers: string[],
Expand Down
2 changes: 1 addition & 1 deletion packages/codingcode/src/approval/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,4 @@ export const READONLY_TOOL_NAMES: string[] = [
'todo_write',
];

export const DESTRUCTIVE_TOOL_NAMES: string[] = ['execute_command'];
export const DANGEROUS_TOOL_NAMES: string[] = ['execute_command'];
Loading
Loading