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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import cn.lypi.contracts.tool.ToolResult;
import cn.lypi.contracts.tool.ToolUseRequest;
import cn.lypi.contracts.runtime.ToolRuntimeInvocation;
import cn.lypi.contracts.session.ShellState;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Clock;
import java.time.Instant;
Expand Down Expand Up @@ -139,6 +142,13 @@ private TurnState executeWithTurnId(TurnRequest request, String turnId) {
contextLeafId = appendNewMessage(request.sessionId(), pendingToolMessage);
newMessages.add(pendingToolMessage);
}
Optional<Path> nextCwd = toolResult.stateDelta()
.flatMap(delta -> validShellCwd(delta.cwd()));
if (nextCwd.isPresent()) {
contextLeafId = ports.sessionManager()
.appendShellStateChange(ShellState.of(nextCwd.orElseThrow()))
.leafId();
}
}
}
if (request.abortSignal().aborted()) {
Expand Down Expand Up @@ -224,7 +234,7 @@ private ContextSnapshot buildContext(
ContextBuildRequest contextBuildRequest = new ContextBuildRequest(
request.sessionId(),
leafEntryId,
// NOTE: lypi-resource 负责从 cwd 探索 project root 和资源层级;agent-core 只传入启动层确定的 cwd 起点。
// Resource scope stays at the session root; shell cwd is tool-runtime state only.
ports.cwd(),
true,
skillMentions
Expand Down Expand Up @@ -478,30 +488,51 @@ private List<ToolResult<?>> executeTools(
TurnRequest turnRequest
) {
ensureToolRuntimeCwdMatches();
List<ToolResult<?>> results;
try {
results = ports.toolRuntime().execute(
toolRequests,
context,
new ToolRuntimeInvocation(
sessionId,
turnId,
parentEntryId,
turnRequest.abortSignal(),
turnRequest.steeringMessages()
)
List<ToolResult<?>> results = ports.toolRuntime().execute(
toolRequests,
context,
new ToolRuntimeInvocation(
sessionId,
turnId,
parentEntryId,
turnRequest.abortSignal(),
turnRequest.steeringMessages(),
currentShellCwd()
)
);
if (results.size() != toolRequests.size()) {
throw new IllegalStateException(
"Tool runtime returned " + results.size() + " result(s) for " + toolRequests.size() + " request(s)"
);
if (results.size() != toolRequests.size()) {
throw new IllegalStateException(
"Tool runtime returned " + results.size() + " result(s) for " + toolRequests.size() + " request(s)"
);
}
} catch (RuntimeException failure) {
throw failure;
}
return results;
}

private Path currentShellCwd() {
return validShellCwd(ports.sessionManager().shellState().cwd()).orElse(ports.cwd());
}

private Optional<Path> validShellCwd(Path candidate) {
if (candidate == null) {
return Optional.empty();
}
Path workspaceRoot = ports.cwd().toAbsolutePath().normalize();
Path normalized = candidate.toAbsolutePath().normalize();
if (!normalized.startsWith(workspaceRoot)) {
return Optional.empty();
}
try {
Path realWorkspaceRoot = workspaceRoot.toRealPath();
Path realCandidate = normalized.toRealPath();
if (Files.isDirectory(realCandidate) && realCandidate.startsWith(realWorkspaceRoot)) {
return Optional.of(normalized);
}
} catch (IOException | SecurityException ignored) {
return Optional.empty();
}
return Optional.empty();
}

private void ensureToolRuntimeCwdMatches() {
Path agentCwd = ports.cwd();
Path toolCwd = ports.toolRuntime().cwd().toAbsolutePath().normalize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import cn.lypi.contracts.session.SessionContext;
import cn.lypi.contracts.session.SessionEntry;
import cn.lypi.contracts.session.SessionHandle;
import cn.lypi.contracts.session.ShellState;
import cn.lypi.contracts.session.ShellStateChangeEntry;
import cn.lypi.contracts.session.SessionView;
import cn.lypi.contracts.session.ThinkingChangeEntry;
import cn.lypi.contracts.tool.Tool;
Expand Down Expand Up @@ -422,6 +424,7 @@ static class InMemorySessionManager implements SessionManagerPort {
private String sessionId;
private String leafId = "";
private final Map<String, SessionEntry> entries = new LinkedHashMap<>();
private ShellState initialShellState = ShellState.of(Path.of(".").toAbsolutePath().normalize());

@Override
public SessionHandle openOrCreate(String sessionId) {
Expand All @@ -436,6 +439,27 @@ public SessionHandle append(SessionEntry entry) {
return handle();
}

@Override
public ShellState shellState() {
ShellState current = initialShellState;
for (SessionEntry entry : branch(leafId)) {
if (entry instanceof ShellStateChangeEntry change) {
current = change.shellState();
}
}
return current;
}

@Override
public SessionHandle appendShellStateChange(ShellState shellState) {
return append(new ShellStateChangeEntry(
"entry-shell-state-" + entries.size(),
leafId,
shellState,
NOW
));
}

@Override
public SessionHandle switchLeaf(String leafId) {
this.leafId = leafId;
Expand Down Expand Up @@ -558,6 +582,10 @@ SessionEntry entry(String entryId) {
return entries.get(entryId);
}

void initialShellState(Path cwd) {
initialShellState = ShellState.of(cwd);
}

SessionHandle handle() {
return new SessionHandle(sessionId, Path.of("test-session.jsonl"), leafId, Map.copyOf(entries));
}
Expand Down
Loading
Loading