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
10 changes: 4 additions & 6 deletions docs/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Coding Code 采用两层压缩策略,在不同阈值下自动触发:
| 触发阈值 | `promptEstimate > modelMaxTokens * 0.9` | prompt 估算超过模型最大 token 90% 时触发 |
| 保留最近 turn | 1 | 保留最近 1 个 turn 不压缩 |
| 压缩方式 | 调用 LLM 生成摘要 | 输出 `<summary>...</summary>` 块 |
| 增量压缩 | 是 | 找到已有 SummaryEvent,只压缩 `lastSummarizedTurnId` 之后的事件 |
| 增量压缩 | 是 | 找到已有 SummaryEvent,只压缩 `endTurnId` 之后的事件 |
| 失败追踪 | 连续 3 次失败后停止 | 24 小时 TTL 后重置 |

---
Expand Down Expand Up @@ -90,17 +90,15 @@ interface CompactEvent {
uuid: string;
startTurnId: number;
endTurnId: number;
timestamp: string;
}

// LLM 压缩摘要事件
interface SummaryEvent {
type: 'summary';
uuid: string;
replaces: string[]; // 被替换的事件 UUID 列表
summaryText: string; // 摘要文本
lastSummarizedTurnId: number; // 最后压缩到的 turn ID
timestamp: string;
startTurnId: number; // 摘要覆盖的起始 turn ID
endTurnId: number; // 摘要覆盖的结束 turn ID
summaryText: string; // 摘要文本
}
```

Expand Down
1 change: 0 additions & 1 deletion packages/codingcode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"./core/result": "./src/core/result.ts",
"./core/types": "./src/core/types.ts",
"./context/context": "./src/context/context.ts",
"./context/config": "./src/context/config.ts",
"./hooks/registry": "./src/hooks/registry.ts",
"./tools/executor": "./src/tools/executor.ts",
"./tools/tool-search-service": "./src/tools/tool-search-service.ts",
Expand Down
62 changes: 24 additions & 38 deletions packages/codingcode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import { buildSystemPrompt } from './prompt.js';
import type { AgentEvent, RunStreamOptions } from './types.js';
import { resolveConfig } from './config.js';
import { getContextConfig } from '../context/config.js';
import { TodoService } from './todo.js';
import { HookService } from '../hooks/registry.js';
import { SkillService } from '../skills/service.js';
Expand Down Expand Up @@ -128,12 +127,12 @@
const hooks = yield* HookService;
const mcp = yield* McpService;
const checkpoint = yield* CheckpointService;
const approval = yield* ApprovalService;

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

View workflow job for this annotation

GitHub Actions / lint

'approval' is assigned a value but never used. Allowed unused vars must match /^_/u
const skills = yield* SkillService;
const runtime = yield* ProjectRuntimeService;
const todo = yield* TodoService;

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

View workflow job for this annotation

GitHub Actions / lint

'todo' is assigned a value but never used. Allowed unused vars must match /^_/u
const rules = yield* RulesService;
const context = yield* ContextService;

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

View workflow job for this annotation

GitHub Actions / lint

'context' is assigned a value but never used. Allowed unused vars must match /^_/u
const memory = yield* MemoryService;
const factory = yield* LLMFactoryService;

Expand All @@ -141,7 +140,10 @@
yield* runtime.prepareProject(normalizedCwd);
yield* skills.evictProject(normalizedCwd);

const state = yield* session.create(normalizedCwd, llm.modelInfo.model, sessionId);
const state = sessionId
? yield* session.load(normalizedCwd, sessionId)
: yield* session.create(normalizedCwd, llm.modelInfo.model);
state.model = llm.modelInfo.model;
state.memorySnapshot = memory.loadMemoryForPrompt(state.cwd);
const sid = state.sessionId;

Expand Down Expand Up @@ -251,18 +253,19 @@
const memorySection = memoryBlock ? `## Session Memory\n\n${memoryBlock}` : '';
const system = [basePrompt, memorySection].filter(Boolean).join('\n\n');

