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
1 change: 0 additions & 1 deletion extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ afterwards the extension reconnects silently — you connect once. Use
- `debugger` — the CDP control channel (Chrome shows a banner while attached).
- `tabs`, `tabGroups` — create/switch/group tabs.
- `storage` — persist the server URL and pairing token.
- `scripting` — reserved for future in-page helpers.
- `host_permissions` limited to `127.0.0.1` / `localhost` — it only ever talks
to your local jcode.

Expand Down
4 changes: 2 additions & 2 deletions extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"manifest_version": 3,
"name": "jcode Browser Bridge",
"version": "0.1.4",
"description": "Let jcode see and operate this Chrome via the Chrome DevTools Protocol. Connects to your local jcode server.",
"description": "Let jcode see and operate your browser via the DevTools Protocol. Connects to your local jcode server.",
"minimum_chrome_version": "116",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0JN3n8PBlNtsaMBRXs5g76Kt8C1VIO5bz+vRY4HMAyn1soIAhNDu9ZAcQjOUmuu1SyJe7A683EfgXJhpFghvSULi63rKHO584FBc9zK53b8m1yVq6HuNZtwXTZyDXeCVNwKstI9zHCLqTUEWyBuy3zJOWRq+0d8h9Moz2a0rDLePqAmPyQb6nlSvDomPIIRnk4p0sBSQbENWKwd/hhJwlsl/D4JK/SVWLXfhQZOP5PceGJ0gnOmIH38bPuxW3l1EWk3nuOZyIVRUvF9QkuAhS9U/+1WEVCco6tijVaBoHI6rzbxouR5BH9Drg0lt9VPJPlq0HlU8AyLLepweJ6MWxwIDAQAB",
"permissions": ["debugger", "tabs", "storage", "tabGroups", "alarms", "scripting", "nativeMessaging"],
"permissions": ["debugger", "tabs", "storage", "tabGroups", "alarms", "nativeMessaging"],
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"],
"background": {
"service_worker": "background.js",
Expand Down
17 changes: 9 additions & 8 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ const maxIterations = 1000

type ApprovalFunc func(ctx context.Context, toolName, toolArgs string) (bool, error)

// NewAgent creates a ChatModelAgent with the following middleware stack
// NewAgent creates a ChatModelAgent with the following handler stack
// (outermost to innermost):
//
// Middlewares (old-style): [langfuse]
// Handlers (new-style): [...caller handlers, approval+safeTool]
// Handlers: [langfuse tracing, ...caller handlers, approval+safeTool]
//
// ModelRetryConfig is always enabled (3 retries with default exponential backoff).
func NewAgent(
Expand All @@ -28,12 +27,15 @@ func NewAgent(
tools []tool.BaseTool,
instruction string,
approvalFunc ApprovalFunc,
middlewares []adk.AgentMiddleware,
middlewares []adk.ChatModelAgentMiddleware,
handlers []adk.ChatModelAgentMiddleware,
) (*adk.ChatModelAgent, error) {
// Approval + safe-tool-error middleware is always the innermost handler
// so that summarization/reduction see the raw tool output first.
enhanced := append(append([]adk.ChatModelAgentMiddleware{}, handlers...), newApprovalMiddleware(approvalFunc))
// Handler order is outermost → innermost: tracing middlewares first, then the
// caller's handlers, then approval + safe-tool-error innermost so that
// summarization/reduction see the raw tool output first.
enhanced := append([]adk.ChatModelAgentMiddleware{}, middlewares...)
enhanced = append(enhanced, handlers...)
enhanced = append(enhanced, newApprovalMiddleware(approvalFunc))

return adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "coding",
Expand All @@ -46,7 +48,6 @@ func NewAgent(
},
},
MaxIterations: maxIterations,
Middlewares: middlewares,
Handlers: enhanced,
ModelRetryConfig: &adk.ModelRetryConfig{
MaxRetries: 5,
Expand Down
18 changes: 16 additions & 2 deletions internal/browser/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@ import (
"github.com/cnjack/jcode/internal/config"
)

// ExtensionID is the chrome extension id derived from the committed public key
// ("key" field) in extension/manifest.json — stable across loads and machines.
// ExtensionID is the unpacked/dev extension id, derived from the committed
// public key ("key" field) in extension/manifest.json — stable across loads.
const ExtensionID = "ekcnniaefmnhnemnpphikhgfoofnojnd"

// AllowedExtensionIDs is every id the Browser Bridge extension can have: the
// unpacked dev build (above) plus the published store builds, which the stores
// re-sign under their own ids. All of these must appear in the native-host
// manifest's allowed_origins, or a store-installed extension can't open the
// native host (the browser enforces the origin before launching it).
var AllowedExtensionIDs = []string{
ExtensionID, // unpacked / dev (manifest "key")
"olkapiiikpfhaccmjphakolinkcggcbd", // Chrome Web Store
// Microsoft Edge Add-ons: add the runtime extension id here once known. It is
// NOT the Partner Center product id (0RDCKGMRP90R) — that's the listing id.
// Find the runtime id at edge://extensions after installing, or in Partner
// Center → Extension overview.
}

// FindChrome returns the path to a Chromium-based browser executable, or ""
// when none is found. Explicit configPath (config.browser.chrome_path) wins.
func FindChrome(configPath string) string {
Expand Down
6 changes: 5 additions & 1 deletion internal/browser/nativehost.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,16 @@ func sendEndpoint(out io.Writer) {

// nativeHostManifest is the JSON Chrome/Edge read to find and authorize the host.
func nativeHostManifest(binPath string) []byte {
origins := make([]string, len(AllowedExtensionIDs))
for i, id := range AllowedExtensionIDs {
origins[i] = fmt.Sprintf("chrome-extension://%s/", id)
}
m := map[string]any{
"name": NativeHostName,
"description": "jcode Browser Bridge native host",
"path": binPath,
"type": "stdio",
"allowed_origins": []string{fmt.Sprintf("chrome-extension://%s/", ExtensionID)},
"allowed_origins": origins,
}
data, _ := json.MarshalIndent(m, "", " ")
return data
Expand Down
23 changes: 17 additions & 6 deletions internal/browser/nativehost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,23 @@ func TestNativeHostManifestShape(t *testing.T) {
t.Errorf("type = %v", m["type"])
}
origins, ok := m["allowed_origins"].([]any)
if !ok || len(origins) != 1 {
t.Fatalf("allowed_origins = %v", m["allowed_origins"])
}
want := "chrome-extension://" + ExtensionID + "/"
if origins[0] != want {
t.Errorf("allowed_origins[0] = %v, want %s", origins[0], want)
if !ok || len(origins) != len(AllowedExtensionIDs) {
t.Fatalf("allowed_origins = %v, want %d entries", m["allowed_origins"], len(AllowedExtensionIDs))
}
got := make(map[string]bool, len(origins))
for _, o := range origins {
s, _ := o.(string)
got[s] = true
}
// Every allowed id (dev + published store builds) must be present, in the
// chrome-extension://<id>/ origin form.
for _, id := range AllowedExtensionIDs {
if want := "chrome-extension://" + id + "/"; !got[want] {
t.Errorf("allowed_origins missing %s (have %v)", want, origins)
}
}
if !got["chrome-extension://"+ExtensionID+"/"] {
t.Errorf("dev/unpacked extension id must always be allowed")
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/command/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (s *interactiveState) subagentTokenFn(totalTokens int64) {
}

func (s *interactiveState) createAgent() (*adk.ChatModelAgent, error) {
var middlewares []adk.AgentMiddleware
var middlewares []adk.ChatModelAgentMiddleware
if s.langfuseTracer != nil {
middlewares = append(middlewares, s.langfuseTracer.AgentMiddleware())
}
Expand Down
2 changes: 1 addition & 1 deletion internal/command/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ func runWebServer(port int, host string, openBrowser bool, authToken string) err
_ = os.MkdirAll(reductionRoot, 0o755)

makeAgent := func(cm model.ToolCallingChatModel, ctxLimit int, planMode bool) (*adk.ChatModelAgent, error) {
var middlewares []adk.AgentMiddleware //nolint:staticcheck // langfuseTracer.AgentMiddleware()/agent.NewAgent still use the deprecated type
var middlewares []adk.ChatModelAgentMiddleware
if langfuseTracer != nil {
middlewares = append(middlewares, langfuseTracer.AgentMiddleware())
}
Expand Down
10 changes: 4 additions & 6 deletions internal/team/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,15 +568,14 @@ func (m *Manager) runAgentTurn(ctx context.Context, state *TeammateState) (strin
return "", fmt.Errorf("invalid chat model type")
}

var middlewares []adk.AgentMiddleware
var handlers []adk.ChatModelAgentMiddleware
// Langfuse child trace (outermost) so teammate spans nest under the parent.
if m.deps.Tracer != nil {
ctx = m.deps.Tracer.WithChildTrace(ctx, fmt.Sprintf("teammate-%s", state.Identity.AgentName))
middlewares = append(middlewares, m.deps.Tracer.ChildAgentMiddleware())
handlers = append(handlers, m.deps.Tracer.ChildAgentMiddleware())
}

var handlers []adk.ChatModelAgentMiddleware
if m.deps.HandlersFactory != nil {
handlers = m.deps.HandlersFactory(state.Identity.AgentName, state.Identity.Color)
handlers = append(handlers, m.deps.HandlersFactory(state.Identity.AgentName, state.Identity.Color)...)
}

ag, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Expand All @@ -590,7 +589,6 @@ func (m *Manager) runAgentTurn(ctx context.Context, state *TeammateState) (strin
},
},
MaxIterations: teammateMaxIter,
Middlewares: middlewares,
Handlers: handlers,
ModelRetryConfig: &adk.ModelRetryConfig{
MaxRetries: 3,
Expand Down
Loading
Loading