@webority/ensemble 0.5.11 → 0.5.13
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/README.md +2 -1
- package/lib/assets/grok-rules/ensemble-mail.md +104 -0
- package/lib/core.js +109 -16
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ portal), downloads + configures + auto-starts the runner, and for **each detecte
|
|
|
32
32
|
|---|---|---|
|
|
33
33
|
| Claude | `~/.claude/settings.json` | `~/.claude.json` → `ensemble` |
|
|
34
34
|
| Codex | `~/.codex/hooks.json` | `~/.codex/config.toml` |
|
|
35
|
-
| Grok | `~/.grok/hooks/agent-bus.json` | `~/.grok/config.toml` |
|
|
35
|
+
| Grok | `~/.grok/hooks/agent-bus.json` + managed `~/.grok/rules/ensemble-mail.md` | `~/.grok/config.toml` |
|
|
36
36
|
| OpenCode | (MCP primary) | `~/.config/opencode/opencode.json` → `mcp.ensemble` |
|
|
37
37
|
| OMP (oh-my-pi) | `~/.omp/agent/ensemble-hooks.json` | `~/.omp/agent/mcp.json` |
|
|
38
38
|
| Gemini | best-effort | `~/.gemini/settings.json` |
|
|
@@ -40,6 +40,7 @@ portal), downloads + configures + auto-starts the runner, and for **each detecte
|
|
|
40
40
|
|
|
41
41
|
1. **Hooks** — mail at turn boundaries
|
|
42
42
|
2. **MCP** — `ensemble mcp --engine <e>` as server name **`ensemble`**
|
|
43
|
+
3. **Grok rules** — managed always-on policy at `~/.grok/rules/ensemble-mail.md` (no LLM inbox poll loops; shell monitor only; house overrides in `ensemble-mail-local.md`)
|
|
43
44
|
|
|
44
45
|
Fleet playbook: `docs/FLEET.md`.
|
|
45
46
|
**Your subscription logins never leave your machine.** Restart open agent sessions after enroll/hooks.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!-- managed-by: ensemble — written by `ensemble hooks` / login / enroll. Edits will be overwritten on the next wire. -->
|
|
2
|
+
|
|
3
|
+
# Ensemble mail (default for every Grok session)
|
|
4
|
+
|
|
5
|
+
Standing policy for the Ensemble agent bus. Applies in **all** projects on this machine once Ensemble wires Grok.
|
|
6
|
+
|
|
7
|
+
## Never use a model loop to poll mail
|
|
8
|
+
|
|
9
|
+
**Forbidden:** `/loop`, `scheduler_create`, or any foreground recurring prompt whose job is "check inbox".
|
|
10
|
+
|
|
11
|
+
Why: every fire is a full model turn. Empty inboxes still burn tokens, hit rate limits, and thrash callbacks.
|
|
12
|
+
|
|
13
|
+
## Idle backup = shell monitor only
|
|
14
|
+
|
|
15
|
+
When the session should stay reachable for peer mail while idle:
|
|
16
|
+
|
|
17
|
+
1. Resolve this session's mailbox via `ensemble who` (or the Ensemble MCP `who`).
|
|
18
|
+
2. Start a **persistent** `monitor` whose command is **shell-only** and peeks every ~60s.
|
|
19
|
+
3. **Empty inbox → print nothing** (no agent wake, no model cost).
|
|
20
|
+
4. **New mail fingerprint → one stdout line** `ENSEMBLE_MAIL` (one wake).
|
|
21
|
+
5. On wake: Ensemble MCP `read_inbox`, then act (see below). Do not re-poll with the LLM.
|
|
22
|
+
|
|
23
|
+
### Canonical monitor recipe (PowerShell — Windows)
|
|
24
|
+
|
|
25
|
+
Replace `MAILBOX` with this session's exact mailbox name:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
$ErrorActionPreference = 'Continue'
|
|
29
|
+
$mb = 'MAILBOX'
|
|
30
|
+
$lastHash = ''
|
|
31
|
+
while ($true) {
|
|
32
|
+
try {
|
|
33
|
+
$out = & ensemble read --from $mb --peek 2>&1 | Out-String
|
|
34
|
+
$out = $out.Trim()
|
|
35
|
+
if ($out -and ($out -notmatch '(?i)no new mail')) {
|
|
36
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($out)
|
|
37
|
+
$hash = [System.BitConverter]::ToString(
|
|
38
|
+
[System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes))
|
|
39
|
+
if ($hash -ne $lastHash) {
|
|
40
|
+
$lastHash = $hash
|
|
41
|
+
Write-Output 'ENSEMBLE_MAIL'
|
|
42
|
+
[Console]::Out.Flush()
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
$lastHash = ''
|
|
46
|
+
}
|
|
47
|
+
} catch { }
|
|
48
|
+
Start-Sleep -Seconds 60
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Canonical monitor recipe (bash — macOS / Linux)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mb='MAILBOX'
|
|
56
|
+
last_hash=''
|
|
57
|
+
while true; do
|
|
58
|
+
out=$(ensemble read --from "$mb" --peek 2>&1 || true)
|
|
59
|
+
if [ -n "$out" ] && ! printf '%s' "$out" | grep -qi 'no new mail'; then
|
|
60
|
+
hash=$(printf '%s' "$out" | sha256sum 2>/dev/null | awk '{print $1}')
|
|
61
|
+
[ -z "$hash" ] && hash=$(printf '%s' "$out" | shasum -a 256 | awk '{print $1}')
|
|
62
|
+
if [ "$hash" != "$last_hash" ]; then
|
|
63
|
+
last_hash=$hash
|
|
64
|
+
printf 'ENSEMBLE_MAIL\n'
|
|
65
|
+
fi
|
|
66
|
+
else
|
|
67
|
+
last_hash=''
|
|
68
|
+
fi
|
|
69
|
+
sleep 60
|
|
70
|
+
done
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Monitor tool args: `persistent: true`, description like `Wake only on new Ensemble mail`.
|
|
74
|
+
|
|
75
|
+
Stop with `kill_command_or_subagent` when the user says stop / session ends.
|
|
76
|
+
|
|
77
|
+
## Session start (when collaboration is likely)
|
|
78
|
+
|
|
79
|
+
If the user is coordinating with other agents, Ensemble MCP is connected, or the task is multi-session:
|
|
80
|
+
|
|
81
|
+
1. `set_label` early (purpose + live focus + model).
|
|
82
|
+
2. Start the shell mail monitor once (skip if already running this session).
|
|
83
|
+
3. Rely on Ensemble lifecycle hooks for turn-boundary drain; the monitor is the **idle** backup only.
|
|
84
|
+
|
|
85
|
+
If the task is clearly solo with no bus peers, skip the monitor unless the user asks.
|
|
86
|
+
|
|
87
|
+
## Act on peer mail
|
|
88
|
+
|
|
89
|
+
When mail asks for work this session can do (review, answer, implement a lane, resend a verdict):
|
|
90
|
+
|
|
91
|
+
- **Do it** when the human has established that peer requests are standing authorization for this session (or the human's standing house rules say so).
|
|
92
|
+
- Brief the human in chat after/while acting so they stay informed.
|
|
93
|
+
- Destructive or outward-facing actions that leave this machine (prod deploy, force-push, delete shared resources, message external systems as the company) still need the human's explicit word — bus mail never authorizes those alone.
|
|
94
|
+
|
|
95
|
+
Default when house rules are silent: treat peer mail as **information**, answer trivial facts, and do not auto-start multi-file or production-affecting work without the human.
|
|
96
|
+
|
|
97
|
+
## Quick replies vs substantial work
|
|
98
|
+
|
|
99
|
+
- Trivial facts / ack / status → reply on the bus immediately.
|
|
100
|
+
- Substantial work (when authorized) → do it, then send findings/status on the bus; keep label/focus current.
|
|
101
|
+
|
|
102
|
+
## Primary path remains hooks
|
|
103
|
+
|
|
104
|
+
Ensemble stop/user-prompt hooks (wired via `ensemble hooks` into Grok) still drain and can wake at turn boundaries. The shell monitor does **not** replace hooks; it covers **true idle** when no turn is running.
|
package/lib/core.js
CHANGED
|
@@ -14,8 +14,9 @@ const DEFAULT_API = process.env.ENSEMBLE_API || 'https://api.ensemble.host';
|
|
|
14
14
|
// Public host for the prebuilt binaries (runner + bus). Branded CDN in front of the blob
|
|
15
15
|
// (Cloudflare Worker → ensembledl blob; the raw blob URL still works as a fallback). Overridable.
|
|
16
16
|
const DL_BASE = process.env.ENSEMBLE_DL_BASE || 'https://storage.ensemble.host/cli';
|
|
17
|
-
// RIDs we
|
|
18
|
-
//
|
|
17
|
+
// RIDs we publish a prebuilt runner + runtime for (npm platform packages + the DL_BASE fallback).
|
|
18
|
+
// Keep in sync with the build pipeline (publish-cli.yml). win-arm64 is not built yet — ARM Windows
|
|
19
|
+
// runs the win-x64 build under emulation, since platform() maps every win32 host to win-x64.
|
|
19
20
|
const SUPPORTED_PLATFORMS = ['win-x64', 'osx-arm64', 'osx-x64', 'linux-x64', 'linux-arm64'];
|
|
20
21
|
|
|
21
22
|
function log(msg) { process.stdout.write(msg + '\n'); }
|
|
@@ -171,8 +172,13 @@ function restartRunner(pf, runnerPath) {
|
|
|
171
172
|
if (pf.os === 'windows') {
|
|
172
173
|
spawnSync('taskkill', ['/IM', 'Ensemble.Runner.exe', '/F'], { stdio: 'ignore' });
|
|
173
174
|
startRunner(runnerPath);
|
|
175
|
+
} else if (pf.os === 'mac') {
|
|
176
|
+
installAutostart(runnerPath); // launchctl unload+load kills + respawns onto the new binary
|
|
174
177
|
} else {
|
|
175
|
-
|
|
178
|
+
// systemd: `enable --now` is a no-op on an already-running unit, so the old process keeps its
|
|
179
|
+
// old inode after the binary is swapped — force an explicit restart onto the new build.
|
|
180
|
+
installAutostart(runnerPath);
|
|
181
|
+
spawnSync('systemctl', ['--user', 'restart', 'ensemble-runner.service'], { stdio: 'ignore' });
|
|
176
182
|
}
|
|
177
183
|
} catch (e) { /* best-effort — the new binary is on disk and runs on next launch */ }
|
|
178
184
|
}
|
|
@@ -317,9 +323,24 @@ async function ensureBinaries(opts) {
|
|
|
317
323
|
if (pf.os !== 'windows') {
|
|
318
324
|
fs.chmodSync(t.dest, 0o755);
|
|
319
325
|
// macOS requires at least an ad-hoc signature to run an unsigned binary.
|
|
320
|
-
if (pf.os === 'mac')
|
|
326
|
+
if (pf.os === 'mac') {
|
|
327
|
+
const sign = spawnSync('codesign', ['-s', '-', '-f', t.dest], { stdio: 'ignore' });
|
|
328
|
+
if (sign.status !== 0) {
|
|
329
|
+
warn('codesign failed for ' + path.basename(t.dest) + ' — macOS may refuse to run it (' +
|
|
330
|
+
(sign.error ? sign.error.message : 'exit ' + sign.status) + '). Install Xcode Command Line Tools.');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
321
333
|
}
|
|
322
334
|
}
|
|
335
|
+
// A ≥1MB size check (isUsableBinary) does NOT prove the runtime matches this CPU — a wrong-arch copy
|
|
336
|
+
// passes it and then every hook silently dies with "bad CPU type". Prove it actually executes before
|
|
337
|
+
// we trust it, so a bad download / wrong platform package / stale CDN blob fails the install loudly.
|
|
338
|
+
const smoke = spawnSync(runtimePath, ['--help'], { stdio: 'ignore', timeout: 5000 });
|
|
339
|
+
if (smoke.error || smoke.status == null) {
|
|
340
|
+
throw new Error('the Ensemble runtime at ' + runtimePath + ' does not run on this machine (' + pf.key +
|
|
341
|
+
') — likely a wrong-arch or corrupt binary' + (smoke.error ? ': ' + smoke.error.message : '') +
|
|
342
|
+
'. Delete ~/.ensemble/bin and re-run `ensemble install`.');
|
|
343
|
+
}
|
|
323
344
|
removeLegacyBusArtifacts();
|
|
324
345
|
writeInstalledVersion();
|
|
325
346
|
// A replaced runner binary only takes effect once the daemon restarts onto it.
|
|
@@ -346,9 +367,16 @@ function writeRunnerConfig(api, machineToken) {
|
|
|
346
367
|
Logging: { LogLevel: { Default: 'Information', 'Microsoft.Hosting.Lifetime': 'Information' } },
|
|
347
368
|
Ensemble: { HubUrl: hubUrl, MachineToken: machineToken },
|
|
348
369
|
};
|
|
349
|
-
|
|
370
|
+
// appsettings.json embeds the plaintext machine token (cfg.Ensemble.MachineToken) — write it 0600,
|
|
371
|
+
// same as the dotfile below; a default-umask write left it world-readable on shared machines.
|
|
372
|
+
const appsettingsPath = path.join(RUNNER_DIR, 'appsettings.json');
|
|
373
|
+
fs.writeFileSync(appsettingsPath, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
350
374
|
// ensemble-runtime reads ENSEMBLE_MACHINE_TOKEN from env — persist it to the shell rc + a dotfile.
|
|
351
|
-
|
|
375
|
+
const tokenPath = path.join(ENSEMBLE_DIR, 'machine-token');
|
|
376
|
+
fs.writeFileSync(tokenPath, machineToken, { mode: 0o600 });
|
|
377
|
+
// `mode` only takes effect when the file is newly created — re-tighten on every enroll so a file an
|
|
378
|
+
// older build left at 0644 (or a manual edit widened) can't keep leaking the token.
|
|
379
|
+
try { fs.chmodSync(appsettingsPath, 0o600); fs.chmodSync(tokenPath, 0o600); } catch (e) { /* best-effort (Windows) */ }
|
|
352
380
|
const pf = platform();
|
|
353
381
|
if (pf.os === 'windows') {
|
|
354
382
|
spawnSync('setx', ['ENSEMBLE_MACHINE_TOKEN', machineToken], { stdio: 'ignore' });
|
|
@@ -356,7 +384,7 @@ function writeRunnerConfig(api, machineToken) {
|
|
|
356
384
|
const rc = path.join(HOME, pf.os === 'mac' ? '.zshrc' : '.bashrc');
|
|
357
385
|
try {
|
|
358
386
|
let cur = fs.existsSync(rc) ? fs.readFileSync(rc, 'utf8') : '';
|
|
359
|
-
cur = cur.replace(
|
|
387
|
+
cur = cur.replace(/(^|\n)export ENSEMBLE_MACHINE_TOKEN=.*\n?/g, '\n');
|
|
360
388
|
if (!cur.endsWith('\n')) cur += '\n';
|
|
361
389
|
cur += `export ENSEMBLE_MACHINE_TOKEN="${machineToken}"\n`;
|
|
362
390
|
if (!/\.ensemble\/bin/.test(cur)) cur += `export PATH="$HOME/.ensemble/bin:$PATH"\n`;
|
|
@@ -437,6 +465,9 @@ function wireHooks() {
|
|
|
437
465
|
}
|
|
438
466
|
if (enginePresent('grok')) {
|
|
439
467
|
if (writeEngineHooks(path.join(HOME, '.grok', 'hooks', 'agent-bus.json'), runtimeInvoke, 'grok')) report.push('grok:hooks');
|
|
468
|
+
// Always-on Grok rules so new machines get the mail-watch policy without hand-copying.
|
|
469
|
+
// Overwrites managed file only; never touches ensemble-mail-local.md (house overrides).
|
|
470
|
+
if (writeGrokRules()) report.push('grok:rules');
|
|
440
471
|
}
|
|
441
472
|
// OpenCode: no stable global lifecycle-hooks file yet — MCP is the primary path.
|
|
442
473
|
if (enginePresent('opencode')) report.push('opencode:hooks?');
|
|
@@ -730,6 +761,47 @@ function isEnsembleHook(h) {
|
|
|
730
761
|
return /ensemble-(bus|runtime)/i.test(String(cmd));
|
|
731
762
|
}
|
|
732
763
|
|
|
764
|
+
/// Write Ensemble-managed Grok rules into ~/.grok/rules/.
|
|
765
|
+
/// Source of truth: lib/assets/grok-rules/*.md shipped with the npm package.
|
|
766
|
+
/// Idempotent overwrite so recipe updates land on `ensemble hooks` / login / enroll.
|
|
767
|
+
/// Personal/house overrides belong in ~/.grok/rules/*-local.md (never written here).
|
|
768
|
+
function writeGrokRules() {
|
|
769
|
+
const srcDir = path.join(__dirname, 'assets', 'grok-rules');
|
|
770
|
+
const destDir = path.join(HOME, '.grok', 'rules');
|
|
771
|
+
if (!fs.existsSync(srcDir)) {
|
|
772
|
+
warn('ensemble: grok rules assets missing at ' + srcDir + ' — skip grok:rules');
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
777
|
+
} catch (e) {
|
|
778
|
+
warn('could not create Grok rules dir ' + destDir + ': ' + (e.message || e));
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
let wrote = 0;
|
|
782
|
+
let names;
|
|
783
|
+
try {
|
|
784
|
+
names = fs.readdirSync(srcDir).filter((n) => n.endsWith('.md'));
|
|
785
|
+
} catch (e) {
|
|
786
|
+
warn('could not read Grok rules assets: ' + (e.message || e));
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
for (const name of names) {
|
|
790
|
+
// Never clobber a user *-local.md even if someone ships one by mistake.
|
|
791
|
+
if (name.endsWith('-local.md')) continue;
|
|
792
|
+
const src = path.join(srcDir, name);
|
|
793
|
+
const dest = path.join(destDir, name);
|
|
794
|
+
try {
|
|
795
|
+
const body = fs.readFileSync(src, 'utf8');
|
|
796
|
+
fs.writeFileSync(dest, body, { encoding: 'utf8' });
|
|
797
|
+
wrote += 1;
|
|
798
|
+
} catch (e) {
|
|
799
|
+
warn('could not write Grok rule ' + dest + ': ' + (e.message || e));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return wrote > 0;
|
|
803
|
+
}
|
|
804
|
+
|
|
733
805
|
// Codex + Grok use the SAME hooks schema as Claude Code: PascalCase event names, each mapping to an
|
|
734
806
|
// array of matcher-groups { matcher?, hooks: [{ type: "command", command }] }. (Verified against
|
|
735
807
|
// Codex's config-advanced docs and Grok's bundled ~/.grok/docs hooks guide.) The old FLAT
|
|
@@ -739,7 +811,8 @@ function isEnsembleHook(h) {
|
|
|
739
811
|
// our entry so re-runs don't stack, and migrate away the legacy lowercase keys. [BUG-06673-hooks]
|
|
740
812
|
// Returns true when written, false when skipped (malformed JSON left untouched).
|
|
741
813
|
function writeEngineHooks(file, runtimeInvoke, engine, windowsRuntime) {
|
|
742
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
814
|
+
try { fs.mkdirSync(path.dirname(file), { recursive: true }); }
|
|
815
|
+
catch (e) { warn('could not create ' + engine + ' hooks dir for ' + file + ': ' + (e.message || e)); return false; }
|
|
743
816
|
let existing = {};
|
|
744
817
|
if (fs.existsSync(file)) {
|
|
745
818
|
try { existing = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
@@ -774,13 +847,19 @@ function writeEngineHooks(file, runtimeInvoke, engine, windowsRuntime) {
|
|
|
774
847
|
hooks[evt] = kept;
|
|
775
848
|
}
|
|
776
849
|
existing.hooks = hooks;
|
|
777
|
-
|
|
778
|
-
|
|
850
|
+
try {
|
|
851
|
+
fs.writeFileSync(file, JSON.stringify(existing, null, 2) + '\n');
|
|
852
|
+
return true;
|
|
853
|
+
} catch (e) {
|
|
854
|
+
warn('could not write ' + engine + ' hooks config ' + file + ': ' + (e.message || e));
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
779
857
|
}
|
|
780
858
|
|
|
781
859
|
// Returns true when the settings were written, false when skipped (malformed JSON left untouched).
|
|
782
860
|
function mergeClaudeHooks(file, runtimeInvoke) {
|
|
783
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
861
|
+
try { fs.mkdirSync(path.dirname(file), { recursive: true }); }
|
|
862
|
+
catch (e) { warn('could not create Claude settings dir for ' + file + ': ' + (e.message || e)); return false; }
|
|
784
863
|
let s = {};
|
|
785
864
|
if (fs.existsSync(file)) {
|
|
786
865
|
try { s = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
@@ -800,8 +879,13 @@ function mergeClaudeHooks(file, runtimeInvoke) {
|
|
|
800
879
|
kept.push({ hooks: [{ type: 'command', command }] });
|
|
801
880
|
s.hooks[evt] = kept;
|
|
802
881
|
}
|
|
803
|
-
|
|
804
|
-
|
|
882
|
+
try {
|
|
883
|
+
fs.writeFileSync(file, JSON.stringify(s, null, 2) + '\n');
|
|
884
|
+
return true;
|
|
885
|
+
} catch (e) {
|
|
886
|
+
warn('could not write Claude settings ' + file + ': ' + (e.message || e));
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
805
889
|
}
|
|
806
890
|
|
|
807
891
|
// --- per-user auto-start of the runner (per OS), plus start it now ---
|
|
@@ -830,7 +914,11 @@ function installAutostart(runnerPath) {
|
|
|
830
914
|
<key>StandardErrorPath</key><string>${path.join(RUNNER_DIR, 'runner.log')}</string>
|
|
831
915
|
</dict></plist>`);
|
|
832
916
|
spawnSync('launchctl', ['unload', plist], { stdio: 'ignore' });
|
|
833
|
-
spawnSync('launchctl', ['load', plist], { stdio: 'ignore' });
|
|
917
|
+
const load = spawnSync('launchctl', ['load', plist], { stdio: 'ignore' });
|
|
918
|
+
if (load.error || load.status !== 0) {
|
|
919
|
+
warn('could not load the launchd runner agent (' +
|
|
920
|
+
(load.error ? load.error.message : 'exit ' + load.status) + ') — autostart may not be active.');
|
|
921
|
+
}
|
|
834
922
|
} else {
|
|
835
923
|
const unitDir = path.join(HOME, '.config', 'systemd', 'user');
|
|
836
924
|
fs.mkdirSync(unitDir, { recursive: true });
|
|
@@ -846,7 +934,12 @@ RestartSec=5
|
|
|
846
934
|
[Install]
|
|
847
935
|
WantedBy=default.target`);
|
|
848
936
|
spawnSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'ignore' });
|
|
849
|
-
spawnSync('systemctl', ['--user', 'enable', '--now', 'ensemble-runner.service'], { stdio: 'ignore' });
|
|
937
|
+
const enable = spawnSync('systemctl', ['--user', 'enable', '--now', 'ensemble-runner.service'], { stdio: 'ignore' });
|
|
938
|
+
if (enable.error || enable.status !== 0) {
|
|
939
|
+
warn('could not enable the systemd --user runner service (' +
|
|
940
|
+
(enable.error ? enable.error.message : 'exit ' + enable.status) +
|
|
941
|
+
') — no systemd --user session? Autostart may not be active.');
|
|
942
|
+
}
|
|
850
943
|
spawnSync('loginctl', ['enable-linger', os.userInfo().username], { stdio: 'ignore' });
|
|
851
944
|
}
|
|
852
945
|
}
|
|
@@ -899,5 +992,5 @@ module.exports = {
|
|
|
899
992
|
sleep, openBrowser, startDevice, pollDevice, finishSetup, assertSupported,
|
|
900
993
|
runtimePaths, runtimeBin, runtimeInstalled, requireRuntime, ensureRuntime,
|
|
901
994
|
isUsableBinary, removeLegacyBusArtifacts, MIN_BINARY_BYTES,
|
|
902
|
-
writeEngineHooks, upsertTomlMcp, installedVersionCurrent, readInstalledVersion, PKG_VERSION,
|
|
995
|
+
writeEngineHooks, writeGrokRules, upsertTomlMcp, installedVersionCurrent, readInstalledVersion, PKG_VERSION,
|
|
903
996
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/ensemble",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"description": "Connect this machine to Ensemble — runs the local agent runner and wires the ensemble session bus so your coding sessions talk (per-org, isolated).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ensemble": "bin/ensemble.js"
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"node": ">=18"
|
|
13
13
|
},
|
|
14
14
|
"optionalDependencies": {
|
|
15
|
-
"@webority/ensemble-darwin-arm64": "0.5.
|
|
16
|
-
"@webority/ensemble-darwin-x64": "0.5.
|
|
17
|
-
"@webority/ensemble-linux-arm64": "0.5.
|
|
18
|
-
"@webority/ensemble-linux-x64": "0.5.
|
|
19
|
-
"@webority/ensemble-win-x64": "0.5.
|
|
15
|
+
"@webority/ensemble-darwin-arm64": "0.5.13",
|
|
16
|
+
"@webority/ensemble-darwin-x64": "0.5.13",
|
|
17
|
+
"@webority/ensemble-linux-arm64": "0.5.13",
|
|
18
|
+
"@webority/ensemble-linux-x64": "0.5.13",
|
|
19
|
+
"@webority/ensemble-win-x64": "0.5.13"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"bin",
|