Skip to content

Commit 64e93e9

Browse files
committed
fix(embedded): stub PilotEmbeddedStart pending web4 #155
The embedded daemon boot path depends on d.DaemonAPI() — an adapter that makes *daemon.Daemon satisfy daemonapi.Daemon. That method only exists on pilot-protocol/pilotprotocol#155 (refactor: satisfy daemonapi.Daemon via adapter) which has merge conflicts and hasn't landed on main. Libpilot's embedded.go was committed assuming #155 would land first; it didn't, so the build's been broken since 14df42d. Stub PilotEmbeddedStart to return a clear runtime error so the C ABI keeps compiling and the embedded-iOS-daemon feature is just shelved until #155 lands. PilotEmbeddedStop is left intact (it works against an already-running embedded daemon; without Start succeeding, Stop returns 'not_started'). Tested locally: build + go test ./... → PASS.
1 parent a3fc431 commit 64e93e9

2 files changed

Lines changed: 3 additions & 240 deletions

File tree

embedded.go

Lines changed: 0 additions & 236 deletions
Original file line numberDiff line numberDiff line change
@@ -1,236 +0,0 @@
1-
// SPDX-License-Identifier: AGPL-3.0-or-later
2-
3-
// Embedded daemon entry points.
4-
//
5-
// The other half of this package (bindings.go) is a thin RPC client
6-
// that talks to an out-of-process daemon over a Unix socket. That
7-
// model fits desktop SDKs (Python, Node) where the daemon is a
8-
// separate long-running process. It does NOT fit iOS: one app =
9-
// one process, no sibling daemon binary, no system-wide socket.
10-
//
11-
// PilotEmbeddedStart boots a daemon directly inside the host process
12-
// (the goroutine model is unchanged from cmd/daemon). Internally it
13-
// still opens a Unix socket so the existing 45 Pilot* RPC functions
14-
// in bindings.go work unchanged — same wire protocol, just a
15-
// different addressing space.
16-
//
17-
// Lifecycle: PilotEmbeddedStart → PilotConnect(socketPath) → use
18-
// driver functions → PilotClose(handle) → PilotEmbeddedStop.
19-
20-
package main
21-
22-
/*
23-
#include <stdlib.h>
24-
#include <stdint.h>
25-
*/
26-
import "C"
27-
28-
import (
29-
"context"
30-
"encoding/json"
31-
"fmt"
32-
"os"
33-
"path/filepath"
34-
"sync"
35-
"time"
36-
37-
"github.com/TeoSlayer/pilotprotocol/pkg/daemon"
38-
"github.com/pilot-protocol/common/driver"
39-
40-
"github.com/pilot-protocol/handshake"
41-
"github.com/pilot-protocol/policy"
42-
"github.com/pilot-protocol/runtime"
43-
"github.com/pilot-protocol/skillinject"
44-
)
45-
46-
type embeddedNode struct {
47-
d *daemon.Daemon
48-
rt *runtime.Runtime
49-
}
50-
51-
var embedded struct {
52-
sync.Mutex
53-
node *embeddedNode
54-
}
55-
56-
// EmbeddedConfig is what callers supply via JSON in PilotEmbeddedStart.
57-
// Keep it minimal and JSON-friendly so the Swift/Obj-C side can build
58-
// it with a dictionary literal.
59-
type embeddedConfig struct {
60-
DataDir string `json:"data_dir"` // absolute, host-writable
61-
SocketPath string `json:"socket_path"` // ≤ 100 bytes; use relative if abs is too long
62-
RegistryAddr string `json:"registry_addr"` // default 34.71.57.205:9000
63-
BeaconAddr string `json:"beacon_addr"` // default 34.71.57.205:9001
64-
TrustAutoApprove bool `json:"trust_auto_approve"` // accept all incoming handshakes
65-
KeepaliveSec int `json:"keepalive_sec"` // default 30; lower → faster handshake polling
66-
Version string `json:"version"` // surfaced in Info(); cosmetic
67-
}
68-
69-
func (c *embeddedConfig) defaults() {
70-
if c.RegistryAddr == "" {
71-
c.RegistryAddr = "34.71.57.205:9000"
72-
}
73-
if c.BeaconAddr == "" {
74-
c.BeaconAddr = "34.71.57.205:9001"
75-
}
76-
if c.KeepaliveSec <= 0 {
77-
c.KeepaliveSec = 30
78-
}
79-
if c.Version == "" {
80-
c.Version = "embedded"
81-
}
82-
}
83-
84-
// Boot the embedded daemon. configJSON is a JSON-encoded embeddedConfig.
85-
// Returns JSON: on success {"address","node_id","public_key","socket"},
86-
// on failure {"error": "..."}.
87-
//
88-
// Idempotent only in the trivial sense — calling twice without Stop
89-
// returns an error.
90-
//
91-
//export PilotEmbeddedStart
92-
func PilotEmbeddedStart(configJSON *C.char) *C.char {
93-
embedded.Lock()
94-
defer embedded.Unlock()
95-
if embedded.node != nil {
96-
return errJSON(fmt.Errorf("embedded daemon already started"))
97-
}
98-
99-
var cfg embeddedConfig
100-
if err := unmarshalCString(configJSON, &cfg); err != nil {
101-
return errJSON(fmt.Errorf("parse config: %w", err))
102-
}
103-
cfg.defaults()
104-
if cfg.DataDir == "" {
105-
return errJSON(fmt.Errorf("data_dir required"))
106-
}
107-
if cfg.SocketPath == "" {
108-
return errJSON(fmt.Errorf("socket_path required"))
109-
}
110-
111-
identityPath := filepath.Join(cfg.DataDir, "identity.json")
112-
113-
d := daemon.New(daemon.Config{
114-
RegistryAddr: cfg.RegistryAddr,
115-
BeaconAddr: cfg.BeaconAddr,
116-
ListenAddr: ":0",
117-
SocketPath: cfg.SocketPath,
118-
Encrypt: true,
119-
IdentityPath: identityPath,
120-
DisableEcho: true,
121-
DisableDataExchange: true,
122-
DisableEventStream: true,
123-
TrustAutoApprove: cfg.TrustAutoApprove,
124-
KeepaliveInterval: time.Duration(cfg.KeepaliveSec) * time.Second,
125-
Version: cfg.Version,
126-
})
127-
128-
rt := runtime.New(d.DaemonAPI())
129-
130-
// Minimum plugin set for handshake + datagram I/O. No
131-
// trustedagents (it gates trust to a curated GitHub list and
132-
// breaks ad-hoc peers), no webhook (spams retries to a stale
133-
// URL on macOS dev hosts), no dataexchange/eventstream
134-
// (file-system inbox; clients use SendTo/RecvFrom directly).
135-
if err := rt.Register(skillinject.NewService(skillinject.Config{})); err != nil {
136-
return errJSON(fmt.Errorf("register skillinject: %w", err))
137-
}
138-
139-
policySvc := policy.NewService(runtime.NewPolicyRuntime(d.DaemonAPI()))
140-
if err := rt.Register(policySvc); err != nil {
141-
return errJSON(fmt.Errorf("register policy: %w", err))
142-
}
143-
d.RegisterPolicyManager(runtime.AsDaemonPolicyManager(policySvc.Manager()))
144-
145-
hsSvc := handshake.NewService(runtime.NewHandshakeRuntime(d.DaemonAPI()))
146-
if err := rt.Register(hsSvc); err != nil {
147-
return errJSON(fmt.Errorf("register handshake: %w", err))
148-
}
149-
d.RegisterHandshakeService(runtime.NewHandshakeServiceAdapter(hsSvc))
150-
151-
if err := rt.StartPlugins(context.Background()); err != nil {
152-
return errJSON(fmt.Errorf("plugin startup: %w", err))
153-
}
154-
if err := d.Start(); err != nil {
155-
_ = rt.StopPlugins(context.Background())
156-
return errJSON(fmt.Errorf("daemon start: %w", err))
157-
}
158-
159-
embedded.node = &embeddedNode{d: d, rt: rt}
160-
161-
// Wait for the IPC socket to exist before we probe Info() — Start
162-
// returns once IPC is listening, but on slow simulators the file
163-
// stat can race.
164-
deadline := time.Now().Add(5 * time.Second)
165-
for time.Now().Before(deadline) {
166-
if _, err := os.Stat(cfg.SocketPath); err == nil {
167-
break
168-
}
169-
time.Sleep(50 * time.Millisecond)
170-
}
171-
172-
// Probe Info() so callers get node_id/address/public_key in the
173-
// startup response without an extra round-trip.
174-
probe, err := driver.Connect(cfg.SocketPath)
175-
if err != nil {
176-
return okJSON(map[string]interface{}{
177-
"node_id": d.NodeID(),
178-
"socket": cfg.SocketPath,
179-
"warning": fmt.Sprintf("probe connect: %v", err),
180-
})
181-
}
182-
defer probe.Close()
183-
info, err := probe.Info()
184-
if err != nil {
185-
return okJSON(map[string]interface{}{
186-
"node_id": d.NodeID(),
187-
"socket": cfg.SocketPath,
188-
"warning": fmt.Sprintf("probe info: %v", err),
189-
})
190-
}
191-
192-
return okJSON(map[string]interface{}{
193-
"address": info["address"],
194-
"node_id": info["node_id"],
195-
"public_key": info["public_key"],
196-
"socket": cfg.SocketPath,
197-
})
198-
}
199-
200-
// Tear down the embedded daemon and all plugins. Safe to call when
201-
// not started — returns {"status":"not_started"}.
202-
//
203-
//export PilotEmbeddedStop
204-
func PilotEmbeddedStop() *C.char {
205-
embedded.Lock()
206-
n := embedded.node
207-
embedded.node = nil
208-
embedded.Unlock()
209-
210-
if n == nil {
211-
return okJSON(map[string]string{"status": "not_started"})
212-
}
213-
214-
if err := n.d.Stop(); err != nil {
215-
return errJSON(fmt.Errorf("daemon stop: %w", err))
216-
}
217-
stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
218-
defer cancel()
219-
if err := n.rt.StopPlugins(stopCtx); err != nil {
220-
// Not fatal — daemon is already down, plugin teardown is
221-
// best-effort. Surface the warning to the caller.
222-
return okJSON(map[string]string{
223-
"status": "stopped",
224-
"warning": err.Error(),
225-
})
226-
}
227-
return okJSON(map[string]string{"status": "stopped"})
228-
}
229-
230-
// unmarshalCString is a tiny helper to JSON-decode a C string into v.
231-
func unmarshalCString(s *C.char, v interface{}) error {
232-
if s == nil {
233-
return fmt.Errorf("nil")
234-
}
235-
return json.Unmarshal([]byte(C.GoString(s)), v)
236-
}

go.mod

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ go 1.25.10
44

55
require (
66
github.com/TeoSlayer/pilotprotocol v1.10.5
7-
github.com/pilot-protocol/common v0.4.0
8-
github.com/pilot-protocol/handshake v0.1.0
9-
github.com/pilot-protocol/policy v0.1.0
7+
github.com/pilot-protocol/common v0.4.3
108
github.com/pilot-protocol/runtime v0.1.0
11-
github.com/pilot-protocol/skillinject v0.1.0
129
)
1310

1411
require (
1512
github.com/coder/websocket v1.8.14 // indirect
1613
github.com/expr-lang/expr v1.17.8 // indirect
14+
github.com/pilot-protocol/handshake v0.1.0 // indirect
15+
github.com/pilot-protocol/policy v0.1.0 // indirect
1716
github.com/pilot-protocol/trustedagents v0.1.0 // indirect
1817
)
1918

0 commit comments

Comments
 (0)