const config = getContextConfig();
const maxOverflowRetries = REACTIVE_COMPACT_MAX_RETRIES;
const model = state.sessionMeta?.model ?? 'unknown';
const effectiveMaxSteps = opts.maxStepsOverride ?? maxSteps;

let stopContinuations = 0;
const effectiveMaxStopContinuations = opts.maxStopContinuations ?? maxStopContinuations;

let messages: Message[] = [];

for (let attempt = 0; attempt <= maxOverflowRetries; attempt++) {
const { messages } = yield* Effect.sync(() =>
context.assemblePayload(state.sessionId, state.projectPath, config, llm.modelInfo.maxTokens)
const payload = yield* Effect.sync(() =>
context.assemblePayload(state.sessionId, state.projectPath, llm.modelInfo.maxTokens)
);
messages = payload.messages;

let lastResult: Result<string, AgentError> | null = null;
let overflow = false;
Expand Down Expand Up @@ -304,31 +307,20 @@
state.projectPath,
messages,
llm.modelInfo.maxTokens,
config,
llm
),
catch: (e) => new AgentError('LLM_FAILED', String(e)),
});
if (compressResult.didCompress) {
if (compressResult.didCompress && compressResult.messages) {
yield* q.offer({
_tag: 'ReactiveCompact',
attempt: 1,
released: compressResult.released,
promptEstimate: compressResult.promptEstimate,
});

const rebuilt = yield* Effect.sync(() =>
context.assemblePayload(
state.sessionId,
state.projectPath,
config,
llm.modelInfo.maxTokens
)
);
messages.length = 0;
messages.push(...rebuilt.messages);
messages = compressResult.messages;
state.usage = undefined;
state.promptEstimate = rebuilt.promptEstimate;
}

const llmMessages = [...messages];
Expand Down Expand Up @@ -364,15 +356,15 @@
context.compactWithLLM(
state.sessionId,
state.projectPath,
config,
null,
undefined,
undefined,
undefined,
llm.modelInfo.maxTokens
llm.modelInfo.maxTokens,
llm,
undefined
),
catch: (e) => new AgentError('LLM_FAILED', String(e)),
});
if (compressResult.didCompress && compressResult.messages) {
messages = compressResult.messages;
}
yield* q.offer({
_tag: 'ReactiveCompact',
attempt: attempt + 1,
Expand Down Expand Up @@ -411,7 +403,7 @@

if (!toolCalls || toolCalls.length === 0) {
if (session) {
yield* session.recordAssistant(state, resp.content, toolCalls || [], model, resp.usage);
yield* session.recordAssistant(state, resp.content, toolCalls || [], resp.usage);
}
const stopDecision = yield* hooks.emitDecision('agent.turn.stop', {
sessionId,
Expand All @@ -431,7 +423,7 @@
status: 'error',
});
memory
.flushSessionToMemory(state.sessionId, llm)
.flushSessionToMemory(state.sessionId, llm, state.cwd)
.catch((e) => logger.error('memory flush failed:', e));
return Result.err(
new AgentError('AGENT_LOOP_DETECTED', 'max stop continuations exceeded')
Expand Down Expand Up @@ -467,13 +459,7 @@
}
}

const record = yield* session.recordAssistant(
state,
resp.content,
toolCalls!,
model,
resp.usage
);
const record = yield* session.recordAssistant(state, resp.content, toolCalls!, resp.usage);

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

View workflow job for this annotation

GitHub Actions / lint

'record' is assigned a value but never used. Allowed unused vars must match /^_/u
const allResults = yield* executor.executeBatch(toolCalls, state.sessionId, {
turnId: state.currentTurnId,
projectPath,
Expand All @@ -485,7 +471,7 @@
let todoPrinted = false;
for (const r of allResults) {
const resultOut = r.type === 'denied' ? '' : r.output;
yield* session.recordToolResult(state, record.uuid, r.name, r.id, resultOut);
yield* session.recordToolResult(state, r.name, r.id, resultOut);
if (r.type === 'denied') {
yield* q.offer({ _tag: 'ToolDenied', id: r.id, name: r.name, reason: r.reason });
} else {
Expand Down Expand Up @@ -517,7 +503,7 @@
yield* checkpoint.snapshotFinal(projectPath, state.sessionId, state.currentTurnId);

memory
.flushSessionToMemory(state.sessionId, llm)
.flushSessionToMemory(state.sessionId, llm, state.cwd)
.catch((e) => logger.error('memory flush failed:', e));

if (lastResult) return lastResult;
Expand All @@ -538,7 +524,7 @@
status: 'maxSteps',
});
memory
.flushSessionToMemory(state.sessionId, llm)
.flushSessionToMemory(state.sessionId, llm, state.cwd)
.catch((e) => logger.error('memory flush failed:', e));
return Result.err(AgentError.maxStepsReached(effectiveMaxSteps));
}).pipe(
Expand All @@ -564,7 +550,7 @@
yield* cp.snapshotFinal(projectPath, sessionId, state.currentTurnId).pipe(Effect.ignore);
const mem = yield* MemoryService;
mem
.flushSessionToMemory(state.sessionId, llm)
.flushSessionToMemory(state.sessionId, llm, state.cwd)
.catch((e) => logger.error('memory flush failed:', e));
})
)
Expand Down
2 changes: 1 addition & 1 deletion packages/codingcode/src/agent/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ToolCall } from '../core/types.js';
import type { AgentError } from '../core/error.js';
import type { SessionStoreState } from '../session/store.js';
import type { SessionStoreState } from '../session/types.js';
import type { LLMClient } from '../llm/client.js';
import type { ToolDefinition, ToolVisibilityPolicy } from '../tools/types.js';
import type { AgentProfile } from '../subagent/types.js';
Expand Down
3 changes: 1 addition & 2 deletions packages/codingcode/src/client/direct/agent-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { sendMessage } from '../../agent/agent.js';
import { ApprovalWaitService } from '../../approval/async-confirm.js';
import { parseApprovalResponse } from '../../approval/response.js';
import { ContextService } from '../../context/service.js';
import { getContextConfig } from '../../context/config.js';
import type { StreamChunk } from '../types.js';
import { agentEventToStreamChunk } from '../direct.js';
import type { AppRuntime } from '../../layer.js';
Expand Down Expand Up @@ -110,7 +109,7 @@ export function createDirectAgentClient(llm: LLMClient, rt: AppRuntime): AgentRu
Effect.gen(function* () {
const context = yield* ContextService;
return yield* Effect.promise(() =>
context.compactWithLLM(sessionId, cwd, getContextConfig(), null)
context.compactWithLLM(sessionId, cwd, llm.modelInfo.maxTokens, null)
);
})
);
Expand Down
51 changes: 21 additions & 30 deletions packages/codingcode/src/client/direct/sessions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Effect } from 'effect';
import { Effect } from 'effect';
import { SessionService } from '../../session/store.js';
import { WorkspaceService } from '../../core/workspace.js';
import { deleteSession } from '../../session/file-ops.js';
import type { PermissionMode } from '../../approval/types.js';
import type {
Expand All @@ -20,10 +19,15 @@ export interface SessionClient {
}): Promise<{ sessionId: string }>;
resumeSession(input: { sessionId: string; cwd: string }): Promise<SessionEvent[]>;
listSessions(input: { cwd: string }): Promise<SessionIndex[]>;
getSessionHistory(input: { sessionId: string }): Promise<SessionEvent[]>;
deleteSession(input: { sessionId: string }): Promise<void>;
getSessionPermissionMode(input: { sessionId: string }): Promise<PermissionMode>;
setSessionPermissionMode(input: { sessionId: string; mode: PermissionMode }): Promise<void>;
getSessionHistory(input: { sessionId: string; cwd: string }): Promise<SessionEvent[]>;

