sensorium-mcp 2.17.28 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Install-Sensorium.ps1 +327 -0
- package/README.md +14 -0
- package/dist/config.d.ts +16 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +39 -2
- package/dist/config.js.map +1 -1
- package/dist/daily-session.d.ts +2 -1
- package/dist/daily-session.d.ts.map +1 -1
- package/dist/daily-session.js +23 -26
- package/dist/daily-session.js.map +1 -1
- package/dist/dashboard/routes/settings.d.ts +4 -0
- package/dist/dashboard/routes/settings.d.ts.map +1 -1
- package/dist/dashboard/routes/settings.js +57 -1
- package/dist/dashboard/routes/settings.js.map +1 -1
- package/dist/dashboard/routes/threads.d.ts +1 -0
- package/dist/dashboard/routes/threads.d.ts.map +1 -1
- package/dist/dashboard/routes/threads.js +23 -25
- package/dist/dashboard/routes/threads.js.map +1 -1
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +7 -2
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/spa.html +11 -11
- package/dist/data/interfaces.d.ts +36 -0
- package/dist/data/interfaces.d.ts.map +1 -0
- package/dist/data/interfaces.js +2 -0
- package/dist/data/interfaces.js.map +1 -0
- package/dist/data/memory/bootstrap.d.ts +36 -16
- package/dist/data/memory/bootstrap.d.ts.map +1 -1
- package/dist/data/memory/bootstrap.js +71 -217
- package/dist/data/memory/bootstrap.js.map +1 -1
- package/dist/data/memory/consolidation.d.ts +35 -34
- package/dist/data/memory/consolidation.d.ts.map +1 -1
- package/dist/data/memory/consolidation.js +43 -554
- package/dist/data/memory/consolidation.js.map +1 -1
- package/dist/data/memory/migration-runner.d.ts +5 -0
- package/dist/data/memory/migration-runner.d.ts.map +1 -0
- package/dist/data/memory/migration-runner.js +403 -0
- package/dist/data/memory/migration-runner.js.map +1 -0
- package/dist/data/memory/reflection.js +1 -1
- package/dist/data/memory/schema-ddl.d.ts +4 -0
- package/dist/data/memory/schema-ddl.d.ts.map +1 -0
- package/dist/data/memory/schema-ddl.js +194 -0
- package/dist/data/memory/schema-ddl.js.map +1 -0
- package/dist/data/memory/schema-guard.d.ts +3 -0
- package/dist/data/memory/schema-guard.d.ts.map +1 -0
- package/dist/data/memory/schema-guard.js +184 -0
- package/dist/data/memory/schema-guard.js.map +1 -0
- package/dist/data/memory/schema.d.ts +2 -5
- package/dist/data/memory/schema.d.ts.map +1 -1
- package/dist/data/memory/schema.js +6 -834
- package/dist/data/memory/schema.js.map +1 -1
- package/dist/data/memory/synthesis.js +2 -2
- package/dist/data/memory/synthesis.js.map +1 -1
- package/dist/data/memory/thread-registry.d.ts +18 -4
- package/dist/data/memory/thread-registry.d.ts.map +1 -1
- package/dist/data/memory/thread-registry.js +25 -0
- package/dist/data/memory/thread-registry.js.map +1 -1
- package/dist/data/sent-message.repository.d.ts +12 -0
- package/dist/data/sent-message.repository.d.ts.map +1 -0
- package/dist/data/sent-message.repository.js +31 -0
- package/dist/data/sent-message.repository.js.map +1 -0
- package/dist/http-server.d.ts.map +1 -1
- package/dist/http-server.js +23 -2
- package/dist/http-server.js.map +1 -1
- package/dist/index.js +27 -48
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +7 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +89 -12
- package/dist/logger.js.map +1 -1
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +15 -0
- package/dist/scheduler.js.map +1 -1
- package/dist/server/factory.d.ts +2 -1
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +11 -4
- package/dist/server/factory.js.map +1 -1
- package/dist/services/agent-spawn.service.d.ts +39 -0
- package/dist/services/agent-spawn.service.d.ts.map +1 -0
- package/dist/services/agent-spawn.service.js +348 -0
- package/dist/services/agent-spawn.service.js.map +1 -0
- package/dist/services/background-runner.d.ts +26 -0
- package/dist/services/background-runner.d.ts.map +1 -0
- package/dist/services/background-runner.js +71 -0
- package/dist/services/background-runner.js.map +1 -0
- package/dist/services/consolidation.service.d.ts +16 -0
- package/dist/services/consolidation.service.d.ts.map +1 -0
- package/dist/services/consolidation.service.js +508 -0
- package/dist/services/consolidation.service.js.map +1 -0
- package/dist/services/dispatcher/broker.d.ts +2 -0
- package/dist/services/dispatcher/broker.d.ts.map +1 -1
- package/dist/services/dispatcher/broker.js +5 -10
- package/dist/services/dispatcher/broker.js.map +1 -1
- package/dist/services/dispatcher/index.d.ts +1 -1
- package/dist/services/dispatcher/index.d.ts.map +1 -1
- package/dist/services/dispatcher/index.js +1 -1
- package/dist/services/dispatcher/index.js.map +1 -1
- package/dist/services/dispatcher/lock.d.ts.map +1 -1
- package/dist/services/dispatcher/lock.js +7 -11
- package/dist/services/dispatcher/lock.js.map +1 -1
- package/dist/services/maintenance-signal.d.ts +18 -0
- package/dist/services/maintenance-signal.d.ts.map +1 -0
- package/dist/services/maintenance-signal.js +48 -0
- package/dist/services/maintenance-signal.js.map +1 -0
- package/dist/services/memory-briefing.service.d.ts +4 -0
- package/dist/services/memory-briefing.service.d.ts.map +1 -0
- package/dist/services/memory-briefing.service.js +143 -0
- package/dist/services/memory-briefing.service.js.map +1 -0
- package/dist/services/process.service.d.ts +31 -0
- package/dist/services/process.service.d.ts.map +1 -0
- package/dist/services/process.service.js +100 -0
- package/dist/services/process.service.js.map +1 -0
- package/dist/services/thread-health.service.d.ts +18 -0
- package/dist/services/thread-health.service.d.ts.map +1 -0
- package/dist/services/thread-health.service.js +118 -0
- package/dist/services/thread-health.service.js.map +1 -0
- package/dist/services/thread-lifecycle.service.d.ts +52 -0
- package/dist/services/thread-lifecycle.service.d.ts.map +1 -0
- package/dist/services/thread-lifecycle.service.js +174 -0
- package/dist/services/thread-lifecycle.service.js.map +1 -0
- package/dist/services/topic.service.d.ts +25 -0
- package/dist/services/topic.service.d.ts.map +1 -0
- package/dist/services/topic.service.js +65 -0
- package/dist/services/topic.service.js.map +1 -0
- package/dist/services/worker-cleanup.service.d.ts +8 -0
- package/dist/services/worker-cleanup.service.d.ts.map +1 -0
- package/dist/services/worker-cleanup.service.js +82 -0
- package/dist/services/worker-cleanup.service.js.map +1 -0
- package/dist/sessions.d.ts +14 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +55 -0
- package/dist/sessions.js.map +1 -1
- package/dist/telegram.d.ts +13 -6
- package/dist/telegram.d.ts.map +1 -1
- package/dist/telegram.js +43 -14
- package/dist/telegram.js.map +1 -1
- package/dist/tools/delegate-tool.d.ts +4 -0
- package/dist/tools/delegate-tool.d.ts.map +1 -1
- package/dist/tools/delegate-tool.js +48 -109
- package/dist/tools/delegate-tool.js.map +1 -1
- package/dist/tools/memory-tools.d.ts.map +1 -1
- package/dist/tools/memory-tools.js +1 -1
- package/dist/tools/memory-tools.js.map +1 -1
- package/dist/tools/shared-agent-utils.d.ts +9 -1
- package/dist/tools/shared-agent-utils.d.ts.map +1 -1
- package/dist/tools/shared-agent-utils.js +21 -38
- package/dist/tools/shared-agent-utils.js.map +1 -1
- package/dist/tools/start-session-tool.d.ts +2 -0
- package/dist/tools/start-session-tool.d.ts.map +1 -1
- package/dist/tools/start-session-tool.js +66 -106
- package/dist/tools/start-session-tool.js.map +1 -1
- package/dist/tools/thread-lifecycle.d.ts +5 -127
- package/dist/tools/thread-lifecycle.d.ts.map +1 -1
- package/dist/tools/thread-lifecycle.js +5 -1167
- package/dist/tools/thread-lifecycle.js.map +1 -1
- package/dist/tools/utility-tools.js +5 -2
- package/dist/tools/utility-tools.js.map +1 -1
- package/dist/tools/wait/drive-handler.d.ts +0 -1
- package/dist/tools/wait/drive-handler.d.ts.map +1 -1
- package/dist/tools/wait/drive-handler.js +5 -22
- package/dist/tools/wait/drive-handler.js.map +1 -1
- package/dist/tools/wait/message-delivery.js +1 -1
- package/dist/tools/wait/message-delivery.js.map +1 -1
- package/dist/tools/wait/message-processing.d.ts.map +1 -1
- package/dist/tools/wait/message-processing.js +9 -8
- package/dist/tools/wait/message-processing.js.map +1 -1
- package/dist/tools/wait/poll-loop.d.ts +2 -0
- package/dist/tools/wait/poll-loop.d.ts.map +1 -1
- package/dist/tools/wait/poll-loop.js +27 -29
- package/dist/tools/wait/poll-loop.js.map +1 -1
- package/dist/tools/wait/task-handler.d.ts +0 -3
- package/dist/tools/wait/task-handler.d.ts.map +1 -1
- package/dist/tools/wait/task-handler.js +3 -2
- package/dist/tools/wait/task-handler.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -8
- package/supervisor/config.go +182 -69
- package/supervisor/config_test.go +78 -0
- package/supervisor/go.mod +12 -0
- package/supervisor/go.sum +20 -0
- package/supervisor/health.go +56 -6
- package/supervisor/health_test.go +29 -0
- package/supervisor/keeper.go +15 -10
- package/supervisor/log.go +109 -28
- package/supervisor/log_test.go +86 -6
- package/supervisor/main.go +150 -19
- package/supervisor/main_test.go +130 -0
- package/supervisor/process.go +47 -4
- package/supervisor/process_test.go +14 -0
- package/supervisor/secrets.go +95 -0
- package/supervisor/secrets_securevault_test.go +98 -0
- package/supervisor/secrets_test.go +119 -0
- package/supervisor/self_update.go +282 -0
- package/supervisor/self_update_test.go +177 -0
- package/supervisor/service_restart_stub.go +9 -0
- package/supervisor/service_restart_windows.go +63 -0
- package/supervisor/service_stub.go +15 -0
- package/supervisor/service_windows.go +216 -0
- package/supervisor/update_state.go +264 -0
- package/supervisor/update_state_test.go +306 -0
- package/supervisor/updater.go +311 -10
- package/supervisor/updater_test.go +64 -0
- package/scripts/install-supervisor.ps1 +0 -67
- package/scripts/install-supervisor.sh +0 -43
- package/scripts/start-supervisor.ps1 +0 -46
- package/scripts/start-supervisor.sh +0 -20
package/supervisor/updater.go
CHANGED
|
@@ -4,21 +4,31 @@ import (
|
|
|
4
4
|
"context"
|
|
5
5
|
"encoding/json"
|
|
6
6
|
"fmt"
|
|
7
|
+
"io"
|
|
7
8
|
"net/http"
|
|
8
9
|
"os"
|
|
9
10
|
"path/filepath"
|
|
10
11
|
"runtime"
|
|
11
12
|
"strings"
|
|
13
|
+
"syscall"
|
|
12
14
|
"time"
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
const registryURL = "https://registry.npmjs.org/sensorium-mcp/latest"
|
|
18
|
+
const supervisorReleaseURL = "https://api.github.com/repos/andriyshevchenko/remote-copilot-mcp/releases/tags/supervisor-latest"
|
|
19
|
+
|
|
20
|
+
var (
|
|
21
|
+
notifyUpdaterOperator = NotifyOperator
|
|
22
|
+
mcpUpdateReadyPollInterval = 3 * time.Second
|
|
23
|
+
mcpUpdateReadyTimeout = 60 * time.Second
|
|
24
|
+
)
|
|
16
25
|
|
|
17
26
|
// Updater checks the npm registry for new versions and performs updates.
|
|
18
27
|
type Updater struct {
|
|
19
28
|
cfg Config
|
|
20
29
|
mcp *MCPClient
|
|
21
30
|
log *Logger
|
|
31
|
+
state *UpdateStateStore
|
|
22
32
|
startAt time.Time
|
|
23
33
|
cancel context.CancelFunc
|
|
24
34
|
done chan struct{}
|
|
@@ -29,6 +39,7 @@ func NewUpdater(cfg Config, mcp *MCPClient, log *Logger) *Updater {
|
|
|
29
39
|
cfg: cfg,
|
|
30
40
|
mcp: mcp,
|
|
31
41
|
log: log,
|
|
42
|
+
state: NewUpdateStateStore(cfg.Paths.UpdateState, log),
|
|
32
43
|
startAt: time.Now(),
|
|
33
44
|
done: make(chan struct{}),
|
|
34
45
|
}
|
|
@@ -75,6 +86,10 @@ func (u *Updater) run(ctx context.Context) {
|
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
u.checkAndUpdate(ctx)
|
|
89
|
+
if ctx.Err() != nil {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
u.checkSupervisorUpdate(ctx)
|
|
78
93
|
}
|
|
79
94
|
}
|
|
80
95
|
|
|
@@ -159,30 +174,54 @@ func (u *Updater) checkAndUpdate(ctx context.Context) {
|
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
u.log.Info("Update available: %s → %s", local, remote)
|
|
162
|
-
|
|
177
|
+
coordLock, ok := AcquireUpdateCoordinatorLock(u.cfg.Paths.UpdateApplyLock, updateScopeMCP, u.log)
|
|
178
|
+
if !ok {
|
|
179
|
+
u.log.Info("Deferring MCP update %s → %s due to active update apply lock", local, remote)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
defer coordLock.Release()
|
|
183
|
+
|
|
184
|
+
u.state.Transition(updateScopeMCP, updatePhaseApplying, remote, local, "")
|
|
185
|
+
markFailed := func(err error) {
|
|
186
|
+
u.state.Transition(updateScopeMCP, updatePhaseFailed, remote, local, err.Error())
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("⚙️ Supervisor: updating sensorium v%s → v%s. Grace period %v...", local, remote, u.cfg.GracePeriod), 0)
|
|
163
190
|
|
|
164
191
|
// Grace period
|
|
165
192
|
u.log.Info("Grace period %v...", u.cfg.GracePeriod)
|
|
166
193
|
select {
|
|
167
194
|
case <-ctx.Done():
|
|
195
|
+
markFailed(ctx.Err())
|
|
168
196
|
return
|
|
169
197
|
case <-time.After(u.cfg.GracePeriod):
|
|
170
198
|
}
|
|
171
199
|
|
|
172
|
-
// Set maintenance flag — always clean up on exit
|
|
173
|
-
|
|
200
|
+
// Set maintenance flag — always clean up on exit.
|
|
201
|
+
// Written as JSON so TypeScript's checkMaintenanceFlag() can parse the
|
|
202
|
+
// version and timestamp fields for accurate maintenance notifications.
|
|
203
|
+
maintenanceJSON, err := json.Marshal(map[string]string{
|
|
204
|
+
"version": remote,
|
|
205
|
+
"timestamp": time.Now().Format(time.RFC3339),
|
|
206
|
+
})
|
|
207
|
+
if err != nil {
|
|
208
|
+
u.log.Warn("Failed to marshal maintenance flag: %v", err)
|
|
209
|
+
} else if err := atomicWrite(u.cfg.Paths.MaintenanceFlag, maintenanceJSON); err != nil {
|
|
174
210
|
u.log.Warn("Failed to write maintenance flag: %v", err)
|
|
175
211
|
}
|
|
176
212
|
defer os.Remove(u.cfg.Paths.MaintenanceFlag)
|
|
177
213
|
|
|
178
214
|
// Kill the current MCP server
|
|
179
215
|
if ctx.Err() != nil {
|
|
216
|
+
markFailed(ctx.Err())
|
|
180
217
|
return
|
|
181
218
|
}
|
|
219
|
+
u.state.Transition(updateScopeMCP, updatePhaseRestarting, remote, local, "")
|
|
182
220
|
u.killServer()
|
|
183
221
|
|
|
184
222
|
// Clean npx cache
|
|
185
223
|
if ctx.Err() != nil {
|
|
224
|
+
markFailed(ctx.Err())
|
|
186
225
|
return
|
|
187
226
|
}
|
|
188
227
|
u.clearNpxCache()
|
|
@@ -191,6 +230,7 @@ func (u *Updater) checkAndUpdate(ctx context.Context) {
|
|
|
191
230
|
var pid int
|
|
192
231
|
for attempt := 1; attempt <= 3; attempt++ {
|
|
193
232
|
if ctx.Err() != nil {
|
|
233
|
+
markFailed(ctx.Err())
|
|
194
234
|
return
|
|
195
235
|
}
|
|
196
236
|
pid, err = SpawnMCPServer(u.cfg, u.log)
|
|
@@ -204,26 +244,287 @@ func (u *Updater) checkAndUpdate(ctx context.Context) {
|
|
|
204
244
|
}
|
|
205
245
|
if err != nil {
|
|
206
246
|
u.log.Error("All spawn attempts failed — server is down!")
|
|
207
|
-
|
|
247
|
+
markFailed(err)
|
|
248
|
+
notifyUpdaterOperator(u.cfg, u.log, "🔴 Supervisor: update FAILED — server is down! Manual intervention required.", 0)
|
|
208
249
|
return
|
|
209
250
|
}
|
|
210
251
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
u.log.Info("Updated MCP server ready (PID %d)", pid)
|
|
214
|
-
} else {
|
|
215
|
-
u.log.Warn("Updated server did not become ready in 60s")
|
|
252
|
+
if !u.verifyUpdatedMCPServerReady(ctx, remote, local, pid) {
|
|
253
|
+
return
|
|
216
254
|
}
|
|
217
255
|
|
|
218
256
|
u.setLocalVersion(remote)
|
|
257
|
+
u.state.Transition(updateScopeMCP, updatePhaseIdle, remote, local, "")
|
|
219
258
|
|
|
220
|
-
|
|
259
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("✅ Supervisor: update to v%s complete. Server ready.", remote), 0)
|
|
221
260
|
u.log.Info("Update complete: v%s → v%s", local, remote)
|
|
222
261
|
|
|
223
262
|
// Reset start time for min uptime tracking
|
|
224
263
|
u.startAt = time.Now()
|
|
225
264
|
}
|
|
226
265
|
|
|
266
|
+
func (u *Updater) verifyUpdatedMCPServerReady(ctx context.Context, remote, local string, pid int) bool {
|
|
267
|
+
u.state.Transition(updateScopeMCP, updatePhaseVerifying, remote, local, "")
|
|
268
|
+
if u.mcp.WaitForReady(ctx, mcpUpdateReadyPollInterval, mcpUpdateReadyTimeout) {
|
|
269
|
+
u.log.Info("Updated MCP server ready (PID %d)", pid)
|
|
270
|
+
return true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
errMsg := fmt.Sprintf("updated MCP server did not become ready within %v after restart (pid=%d)", mcpUpdateReadyTimeout, pid)
|
|
274
|
+
u.log.Error(errMsg)
|
|
275
|
+
u.state.Transition(updateScopeMCP, updatePhaseFailed, remote, local, errMsg)
|
|
276
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("🔴 Supervisor: update to v%s FAILED verification. Server did not become ready after restart.", remote), 0)
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
type githubRelease struct {
|
|
281
|
+
TagName string `json:"tag_name"`
|
|
282
|
+
Name string `json:"name"`
|
|
283
|
+
Assets []struct {
|
|
284
|
+
Name string `json:"name"`
|
|
285
|
+
URL string `json:"browser_download_url"`
|
|
286
|
+
Size int64 `json:"size"`
|
|
287
|
+
} `json:"assets"`
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func (u *Updater) getSupervisorRelease(ctx context.Context) (string, string, error) {
|
|
291
|
+
ctx2, cancel := context.WithTimeout(ctx, 20*time.Second)
|
|
292
|
+
defer cancel()
|
|
293
|
+
|
|
294
|
+
req, err := http.NewRequestWithContext(ctx2, http.MethodGet, supervisorReleaseURL, nil)
|
|
295
|
+
if err != nil {
|
|
296
|
+
return "", "", err
|
|
297
|
+
}
|
|
298
|
+
req.Header.Set("Accept", "application/vnd.github+json")
|
|
299
|
+
req.Header.Set("User-Agent", "sensorium-supervisor-updater")
|
|
300
|
+
|
|
301
|
+
resp, err := http.DefaultClient.Do(req)
|
|
302
|
+
if err != nil {
|
|
303
|
+
return "", "", err
|
|
304
|
+
}
|
|
305
|
+
defer resp.Body.Close()
|
|
306
|
+
|
|
307
|
+
if resp.StatusCode != http.StatusOK {
|
|
308
|
+
return "", "", fmt.Errorf("GitHub releases HTTP %d", resp.StatusCode)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
var release githubRelease
|
|
312
|
+
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
|
313
|
+
return "", "", err
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
assetName := supervisorAssetName()
|
|
317
|
+
for _, asset := range release.Assets {
|
|
318
|
+
if asset.Name != assetName {
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
version := strings.TrimSpace(release.Name)
|
|
323
|
+
if version == "" {
|
|
324
|
+
version = strings.TrimSpace(release.TagName)
|
|
325
|
+
}
|
|
326
|
+
if version == "" {
|
|
327
|
+
return "", "", fmt.Errorf("release version missing for %s", assetName)
|
|
328
|
+
}
|
|
329
|
+
if strings.TrimSpace(asset.URL) == "" {
|
|
330
|
+
return "", "", fmt.Errorf("release asset URL missing for %s", assetName)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return version, asset.URL, nil
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return "", "", fmt.Errorf("release asset %q not found", assetName)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
func supervisorAssetName() string {
|
|
340
|
+
suffix := ""
|
|
341
|
+
if runtime.GOOS == "windows" {
|
|
342
|
+
suffix = ".exe"
|
|
343
|
+
}
|
|
344
|
+
return fmt.Sprintf("sensorium-supervisor-%s-%s%s", runtime.GOOS, runtime.GOARCH, suffix)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func (u *Updater) getLocalSupervisorVersion() string {
|
|
348
|
+
data, err := os.ReadFile(u.cfg.Paths.SupervisorVersion)
|
|
349
|
+
if err != nil {
|
|
350
|
+
return ""
|
|
351
|
+
}
|
|
352
|
+
return strings.TrimSpace(string(data))
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
func (u *Updater) setLocalSupervisorVersion(v string) {
|
|
356
|
+
os.MkdirAll(u.cfg.DataDir, 0755)
|
|
357
|
+
if err := atomicWrite(u.cfg.Paths.SupervisorVersion, []byte(v)); err != nil {
|
|
358
|
+
u.log.Warn("Failed to write supervisor version file: %v", err)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
func (u *Updater) stagePendingSupervisorVersion(v string) error {
|
|
363
|
+
if err := os.MkdirAll(filepath.Dir(u.cfg.Paths.PendingVersion), 0755); err != nil {
|
|
364
|
+
return fmt.Errorf("create pending supervisor version dir: %w", err)
|
|
365
|
+
}
|
|
366
|
+
if err := atomicWrite(u.cfg.Paths.PendingVersion, []byte(v)); err != nil {
|
|
367
|
+
return fmt.Errorf("write pending supervisor version: %w", err)
|
|
368
|
+
}
|
|
369
|
+
return nil
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
func (u *Updater) checkSupervisorUpdate(ctx context.Context) {
|
|
373
|
+
uptime := time.Since(u.startAt)
|
|
374
|
+
if uptime < u.cfg.MinUptime {
|
|
375
|
+
u.log.Info("Deferring supervisor update — too early (uptime %v < %v)", uptime.Round(time.Second), u.cfg.MinUptime)
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
remote, downloadURL, err := u.getSupervisorRelease(ctx)
|
|
380
|
+
if err != nil {
|
|
381
|
+
u.log.Warn("Failed to check supervisor release: %v", err)
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
local := u.getLocalSupervisorVersion()
|
|
386
|
+
if local == "" {
|
|
387
|
+
u.log.Info("No local supervisor version recorded — storing %s", remote)
|
|
388
|
+
u.setLocalSupervisorVersion(remote)
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if local == remote {
|
|
393
|
+
u.log.Debug("Supervisor updater: version %s is up to date", local)
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
u.log.Info("Supervisor update available: %s → %s", local, remote)
|
|
398
|
+
coordLock, ok := AcquireUpdateCoordinatorLock(u.cfg.Paths.UpdateApplyLock, updateScopeSupervisor, u.log)
|
|
399
|
+
if !ok {
|
|
400
|
+
u.log.Info("Deferring supervisor binary update %s → %s due to active update apply lock", local, remote)
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
defer coordLock.Release()
|
|
404
|
+
|
|
405
|
+
markFailed := func(err error) {
|
|
406
|
+
u.state.Transition(updateScopeSupervisor, updatePhaseFailed, remote, local, err.Error())
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("⚙️ Supervisor: updating binary %s → %s. Grace period %v...", local, remote, u.cfg.GracePeriod), 0)
|
|
410
|
+
|
|
411
|
+
select {
|
|
412
|
+
case <-ctx.Done():
|
|
413
|
+
markFailed(ctx.Err())
|
|
414
|
+
return
|
|
415
|
+
case <-time.After(u.cfg.GracePeriod):
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if err := u.downloadSupervisorBinary(ctx, downloadURL); err != nil {
|
|
419
|
+
markFailed(err)
|
|
420
|
+
u.log.Error("Supervisor binary download failed: %v", err)
|
|
421
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("🔴 Supervisor: binary update to %s failed during download.", remote), 0)
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if err := u.stagePendingSupervisorVersion(remote); err != nil {
|
|
426
|
+
_ = os.Remove(u.cfg.Paths.PendingBinary)
|
|
427
|
+
markFailed(err)
|
|
428
|
+
u.log.Error("Failed to stage supervisor version %s: %v", remote, err)
|
|
429
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("🔴 Supervisor: binary update to %s failed during staging.", remote), 0)
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
u.state.Transition(updateScopeSupervisor, updatePhaseStaged, remote, local, "")
|
|
433
|
+
notifyUpdaterOperator(u.cfg, u.log, fmt.Sprintf("⚙️ Supervisor: downloaded %s. Restarting supervisor to apply update...", remote), 0)
|
|
434
|
+
|
|
435
|
+
isService, err := isWindowsService()
|
|
436
|
+
if err != nil {
|
|
437
|
+
markFailed(err)
|
|
438
|
+
u.log.Error("Failed to detect service mode for restart: %v", err)
|
|
439
|
+
notifyUpdaterOperator(u.cfg, u.log, "🔴 Supervisor: update downloaded but service detection failed.", 0)
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
u.state.Transition(updateScopeSupervisor, updatePhaseRestarting, remote, local, "")
|
|
443
|
+
|
|
444
|
+
if isService {
|
|
445
|
+
if err := scheduleServiceRestartForUpdate(u.log); err != nil {
|
|
446
|
+
markFailed(err)
|
|
447
|
+
u.log.Error("Failed to schedule service restart: %v", err)
|
|
448
|
+
notifyUpdaterOperator(u.cfg, u.log, "🔴 Supervisor: update downloaded but service restart scheduling failed.", 0)
|
|
449
|
+
}
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if err := signalSelf(syscall.SIGTERM); err != nil {
|
|
454
|
+
markFailed(err)
|
|
455
|
+
u.log.Error("Failed to signal supervisor for restart: %v", err)
|
|
456
|
+
notifyUpdaterOperator(u.cfg, u.log, "🔴 Supervisor: update downloaded but restart signal failed.", 0)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
func (u *Updater) downloadSupervisorBinary(ctx context.Context, downloadURL string) error {
|
|
461
|
+
if err := os.MkdirAll(u.cfg.Paths.BinaryDir, 0755); err != nil {
|
|
462
|
+
return fmt.Errorf("create binary dir: %w", err)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
tmpPath := u.cfg.Paths.PendingBinary + ".download"
|
|
466
|
+
defer os.Remove(tmpPath)
|
|
467
|
+
|
|
468
|
+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
|
|
469
|
+
if err != nil {
|
|
470
|
+
return err
|
|
471
|
+
}
|
|
472
|
+
req.Header.Set("User-Agent", "sensorium-supervisor-updater")
|
|
473
|
+
|
|
474
|
+
resp, err := http.DefaultClient.Do(req)
|
|
475
|
+
if err != nil {
|
|
476
|
+
return err
|
|
477
|
+
}
|
|
478
|
+
defer resp.Body.Close()
|
|
479
|
+
|
|
480
|
+
if resp.StatusCode != http.StatusOK {
|
|
481
|
+
return fmt.Errorf("download HTTP %d", resp.StatusCode)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0755)
|
|
485
|
+
if err != nil {
|
|
486
|
+
return err
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
written, copyErr := io.Copy(f, resp.Body)
|
|
490
|
+
closeErr := f.Close()
|
|
491
|
+
if copyErr != nil {
|
|
492
|
+
return copyErr
|
|
493
|
+
}
|
|
494
|
+
if closeErr != nil {
|
|
495
|
+
return closeErr
|
|
496
|
+
}
|
|
497
|
+
if written <= 0 {
|
|
498
|
+
return fmt.Errorf("downloaded empty binary")
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
info, err := os.Stat(tmpPath)
|
|
502
|
+
if err != nil {
|
|
503
|
+
return err
|
|
504
|
+
}
|
|
505
|
+
if info.Size() <= 0 {
|
|
506
|
+
return fmt.Errorf("downloaded binary has invalid size %d", info.Size())
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if err := os.Remove(u.cfg.Paths.PendingBinary); err != nil && !os.IsNotExist(err) {
|
|
510
|
+
return err
|
|
511
|
+
}
|
|
512
|
+
if err := os.Rename(tmpPath, u.cfg.Paths.PendingBinary); err != nil {
|
|
513
|
+
return err
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
u.log.Info("Supervisor binary downloaded to %s (%d bytes)", u.cfg.Paths.PendingBinary, info.Size())
|
|
517
|
+
return nil
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
func signalSelf(sig os.Signal) error {
|
|
521
|
+
proc, err := os.FindProcess(os.Getpid())
|
|
522
|
+
if err != nil {
|
|
523
|
+
return err
|
|
524
|
+
}
|
|
525
|
+
return proc.Signal(sig)
|
|
526
|
+
}
|
|
527
|
+
|
|
227
528
|
func (u *Updater) killServer() {
|
|
228
529
|
u.log.Info("Updater: stopping current MCP server for update")
|
|
229
530
|
pid, err := ReadPIDFile(u.cfg.Paths.ServerPID)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
"time"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func TestVerifyUpdatedMCPServerReady_FailureSetsFailedStateAndNoSuccessMessage(t *testing.T) {
|
|
12
|
+
dir := t.TempDir()
|
|
13
|
+
log := NewLogger(filepath.Join(dir, "test.log"))
|
|
14
|
+
defer log.Close()
|
|
15
|
+
|
|
16
|
+
cfg := Config{
|
|
17
|
+
DataDir: dir,
|
|
18
|
+
Paths: Paths{
|
|
19
|
+
UpdateState: filepath.Join(dir, "update-state.json"),
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
u := NewUpdater(cfg, NewMCPClient(1, ""), log)
|
|
24
|
+
u.state = NewUpdateStateStore(cfg.Paths.UpdateState, log)
|
|
25
|
+
|
|
26
|
+
origNotify := notifyUpdaterOperator
|
|
27
|
+
origPoll := mcpUpdateReadyPollInterval
|
|
28
|
+
origTimeout := mcpUpdateReadyTimeout
|
|
29
|
+
defer func() {
|
|
30
|
+
notifyUpdaterOperator = origNotify
|
|
31
|
+
mcpUpdateReadyPollInterval = origPoll
|
|
32
|
+
mcpUpdateReadyTimeout = origTimeout
|
|
33
|
+
}()
|
|
34
|
+
|
|
35
|
+
mcpUpdateReadyPollInterval = 1 * time.Millisecond
|
|
36
|
+
mcpUpdateReadyTimeout = 5 * time.Millisecond
|
|
37
|
+
|
|
38
|
+
var messages []string
|
|
39
|
+
notifyUpdaterOperator = func(_ Config, _ *Logger, text string, _ int) {
|
|
40
|
+
messages = append(messages, text)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ok := u.verifyUpdatedMCPServerReady(context.Background(), "2.0.0", "1.0.0", 4242)
|
|
44
|
+
if ok {
|
|
45
|
+
t.Fatal("expected verification to fail")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
state, err := u.state.Load()
|
|
49
|
+
if err != nil {
|
|
50
|
+
t.Fatalf("load update state: %v", err)
|
|
51
|
+
}
|
|
52
|
+
if state.Phase != updatePhaseFailed {
|
|
53
|
+
t.Fatalf("state phase = %q, want %q", state.Phase, updatePhaseFailed)
|
|
54
|
+
}
|
|
55
|
+
if !strings.Contains(state.LastError, "did not become ready") {
|
|
56
|
+
t.Fatalf("last error = %q, want readiness failure detail", state.LastError)
|
|
57
|
+
}
|
|
58
|
+
if len(messages) == 0 {
|
|
59
|
+
t.Fatal("expected failure notification message")
|
|
60
|
+
}
|
|
61
|
+
if strings.Contains(messages[len(messages)-1], "complete") {
|
|
62
|
+
t.Fatalf("unexpected success message: %q", messages[len(messages)-1])
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env pwsh
|
|
2
|
-
<#
|
|
3
|
-
.SYNOPSIS
|
|
4
|
-
Build and install the sensorium-supervisor Go binary.
|
|
5
|
-
.DESCRIPTION
|
|
6
|
-
Compiles the Go supervisor and places it in ~/.remote-copilot-mcp/bin/.
|
|
7
|
-
Requires Go 1.22+ installed and on PATH.
|
|
8
|
-
.PARAMETER Force
|
|
9
|
-
Rebuild even if the binary already exists.
|
|
10
|
-
#>
|
|
11
|
-
param(
|
|
12
|
-
[switch]$Force
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
$ErrorActionPreference = "Stop"
|
|
16
|
-
|
|
17
|
-
$DataDir = Join-Path $env:USERPROFILE ".remote-copilot-mcp"
|
|
18
|
-
$BinDir = Join-Path $DataDir "bin"
|
|
19
|
-
$Binary = Join-Path $BinDir "sensorium-supervisor.exe"
|
|
20
|
-
|
|
21
|
-
# Find the supervisor source directory (relative to this script)
|
|
22
|
-
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
23
|
-
$SupervisorDir = Join-Path (Split-Path -Parent $ScriptDir) "supervisor"
|
|
24
|
-
|
|
25
|
-
if (-not (Test-Path (Join-Path $SupervisorDir "go.mod"))) {
|
|
26
|
-
Write-Error "Cannot find supervisor source at $SupervisorDir"
|
|
27
|
-
exit 1
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
# Check if Go is available
|
|
31
|
-
$goExe = Get-Command go -ErrorAction SilentlyContinue
|
|
32
|
-
if (-not $goExe) {
|
|
33
|
-
Write-Host "Go is not installed. Install from https://go.dev/dl/ (requires Go 1.22+)" -ForegroundColor Red
|
|
34
|
-
exit 1
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
# Check version
|
|
38
|
-
$goVersion = (go version) -replace 'go version go', '' -replace ' .*', ''
|
|
39
|
-
Write-Host "Found Go $goVersion"
|
|
40
|
-
|
|
41
|
-
# Skip build if binary exists and is newer than source (unless -Force)
|
|
42
|
-
if (-not $Force -and (Test-Path $Binary)) {
|
|
43
|
-
$binaryTime = (Get-Item $Binary).LastWriteTime
|
|
44
|
-
$sourceFiles = Get-ChildItem $SupervisorDir -Filter "*.go"
|
|
45
|
-
$newestSource = ($sourceFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1).LastWriteTime
|
|
46
|
-
if ($binaryTime -gt $newestSource) {
|
|
47
|
-
Write-Host "sensorium-supervisor is up to date ($Binary)"
|
|
48
|
-
exit 0
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# Ensure bin directory exists
|
|
53
|
-
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
|
|
54
|
-
|
|
55
|
-
Write-Host "Building sensorium-supervisor..."
|
|
56
|
-
Push-Location $SupervisorDir
|
|
57
|
-
try {
|
|
58
|
-
go build -o $Binary .
|
|
59
|
-
if ($LASTEXITCODE -ne 0) {
|
|
60
|
-
Write-Error "Go build failed"
|
|
61
|
-
exit 1
|
|
62
|
-
}
|
|
63
|
-
} finally {
|
|
64
|
-
Pop-Location
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
Write-Host "Installed: $Binary" -ForegroundColor Green
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# Build and install the sensorium-supervisor Go binary.
|
|
3
|
-
# Requires Go 1.22+ installed and on PATH.
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
FORCE="${1:-}"
|
|
7
|
-
DATA_DIR="$HOME/.remote-copilot-mcp"
|
|
8
|
-
BIN_DIR="$DATA_DIR/bin"
|
|
9
|
-
BINARY="$BIN_DIR/sensorium-supervisor"
|
|
10
|
-
|
|
11
|
-
# Find supervisor source relative to this script
|
|
12
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
-
SUPERVISOR_DIR="$(dirname "$SCRIPT_DIR")/supervisor"
|
|
14
|
-
|
|
15
|
-
if [ ! -f "$SUPERVISOR_DIR/go.mod" ]; then
|
|
16
|
-
echo "ERROR: Cannot find supervisor source at $SUPERVISOR_DIR" >&2
|
|
17
|
-
exit 1
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# Check Go is available
|
|
21
|
-
if ! command -v go >/dev/null 2>&1; then
|
|
22
|
-
echo "ERROR: Go is not installed. Install from https://go.dev/dl/ (requires Go 1.22+)" >&2
|
|
23
|
-
exit 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
echo "Found $(go version)"
|
|
27
|
-
|
|
28
|
-
# Skip if binary is newer than source (unless --force)
|
|
29
|
-
if [ "$FORCE" != "--force" ] && [ -f "$BINARY" ]; then
|
|
30
|
-
NEWEST_SRC=$(find "$SUPERVISOR_DIR" -name '*.go' -newer "$BINARY" 2>/dev/null | head -1)
|
|
31
|
-
if [ -z "$NEWEST_SRC" ]; then
|
|
32
|
-
echo "sensorium-supervisor is up to date ($BINARY)"
|
|
33
|
-
exit 0
|
|
34
|
-
fi
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
mkdir -p "$BIN_DIR"
|
|
38
|
-
|
|
39
|
-
echo "Building sensorium-supervisor..."
|
|
40
|
-
cd "$SUPERVISOR_DIR"
|
|
41
|
-
go build -o "$BINARY" .
|
|
42
|
-
|
|
43
|
-
echo "Installed: $BINARY"
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env pwsh
|
|
2
|
-
<#
|
|
3
|
-
.SYNOPSIS
|
|
4
|
-
Launch sensorium-supervisor. Builds automatically if needed.
|
|
5
|
-
.DESCRIPTION
|
|
6
|
-
Replaces update-watcher.ps1. Builds the Go supervisor if it doesn't exist,
|
|
7
|
-
then runs it. All environment variables (MCP_HTTP_PORT, TELEGRAM_TOKEN, etc.)
|
|
8
|
-
are passed through to the supervisor process.
|
|
9
|
-
.PARAMETER Mode
|
|
10
|
-
Watcher mode: production or development. Maps to WATCHER_MODE env var.
|
|
11
|
-
.PARAMETER Build
|
|
12
|
-
Force rebuild of the supervisor binary before starting.
|
|
13
|
-
#>
|
|
14
|
-
param(
|
|
15
|
-
[ValidateSet("production", "development")]
|
|
16
|
-
[string]$Mode = "production",
|
|
17
|
-
[switch]$Build
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
$ErrorActionPreference = "Stop"
|
|
21
|
-
|
|
22
|
-
$DataDir = Join-Path $env:USERPROFILE ".remote-copilot-mcp"
|
|
23
|
-
$BinDir = Join-Path $DataDir "bin"
|
|
24
|
-
$Binary = Join-Path $BinDir "sensorium-supervisor.exe"
|
|
25
|
-
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
26
|
-
|
|
27
|
-
# Build if missing or requested
|
|
28
|
-
if ($Build -or -not (Test-Path $Binary)) {
|
|
29
|
-
$installScript = Join-Path $ScriptDir "install-supervisor.ps1"
|
|
30
|
-
if ($Build) {
|
|
31
|
-
& $installScript -Force
|
|
32
|
-
} else {
|
|
33
|
-
& $installScript
|
|
34
|
-
}
|
|
35
|
-
if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
# Set WATCHER_MODE if not already set
|
|
39
|
-
if (-not $env:WATCHER_MODE) {
|
|
40
|
-
$env:WATCHER_MODE = $Mode
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# Launch supervisor
|
|
44
|
-
Write-Host "Starting sensorium-supervisor ($Mode mode)..."
|
|
45
|
-
& $Binary
|
|
46
|
-
exit $LASTEXITCODE
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# Launch sensorium-supervisor. Builds automatically if needed.
|
|
3
|
-
# Replaces update-watcher.ps1 on Unix systems.
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
MODE="${1:-production}"
|
|
7
|
-
DATA_DIR="$HOME/.remote-copilot-mcp"
|
|
8
|
-
BIN_DIR="$DATA_DIR/bin"
|
|
9
|
-
BINARY="$BIN_DIR/sensorium-supervisor"
|
|
10
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
-
|
|
12
|
-
# Build if missing
|
|
13
|
-
if [ ! -f "$BINARY" ]; then
|
|
14
|
-
"$SCRIPT_DIR/install-supervisor.sh"
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
export WATCHER_MODE="${WATCHER_MODE:-$MODE}"
|
|
18
|
-
|
|
19
|
-
echo "Starting sensorium-supervisor ($WATCHER_MODE mode)..."
|
|
20
|
-
exec "$BINARY"
|