Skip to content

Commit d824327

Browse files
Cai-Tang-wwwclaude
andcommitted
fix(gateway): 完成 Gateway 启动接线与 Runtime 工具分流
- MultiWorkspaceRuntime 新增 InjectRunnerDispatcher,同时注入已有 和未来创建的 workspace bundle - gateway_commands 中创建 RunnerRegistry/RunnerToolManager 并传入 NetworkServerOptions 和 runtime dispatcher - network_server 在 WS 断连时自动清理 runner 注册记录 - runtime 新增 RunnerToolDispatcher 接口及设值方法,在工具执行前 优先尝试 runner 分发,handled=false 时回退本地执行 - 新增 runner_tool_bridge 适配 RunnerToolManager 到 runtime 接口 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 287c224 commit d824327

6 files changed

Lines changed: 132 additions & 1 deletion

File tree

internal/cli/gateway_commands.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"neo-code/internal/config"
2020
"neo-code/internal/gateway"
2121
gatewayauth "neo-code/internal/gateway/auth"
22+
agentruntime "neo-code/internal/runtime"
2223
"neo-code/internal/webassets"
2324
)
2425

@@ -238,6 +239,15 @@ func startGatewayServer(ctx context.Context, options gatewayCommandOptions, stat
238239
Metrics: metrics,
239240
})
240241

242+
runnerRegistry := gateway.NewRunnerRegistry(logger)
243+
runnerToolManager := gateway.NewRunnerToolManager(
244+
runnerRegistry,
245+
relay,
246+
nil, // capability signer: nil allows execution without token for MVP
247+
30*time.Second,
248+
logger,
249+
)
250+
241251
runtimePort, closeRuntimePort, err := buildGatewayRuntimePort(signalContext, options.Workdir)
242252
if err != nil {
243253
return fmt.Errorf("initialize gateway runtime: %w", err)
@@ -248,6 +258,9 @@ func startGatewayServer(ctx context.Context, options gatewayCommandOptions, stat
248258
}
249259
}()
250260

261+
// 注入 Runner 工具分发器到 runtime,使 ReAct 循环中的工具调用可以通过 runner 执行
262+
injectRunnerDispatcherIntoRuntime(runtimePort, runnerToolManager)
263+
251264
idleCloser := newGatewayIdleShutdownController(logger, cancelRuntime)
252265
defer idleCloser.close()
253266