deleteSession(input: { sessionId: string; cwd: string }): Promise<void>;
getSessionPermissionMode(input: { sessionId: string; cwd: string }): Promise<PermissionMode>;
setSessionPermissionMode(input: {
sessionId: string;
cwd: string;
mode: PermissionMode;
}): Promise<void>;

getCheckpointDiff(input: {
sessionId: string;
Expand Down Expand Up @@ -69,15 +73,6 @@ export interface SessionClient {
}): Promise<{ sessionId: string; turns: SessionEvent[] }>;
}

function getWorkspaceCwd(rt: AppRuntime): Promise<string> {
return rt.runPromise(
Effect.gen(function* () {
const ws = yield* WorkspaceService;
return ws.getWorkspaceCwd();
})
);
}

export function createDirectSessionClient(rt: AppRuntime): SessionClient {
return {
async createSession({ cwd }) {
Expand All @@ -94,7 +89,7 @@ export function createDirectSessionClient(rt: AppRuntime): SessionClient {
return rt.runPromise(
Effect.gen(function* () {
const session = yield* SessionService;
const state = yield* session.create(cwd, 'unknown', sessionId);
const state = yield* session.load(cwd, sessionId);
return yield* session.readHistory(state);
})
);
Expand All @@ -109,39 +104,36 @@ export function createDirectSessionClient(rt: AppRuntime): SessionClient {
);
},

async getSessionHistory({ sessionId }) {
const cwd = await getWorkspaceCwd(rt);
async getSessionHistory({ sessionId, cwd }) {
return rt.runPromise(
Effect.gen(function* () {
const session = yield* SessionService;
const state = yield* session.create(cwd, 'unknown', sessionId);
const state = yield* session.load(cwd, sessionId);
return yield* session.readHistory(state);
})
);
},

async deleteSession({ sessionId }) {
deleteSession(sessionId);
async deleteSession({ sessionId, cwd }) {
deleteSession(sessionId, cwd);
},

async getSessionPermissionMode({ sessionId }): Promise<PermissionMode> {
const cwd = await getWorkspaceCwd(rt);
async getSessionPermissionMode({ sessionId, cwd }): Promise<PermissionMode> {
const mode = await rt.runPromise(
Effect.gen(function* () {
const session = yield* SessionService;
const state = yield* session.create(cwd, 'unknown', sessionId);
const state = yield* session.load(cwd, sessionId);
return yield* session.getPermissionMode(state);
})
);
return mode as PermissionMode;
},

async setSessionPermissionMode({ sessionId, mode }) {
const cwd = await getWorkspaceCwd(rt);
async setSessionPermissionMode({ sessionId, cwd, mode }) {
return rt.runPromise(
Effect.gen(function* () {
const session = yield* SessionService;
const state = yield* session.create(cwd, 'unknown', sessionId);
const state = yield* session.load(cwd, sessionId);
yield* session.setPermissionMode(state, mode);
})
);
Expand Down Expand Up @@ -221,12 +213,11 @@ export function createDirectSessionClient(rt: AppRuntime): SessionClient {
code: { canUndoLast: false, lastEntry: null, revertedFiles: [], lastEntryId: null },
};
},
async forkSession({ sessionId, atTurnId }) {
const cwd = await getWorkspaceCwd(rt);
async forkSession({ sessionId, cwd, atTurnId }) {
const newSessionId = await rt.runPromise(
Effect.gen(function* () {
const session = yield* SessionService;
const state = yield* session.create(cwd, 'unknown', sessionId);
const state = yield* session.load(cwd, sessionId);
return yield* session.forkSession(state, atTurnId ?? 0);
})
);
Expand Down
Loading
Loading