@koalaauto/agentchannel 3.12.0 → 3.13.1
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/dist/src/cli.js +42 -2
- package/dist/src/sandboxcheck.js +4 -4
- package/dist/src/service.d.ts +20 -0
- package/dist/src/service.js +97 -1
- package/dist/src/servicefix.d.ts +41 -0
- package/dist/src/servicefix.js +69 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, execSync, spawn } from 'node:child_process';
|
|
3
3
|
import { randomBytes } from 'node:crypto';
|
|
4
|
-
import { chmodSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { HELP, parseCli, UsageError } from './args.js';
|
|
@@ -1606,6 +1606,10 @@ async function runCommand(p, io) {
|
|
|
1606
1606
|
}
|
|
1607
1607
|
if (v['install-service'])
|
|
1608
1608
|
return installService(p, io, { agentName: me.name, dir, repos: repoFlags, home });
|
|
1609
|
+
// started by a service written by an early release (node on the npx cache, which no restart ever refreshes): make it `npx -y <spec>`
|
|
1610
|
+
const moved = await moveOldService(io, me.name, home);
|
|
1611
|
+
if (moved)
|
|
1612
|
+
return UPDATE_EXIT_CODE;
|
|
1609
1613
|
const runtime = str(v.runtime) ?? (me.runtime === 'codex' ? 'codex' : 'claude');
|
|
1610
1614
|
const { makeAdapter } = await import('./adapters/index.js');
|
|
1611
1615
|
const adapter = makeAdapter(runtime, { model: str(v.model) });
|
|
@@ -1768,6 +1772,9 @@ async function installService(p, io, o) {
|
|
|
1768
1772
|
}, env.ACH_SERVICE_DIR || undefined, env);
|
|
1769
1773
|
writeServiceFile(f);
|
|
1770
1774
|
const load = !env.ACH_SERVICE_DIR;
|
|
1775
|
+
// an agent that was renamed has an older service under its former name: two runners on one key. They go.
|
|
1776
|
+
const { retireSiblings } = await import('./servicefix.js');
|
|
1777
|
+
const retired = retireSiblings({ platform, dir: env.ACH_SERVICE_DIR || undefined, env, ownPath: f.path, ownHome: o.home, run: load });
|
|
1771
1778
|
let failed = '';
|
|
1772
1779
|
if (load) {
|
|
1773
1780
|
for (const c of f.start) {
|
|
@@ -1787,12 +1794,13 @@ async function installService(p, io, o) {
|
|
|
1787
1794
|
const linger = platform === 'linux' && load && !failed ? lingers(env) : true;
|
|
1788
1795
|
const logs = platform === 'linux' ? `journalctl --user -u ${basename(f.path)} -f` : join(o.home, 'logs', 'runner.log');
|
|
1789
1796
|
if (v.json) {
|
|
1790
|
-
io.out(JSON.stringify({ path: f.path, started: load && !failed, ...(failed ? { error: failed } : {}), start: f.start, stop: f.stop }, null, 2));
|
|
1797
|
+
io.out(JSON.stringify({ path: f.path, started: load && !failed, ...(failed ? { error: failed } : {}), start: f.start, stop: f.stop, retired }, null, 2));
|
|
1791
1798
|
return failed ? 1 : 0;
|
|
1792
1799
|
}
|
|
1793
1800
|
io.out([
|
|
1794
1801
|
!load ? `wrote ${f.path} (not loaded: ACH_SERVICE_DIR is set). Start it with:` : failed ? `wrote ${f.path}, but starting it failed: ${failed}\nStart it with:` : `installed and started ${f.path} (also starts at login)`,
|
|
1795
1802
|
...(!load || failed ? f.start.map((x) => ` ${x}`) : []),
|
|
1803
|
+
...retired.map((r) => `stopped and removed the older service of this agent (${r.agentName}): ${r.path}`),
|
|
1796
1804
|
`Stop it with: ${f.stop.join(' && ')}`,
|
|
1797
1805
|
`Runner output: ${logs}`,
|
|
1798
1806
|
...(linger ? [] : ['Note: it stops when you log out of this machine. To keep it running: sudo loginctl enable-linger "$USER"']),
|
|
@@ -1809,6 +1817,38 @@ function lingers(env) {
|
|
|
1809
1817
|
return true;
|
|
1810
1818
|
}
|
|
1811
1819
|
}
|
|
1820
|
+
/** The runner is the service's own process and the service is in the old form: rewrite it, have the supervisor reload it, and stop (the reload starts the new one). */
|
|
1821
|
+
async function moveOldService(io, agentName, home) {
|
|
1822
|
+
const env = io.env;
|
|
1823
|
+
const platform = (env.ACH_SERVICE_PLATFORM || process.platform);
|
|
1824
|
+
if (platform !== 'darwin' && platform !== 'linux')
|
|
1825
|
+
return false;
|
|
1826
|
+
const { migrateOldService, reloadDetached, startedByService } = await import('./servicefix.js');
|
|
1827
|
+
if (!env.ACH_SERVICE_DIR && !startedByService(agentName, platform, env))
|
|
1828
|
+
return false;
|
|
1829
|
+
const { npmSpec, whichAll, isBundledBin } = await import('./invite.js');
|
|
1830
|
+
// the new service must be able to start: without an npx to point at, the old one (stale, but running) stays as it is
|
|
1831
|
+
const beside = join(dirname(process.execPath), 'npx');
|
|
1832
|
+
const npx = whichAll('npx', env).find((x) => !isBundledBin(x)) ?? (existsSync(beside) ? beside : '');
|
|
1833
|
+
if (!npx) {
|
|
1834
|
+
io.err('[ach run] this service starts a fixed copy of the CLI from the npx cache, but there is no npx to start it with: leaving it. Run `ach run --install-service` after installing npm.');
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1837
|
+
let m;
|
|
1838
|
+
try {
|
|
1839
|
+
m = migrateOldService({ agentName, platform, dir: env.ACH_SERVICE_DIR || undefined, env, home, npx, spec: npmSpec() });
|
|
1840
|
+
}
|
|
1841
|
+
catch (e) {
|
|
1842
|
+
io.err(`[ach run] could not rewrite the service file: ${e?.message ?? e}`);
|
|
1843
|
+
return false;
|
|
1844
|
+
}
|
|
1845
|
+
if (!m)
|
|
1846
|
+
return false;
|
|
1847
|
+
io.err(`[ach run] this service started a fixed copy of the CLI from the npx cache, which no restart updates: rewrote ${m.path} to start \`npx -y ${npmSpec()}\` and reloading it`);
|
|
1848
|
+
if (!env.ACH_SERVICE_DIR)
|
|
1849
|
+
reloadDetached(m.start, env);
|
|
1850
|
+
return true;
|
|
1851
|
+
}
|
|
1812
1852
|
function isEntry() {
|
|
1813
1853
|
try {
|
|
1814
1854
|
return !!process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
package/dist/src/sandboxcheck.js
CHANGED
|
@@ -219,11 +219,11 @@ export function sandboxFix(st, host) {
|
|
|
219
219
|
}
|
|
220
220
|
if (appArmor)
|
|
221
221
|
commands.push(BWRAP_APPARMOR);
|
|
222
|
-
const
|
|
223
|
-
const
|
|
222
|
+
const en = [need.length ? `needs ${need.join(' and ')}` : '', appArmor ? 'AppArmor blocks bwrap' : ''].filter(Boolean).join(', ');
|
|
223
|
+
const zh = [need.length ? `缺 ${need.join('、')}` : '', appArmor ? 'AppArmor 拦了沙箱' : ''].filter(Boolean).join(',');
|
|
224
224
|
return {
|
|
225
|
-
en: `${name}'s sandbox cannot start
|
|
226
|
-
zh: `${name}
|
|
225
|
+
en: `${name}'s sandbox cannot start: ${en}.`,
|
|
226
|
+
zh: `${name} 的沙箱起不来:${zh}。`,
|
|
227
227
|
commands,
|
|
228
228
|
};
|
|
229
229
|
}
|
package/dist/src/service.d.ts
CHANGED
|
@@ -32,3 +32,23 @@ export declare function windowsScript(s: ServiceSpec): string;
|
|
|
32
32
|
export declare function serviceFile(s: ServiceSpec, dir?: string, env?: NodeJS.ProcessEnv): ServiceFile;
|
|
33
33
|
/** Write the unit file (mode 0644; it holds no secrets). */
|
|
34
34
|
export declare function writeServiceFile(f: ServiceFile): void;
|
|
35
|
+
export interface FoundService {
|
|
36
|
+
path: string;
|
|
37
|
+
/** From the file name: what serviceLabel / the unit name made of the agent's name. */
|
|
38
|
+
agentName: string;
|
|
39
|
+
command: string[];
|
|
40
|
+
args: string[];
|
|
41
|
+
env: Record<string, string>;
|
|
42
|
+
logFile: string;
|
|
43
|
+
/** `npx`: starts `npx -y <spec>`, so a restart gets the newest release. `node-cache`: starts node on a file inside the npx cache, which only ever
|
|
44
|
+
* runs the release that was cached. `node`: node on some other install. */
|
|
45
|
+
format: 'npx' | 'node-cache' | 'node' | 'unknown';
|
|
46
|
+
}
|
|
47
|
+
/** Where this platform keeps the files serviceFile() writes. */
|
|
48
|
+
export declare function serviceDir(platform: ServiceSpec['platform'], env?: NodeJS.ProcessEnv): string;
|
|
49
|
+
/** Read a service file this module wrote (launchd plist or systemd unit); null for anything else, a Windows script included. */
|
|
50
|
+
export declare function parseServiceFile(path: string, platform: ServiceSpec['platform'], text: string): FoundService | null;
|
|
51
|
+
/** Every runner service of this user on this machine (launchd and systemd). */
|
|
52
|
+
export declare function listServices(platform: ServiceSpec['platform'], dir?: string, env?: NodeJS.ProcessEnv): FoundService[];
|
|
53
|
+
/** The ServiceSpec that would write this service again (so it can be rewritten with another command). */
|
|
54
|
+
export declare function specOf(s: FoundService, platform: ServiceSpec['platform'], command?: string[]): ServiceSpec;
|
package/dist/src/service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
function xml(s) {
|
|
@@ -140,3 +140,99 @@ export function writeServiceFile(f) {
|
|
|
140
140
|
mkdirSync(dir, { recursive: true });
|
|
141
141
|
writeFileSync(f.path, f.content, { mode: 0o644 });
|
|
142
142
|
}
|
|
143
|
+
/** Where this platform keeps the files serviceFile() writes. */
|
|
144
|
+
export function serviceDir(platform, env = process.env) {
|
|
145
|
+
if (platform === 'win32')
|
|
146
|
+
return join(env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
147
|
+
if (platform === 'darwin')
|
|
148
|
+
return join(homedir(), 'Library', 'LaunchAgents');
|
|
149
|
+
return join(homedir(), '.config', 'systemd', 'user');
|
|
150
|
+
}
|
|
151
|
+
const unxml = (s) => s.replace(/"/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&');
|
|
152
|
+
/** The inverse of sd(): words of an ExecStart line, quoted or not. */
|
|
153
|
+
function sdWords(line) {
|
|
154
|
+
const out = [];
|
|
155
|
+
const re = /"((?:[^"\\]|\\.)*)"|(\S+)/g;
|
|
156
|
+
let m;
|
|
157
|
+
while ((m = re.exec(line)))
|
|
158
|
+
out.push(m[1] !== undefined ? m[1].replace(/\\(.)/g, '$1').replace(/\$\$/g, '$').replace(/%%/g, '%') : m[2]);
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
const formatOf = (command) => {
|
|
162
|
+
if (!command.length)
|
|
163
|
+
return 'unknown';
|
|
164
|
+
if (/(^|[\\/])npx(\.cmd)?$/.test(command[0]))
|
|
165
|
+
return 'npx';
|
|
166
|
+
return command.slice(1).some((x) => /[\\/]_npx[\\/]/.test(x)) ? 'node-cache' : 'node';
|
|
167
|
+
};
|
|
168
|
+
/** Read a service file this module wrote (launchd plist or systemd unit); null for anything else, a Windows script included. */
|
|
169
|
+
export function parseServiceFile(path, platform, text) {
|
|
170
|
+
let words = [];
|
|
171
|
+
const env = {};
|
|
172
|
+
let logFile = '';
|
|
173
|
+
let agentName = '';
|
|
174
|
+
if (platform === 'darwin') {
|
|
175
|
+
const label = /<key>Label<\/key>\s*<string>([^<]*)<\/string>/.exec(text)?.[1];
|
|
176
|
+
if (!label?.startsWith('io.agentchannel.runner.'))
|
|
177
|
+
return null;
|
|
178
|
+
agentName = unxml(label).slice('io.agentchannel.runner.'.length);
|
|
179
|
+
const arr = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(text)?.[1] ?? '';
|
|
180
|
+
words = [...arr.matchAll(/<string>([^<]*)<\/string>/g)].map((m) => unxml(m[1]));
|
|
181
|
+
const dict = /<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/.exec(text)?.[1] ?? '';
|
|
182
|
+
for (const m of dict.matchAll(/<key>([^<]*)<\/key>\s*<string>([^<]*)<\/string>/g))
|
|
183
|
+
env[unxml(m[1])] = unxml(m[2]);
|
|
184
|
+
logFile = unxml(/<key>StandardOutPath<\/key>\s*<string>([^<]*)<\/string>/.exec(text)?.[1] ?? '');
|
|
185
|
+
}
|
|
186
|
+
else if (platform === 'linux') {
|
|
187
|
+
const exec = /^ExecStart=(.*)$/m.exec(text)?.[1];
|
|
188
|
+
const base = path.split(/[\\/]/).pop() ?? '';
|
|
189
|
+
if (!exec || !/^agentchannel-runner-.+\.service$/.test(base))
|
|
190
|
+
return null;
|
|
191
|
+
agentName = base.slice('agentchannel-runner-'.length, -'.service'.length);
|
|
192
|
+
words = sdWords(exec);
|
|
193
|
+
for (const m of text.matchAll(/^Environment=(.*)$/gm)) {
|
|
194
|
+
const kv = sdWords(m[1])[0] ?? '';
|
|
195
|
+
const i = kv.indexOf('=');
|
|
196
|
+
if (i > 0)
|
|
197
|
+
env[kv.slice(0, i)] = kv.slice(i + 1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else
|
|
201
|
+
return null;
|
|
202
|
+
const run = words.indexOf('run');
|
|
203
|
+
if (run < 1)
|
|
204
|
+
return null;
|
|
205
|
+
const command = words.slice(0, run);
|
|
206
|
+
return { path, agentName, command, args: words.slice(run + 1), env, logFile, format: formatOf(command) };
|
|
207
|
+
}
|
|
208
|
+
/** Every runner service of this user on this machine (launchd and systemd). */
|
|
209
|
+
export function listServices(platform, dir, env = process.env) {
|
|
210
|
+
if (platform === 'win32')
|
|
211
|
+
return [];
|
|
212
|
+
const d = dir ?? serviceDir(platform, env);
|
|
213
|
+
let names = [];
|
|
214
|
+
try {
|
|
215
|
+
names = readdirSync(d);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
const out = [];
|
|
221
|
+
for (const f of names) {
|
|
222
|
+
if (!(platform === 'darwin' ? /^io\.agentchannel\.runner\..+\.plist$/ : /^agentchannel-runner-.+\.service$/).test(f))
|
|
223
|
+
continue;
|
|
224
|
+
try {
|
|
225
|
+
const s = parseServiceFile(join(d, f), platform, readFileSync(join(d, f), 'utf8'));
|
|
226
|
+
if (s)
|
|
227
|
+
out.push(s);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
/* unreadable: not ours to judge */
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/** The ServiceSpec that would write this service again (so it can be rewritten with another command). */
|
|
236
|
+
export function specOf(s, platform, command = s.command) {
|
|
237
|
+
return { platform, agentName: s.agentName, command, args: s.args, env: s.env, logFile: s.logFile };
|
|
238
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type FoundService, type ServiceSpec } from './service.js';
|
|
2
|
+
/** The config dir this service's runner uses: its own AGENTCHANNEL_HOME, else the default one. It names the agent (one key per home). */
|
|
3
|
+
export declare function homeOfService(s: FoundService, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
export interface Retired {
|
|
5
|
+
agentName: string;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Stop and delete the other services of the agent whose home this is (not the one at `ownPath`). `run` false only deletes the files
|
|
10
|
+
* (the installer was told not to load anything). Returns what was retired.
|
|
11
|
+
*/
|
|
12
|
+
export declare function retireSiblings(o: {
|
|
13
|
+
platform: ServiceSpec['platform'];
|
|
14
|
+
dir?: string;
|
|
15
|
+
env: NodeJS.ProcessEnv;
|
|
16
|
+
ownPath: string;
|
|
17
|
+
ownHome: string;
|
|
18
|
+
run: boolean;
|
|
19
|
+
}): Retired[];
|
|
20
|
+
/** Was this process started by its own service (launchd, systemd)? A runner someone started by hand must not be replaced by a restart. */
|
|
21
|
+
export declare function startedByService(agentName: string, platform: string, env: NodeJS.ProcessEnv): boolean;
|
|
22
|
+
export interface Migration {
|
|
23
|
+
path: string;
|
|
24
|
+
/** Shell commands that make the supervisor load the rewritten file. */
|
|
25
|
+
start: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* This agent's own service, when it is in the old form (node on the npx cache): rewritten to start `npx -y <spec>`. Null when there is
|
|
29
|
+
* none, or it is already fine. Writing only; the caller decides when to reload (that ends this very process).
|
|
30
|
+
*/
|
|
31
|
+
export declare function migrateOldService(o: {
|
|
32
|
+
agentName: string;
|
|
33
|
+
platform: ServiceSpec['platform'];
|
|
34
|
+
dir?: string;
|
|
35
|
+
env: NodeJS.ProcessEnv;
|
|
36
|
+
home: string;
|
|
37
|
+
npx: string;
|
|
38
|
+
spec: string;
|
|
39
|
+
}): Migration | null;
|
|
40
|
+
/** Run the reload so that it outlives this process (the reload stops it): a detached shell. */
|
|
41
|
+
export declare function reloadDetached(start: string[], env: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Keeping an agent's runner service right (issue #4): one service per agent, and a service that starts `npx -y <spec>`.
|
|
2
|
+
// - Installing a service retires the others on this machine that run the same agent (same AGENTCHANNEL_HOME): a renamed agent
|
|
3
|
+
// gets a new service label, and the old one went on running beside it.
|
|
4
|
+
// - A service written by an early release starts node on a file inside the npx cache. A restart (including the self-update's)
|
|
5
|
+
// runs that same cached file, so the runner never moves to a newer release. At start the runner rewrites such a service to
|
|
6
|
+
// `npx -y <spec>` and has the supervisor reload it.
|
|
7
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
8
|
+
import { unlinkSync } from 'node:fs';
|
|
9
|
+
import { achHome } from './config.js';
|
|
10
|
+
import { listServices, serviceFile, serviceLabel, specOf, writeServiceFile } from './service.js';
|
|
11
|
+
/** The config dir this service's runner uses: its own AGENTCHANNEL_HOME, else the default one. It names the agent (one key per home). */
|
|
12
|
+
export function homeOfService(s, env = process.env) {
|
|
13
|
+
return s.env.AGENTCHANNEL_HOME || achHome({ ...env, AGENTCHANNEL_HOME: '' });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Stop and delete the other services of the agent whose home this is (not the one at `ownPath`). `run` false only deletes the files
|
|
17
|
+
* (the installer was told not to load anything). Returns what was retired.
|
|
18
|
+
*/
|
|
19
|
+
export function retireSiblings(o) {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const s of listServices(o.platform, o.dir, o.env)) {
|
|
22
|
+
if (s.path === o.ownPath || homeOfService(s, o.env) !== o.ownHome)
|
|
23
|
+
continue;
|
|
24
|
+
if (o.run) {
|
|
25
|
+
for (const c of serviceFile(specOf(s, o.platform), o.dir, o.env).stop) {
|
|
26
|
+
try {
|
|
27
|
+
execFileSync('/bin/sh', ['-c', c], { env: o.env, stdio: 'ignore' });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* it was not loaded: the file still goes */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
unlinkSync(s.path);
|
|
36
|
+
out.push({ agentName: s.agentName, path: s.path });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* cannot delete: left for the person */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/** Was this process started by its own service (launchd, systemd)? A runner someone started by hand must not be replaced by a restart. */
|
|
45
|
+
export function startedByService(agentName, platform, env) {
|
|
46
|
+
if (platform === 'darwin')
|
|
47
|
+
return env.XPC_SERVICE_NAME === serviceLabel(agentName);
|
|
48
|
+
if (platform === 'linux')
|
|
49
|
+
return !!env.INVOCATION_ID;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* This agent's own service, when it is in the old form (node on the npx cache): rewritten to start `npx -y <spec>`. Null when there is
|
|
54
|
+
* none, or it is already fine. Writing only; the caller decides when to reload (that ends this very process).
|
|
55
|
+
*/
|
|
56
|
+
export function migrateOldService(o) {
|
|
57
|
+
const name = o.agentName.replace(/[^A-Za-z0-9-]/g, '-');
|
|
58
|
+
const own = listServices(o.platform, o.dir, o.env).find((s) => s.agentName === name && homeOfService(s, o.env) === o.home);
|
|
59
|
+
if (!own || own.format !== 'node-cache')
|
|
60
|
+
return null;
|
|
61
|
+
const f = serviceFile(specOf(own, o.platform, [o.npx, '-y', o.spec]), o.dir, o.env);
|
|
62
|
+
writeServiceFile(f);
|
|
63
|
+
return { path: f.path, start: f.start };
|
|
64
|
+
}
|
|
65
|
+
/** Run the reload so that it outlives this process (the reload stops it): a detached shell. */
|
|
66
|
+
export function reloadDetached(start, env) {
|
|
67
|
+
const child = spawn('/bin/sh', ['-c', `sleep 2; ${start.join(' && ')}`], { env, detached: true, stdio: 'ignore' });
|
|
68
|
+
child.unref();
|
|
69
|
+
}
|
package/dist/src/version.d.ts
CHANGED
package/dist/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koalaauto/agentchannel",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.1",
|
|
4
4
|
"description": "Hand work between your team's agents (Claude Code, Codex) and follow it in one panel: the ach CLI, MCP server and runner for AgentChannel v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentchannel",
|