@@ -294,6 +307,8 @@ func startGatewayServer(ctx context.Context, options gatewayCommandOptions, stat
294307
AllowedOrigins: gatewayConfig.Security.AllowOrigins,
295308
StaticFileDir: staticFileDir,
296309
StaticFileFS: staticFileFS,
310+
RunnerRegistry: runnerRegistry,
311+
RunnerToolManager: runnerToolManager,
297312
ConnectionCountChanged: func(active int) {
298313
idleCloser.observe(active)
299314
},
@@ -479,6 +494,33 @@ func defaultNewGatewayNetworkServer(options gateway.NetworkServerOptions) (gatew
479494
return gateway.NewNetworkServer(options)
480495
}
481496

497+
// injectRunnerDispatcherIntoRuntime 将 RunnerToolManager 注入到多工作区 runtime 的所有 bundle 中,
498+
// 使 ReAct 循环中的工具调用可以通过 runner 远程执行。
499+
func injectRunnerDispatcherIntoRuntime(runtimePort gateway.RuntimePort, runnerToolManager *gateway.RunnerToolManager) {
500+
if runtimePort == nil || runnerToolManager == nil {
501+
return
502+
}
503+
504+
mw, ok := runtimePort.(*gateway.MultiWorkspaceRuntime)
505+
if !ok {
506+
return
507+
}
508+
509+
dispatcher := gateway.NewRunnerToolDispatcher(runnerToolManager)
510+
511+
mw.InjectRunnerDispatcher(func(port gateway.RuntimePort) {
512+
bridge, ok := port.(*gatewayRuntimePortBridge)
513+
if !ok {
514+
return
515+
}
516+
svc, ok := bridge.runtime.(*agentruntime.Service)
517+
if !ok {
518+
return
519+
}
520+
svc.SetRunnerToolDispatcher(dispatcher)
521+
})
522+
}
523+
482524
// encodeJSONLine 将对象编码为单行 JSON,并写入目标输出流。
483525
func encodeJSONLine(writer io.Writer, payload any) error {
484526
encoder := json.NewEncoder(writer)

internal/gateway/multi_workspace_runtime.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type MultiWorkspaceRuntime struct {
2121
defaultHash string
2222
managementPort ManagementRuntimePort
2323

24+
runnerDispatcherInjector func(RuntimePort)
25+
2426
events chan RuntimeEvent
2527
eventSubs map[string]chan<- RuntimeEvent
2628
eventMu sync.Mutex
@@ -94,6 +96,10 @@ func (m *MultiWorkspaceRuntime) getPortForHash(hash string) (RuntimePort, error)
9496
return nil, fmt.Errorf("build workspace runtime for %s: %w", hash, err)
9597
}
9698

99+
if m.runnerDispatcherInjector != nil {
100+
m.runnerDispatcherInjector(port)
101+
}
102+
97103
b = &workspaceBundle{port: port, cleanup: cleanup}
98104
m.bundles[hash] = b
99105
m.startEventForwarder(hash, port)
@@ -135,6 +141,19 @@ func (m *MultiWorkspaceRuntime) startEventForwarder(hash string, port RuntimePor
135141
}()
136142
}
137143

144+
// InjectRunnerDispatcher 设置 runner tool dispatcher 注入回调。
145+
// fn 对每个已加载或未来创建的 RuntimePort 调用一次。
146+
func (m *MultiWorkspaceRuntime) InjectRunnerDispatcher(fn func(RuntimePort)) {
147+
m.mu.Lock()
148+
defer m.mu.Unlock()
149+
150+
m.runnerDispatcherInjector = fn
151+
152+
for _, b := range m.bundles {
153+
fn(b.port)
154+
}
155+
}
156+
138157
// Close 优雅关闭所有已加载的工作区 runtime。
139158
func (m *MultiWorkspaceRuntime) Close() error {
140159
close(m.stopCh)

internal/gateway/network_server.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,9 @@ func (s *NetworkServer) handleWebSocket(conn *websocket.Conn, runtimePort Runtim
621621

622622
defer func() {
623623
s.unregisterWSConnection(conn)
624+
if s.runnerRegistry != nil {
625+
s.runnerRegistry.OnConnectionDropped(connectionID)
626+
}
624627
relay.dropConnection(connectionID)
625628
_ = conn.Close()
626629
}()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package gateway
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"strings"
7+
8+
agentruntime "neo-code/internal/runtime"
9+
"neo-code/internal/tools"
10+
)
11+
12+
// runnerToolDispatcherBridge 适配 RunnerToolManager 为 runtime.RunnerToolDispatcher。
13+
type runnerToolDispatcherBridge struct {
14+
manager *RunnerToolManager
15+
}
16+
17+
// NewRunnerToolDispatcher 创建 runtime.RunnerToolDispatcher 的 gateway 端适配器。
18+
func NewRunnerToolDispatcher(manager *RunnerToolManager) agentruntime.RunnerToolDispatcher {
19+
if manager == nil {
20+
return nil
21+
}
22+
return &runnerToolDispatcherBridge{manager: manager}
23+
}
24+
25+
func (b *runnerToolDispatcherBridge) TryDispatch(
26+
ctx context.Context,
27+
sessionID string,
28+
runID string,
29+
input tools.ToolCallInput,
30+
) (tools.ToolResult, bool, error) {
31+
content, isError, err := b.manager.DispatchToolRequest(
32+
ctx,
33+
strings.TrimSpace(sessionID),
34+
strings.TrimSpace(runID),
35+
strings.TrimSpace(input.ID),
36+
strings.TrimSpace(input.Name),
37+
json.RawMessage(input.Arguments),
38+
)
39+
if err != nil {
40+
if strings.Contains(err.Error(), "runner not online") {
41+
return tools.ToolResult{}, false, nil
42+
}
43+
return tools.ToolResult{Content: err.Error(), IsError: true}, true, nil
44+
}
45+
return tools.ToolResult{Content: content, IsError: isError}, true, nil
46+
}

internal/runtime/permission.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,16 @@ func (s *Service) executeToolCallWithPermission(ctx context.Context, input permi
107107

108108
effectiveTimeout := resolveToolExecutionTimeout(input.Call, input.ToolTimeout)
109109
runCtx, cancel := context.WithTimeout(ctx, effectiveTimeout)
110+
defer cancel()
111+
112+
if s.runnerToolDispatcher != nil {
113+
result, handled, dispatchErr := s.runnerToolDispatcher.TryDispatch(runCtx, input.SessionID, input.RunID, callInput)
114+
if handled {
115+
return result, dispatchErr
116+
}
117+
}
118+
110119
result, execErr := s.toolManager.Execute(runCtx, callInput)
111-
cancel()
112120
if execErr == nil {
113121
return result, nil
114122
}

internal/runtime/runtime.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,19 @@ type Service struct {
184184
permissionAskLocks map[string]*permissionAskLockEntry
185185

186186
thinkingEnabled bool
187+
188+
runnerToolDispatcher RunnerToolDispatcher
189+
}
190+
191+
// RunnerToolDispatcher 可选:将工具执行分发到远程 runner。
192+
// 返回 (result, handled, error)。handled=false 表示继续走本地执行。
193+
type RunnerToolDispatcher interface {
194+
TryDispatch(ctx context.Context, sessionID, runID string, input tools.ToolCallInput) (tools.ToolResult, bool, error)
195+
}
196+
197+
// SetRunnerToolDispatcher 设置远程工具分发器。
198+
func (s *Service) SetRunnerToolDispatcher(d RunnerToolDispatcher) {
199+
s.runnerToolDispatcher = d
187200
}
188201

189202
// sessionLockEntry 维护单个会话读写锁及其当前引用计数,用于在无引用时回收 map 项。

0 commit comments

Comments
 (0)