@teamvibe/poller 0.5.0 → 0.5.2
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/claude-spawner.js +11 -3
- package/dist/cli/doctor.js +39 -1
- package/dist/cli/install.js +4 -4
- package/dist/cli/plist.d.ts +7 -1
- package/dist/cli/plist.js +16 -2
- package/dist/cli/update.js +69 -61
- package/package.json +1 -1
package/dist/claude-spawner.js
CHANGED
|
@@ -134,7 +134,7 @@ export async function buildContextHint(p) {
|
|
|
134
134
|
if (!p.isFirstMessage && p.lastMessageTs && p.slack?.channel && p.slack?.thread_ts) {
|
|
135
135
|
const newMessageCount = await p.countNewSinceTs(p.slack.channel, p.slack.thread_ts, p.lastMessageTs);
|
|
136
136
|
return newMessageCount > 0
|
|
137
|
-
? `**Resumed session:** ${newMessageCount} new message(s) in the thread since your last response
|
|
137
|
+
? `**Resumed session:** ${newMessageCount} new message(s) in the thread since your last response.\nCall \`read_thread\` first to load conversation context.`
|
|
138
138
|
: `**Resumed session:** No new messages since your last response.`;
|
|
139
139
|
}
|
|
140
140
|
if (p.isFirstMessage &&
|
|
@@ -405,14 +405,22 @@ async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = t
|
|
|
405
405
|
// Placed FIRST so subsequent spreads can overwrite. Belt-and-
|
|
406
406
|
// suspenders: even if the API's reserved-prefix validator fails
|
|
407
407
|
// (`AWS_`, `TEAMVIBE_`, `SLACK_`, `CLAUDE_CODE_`, `ANTHROPIC_`)
|
|
408
|
-
// a name collision is silently won by the platform variable
|
|
408
|
+
// a name collision is silently won by the platform variable
|
|
409
|
+
// explicit field below.
|
|
410
|
+
//
|
|
411
|
+
// Phase 2 of teamvibe.ai#215 (poller v0.5.1): the explicit
|
|
412
|
+
// `SLACK_BOT_TOKEN: msg.teamvibe.botToken` injection that used
|
|
413
|
+
// to live here was removed. `SLACK_BOT_TOKEN` now sources from
|
|
414
|
+
// the `/secrets/spawn` platform overlay (the spread above) —
|
|
415
|
+
// backend v0.2.2+ resolves it via the `Channel → SlackBot`
|
|
416
|
+
// relation. Requires backend on Phase 1; pollers on 0.5.0 still
|
|
417
|
+
// read the explicit field, so split rollout is safe.
|
|
409
418
|
...spawnSecrets.env.unwrap(),
|
|
410
419
|
...process.env,
|
|
411
420
|
CLAUDECODE: undefined,
|
|
412
421
|
PATH: envPath,
|
|
413
422
|
CI: 'true',
|
|
414
423
|
CLAUDE_CONFIG_DIR: getBaseBrainPath(),
|
|
415
|
-
SLACK_BOT_TOKEN: msg.teamvibe.botToken,
|
|
416
424
|
...(config.PERSISTENT_STORAGE_PATH && {
|
|
417
425
|
PERSISTENT_STORAGE_PATH: config.PERSISTENT_STORAGE_PATH,
|
|
418
426
|
}),
|
package/dist/cli/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
|
-
import { PLIST_PATH, SERVICE_LABEL, TEAMVIBE_DIR, LOGS_DIR, isContainerRuntime } from './plist.js';
|
|
4
|
+
import { PLIST_PATH, SERVICE_LABEL, TEAMVIBE_DIR, LOGS_DIR, isContainerRuntime, plistHasStaleNodePath } from './plist.js';
|
|
5
5
|
function ok(name, message) {
|
|
6
6
|
return { name, status: 'ok', message };
|
|
7
7
|
}
|
|
@@ -78,9 +78,46 @@ function checkLaunchdPlist() {
|
|
|
78
78
|
if (content.includes('/_npx/')) {
|
|
79
79
|
throw new Error('Plist references stale npx cache path. Run \'poller update\' to fix.');
|
|
80
80
|
}
|
|
81
|
+
if (plistHasStaleNodePath(content)) {
|
|
82
|
+
throw new Error('Plist references brew Cellar Node path (e.g. /opt/homebrew/Cellar/node/<version>/bin/node). ' +
|
|
83
|
+
'This path disappears on `brew upgrade node`, causing launchd exit 78 (EX_CONFIG). ' +
|
|
84
|
+
'Run \'poller update\' to migrate to /usr/bin/env node.');
|
|
85
|
+
}
|
|
81
86
|
return 'Installed with KeepAlive';
|
|
82
87
|
});
|
|
83
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Verify the binary referenced as the first ProgramArguments entry actually exists.
|
|
91
|
+
* Catches cases where launchd cannot spawn the program (manifesting as exit 78
|
|
92
|
+
* with no logs, since the process never ran).
|
|
93
|
+
*/
|
|
94
|
+
function checkPlistBinary() {
|
|
95
|
+
if (process.platform !== 'darwin')
|
|
96
|
+
return skipped('Plist binary', 'not macOS');
|
|
97
|
+
return check('Plist binary', () => {
|
|
98
|
+
if (!fs.existsSync(PLIST_PATH)) {
|
|
99
|
+
throw new Error(`Plist not found at ${PLIST_PATH}`);
|
|
100
|
+
}
|
|
101
|
+
const content = fs.readFileSync(PLIST_PATH, 'utf-8');
|
|
102
|
+
const argsMatch = content.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
|
|
103
|
+
if (!argsMatch?.[1]) {
|
|
104
|
+
throw new Error('Plist has no ProgramArguments');
|
|
105
|
+
}
|
|
106
|
+
const firstArg = argsMatch[1].match(/<string>([^<]+)<\/string>/)?.[1];
|
|
107
|
+
if (!firstArg) {
|
|
108
|
+
throw new Error('Plist ProgramArguments is empty');
|
|
109
|
+
}
|
|
110
|
+
if (!path.isAbsolute(firstArg)) {
|
|
111
|
+
throw new Error(`Plist ProgramArguments[0] is not absolute: ${firstArg}`);
|
|
112
|
+
}
|
|
113
|
+
if (!fs.existsSync(firstArg)) {
|
|
114
|
+
throw new Error(`Plist references non-existent binary: ${firstArg}. ` +
|
|
115
|
+
'Likely cause: brew upgraded Node and the old Cellar path is gone. ' +
|
|
116
|
+
'Run \'poller update\' to regenerate plist with /usr/bin/env node.');
|
|
117
|
+
}
|
|
118
|
+
return firstArg;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
84
121
|
function checkServiceRunning() {
|
|
85
122
|
if (process.platform !== 'darwin')
|
|
86
123
|
return skipped('Service status', 'not macOS');
|
|
@@ -184,6 +221,7 @@ export async function doctor() {
|
|
|
184
221
|
checkTeamVibeDir(container),
|
|
185
222
|
checkEnvFile(container),
|
|
186
223
|
checkLaunchdPlist(),
|
|
224
|
+
checkPlistBinary(),
|
|
187
225
|
checkServiceRunning(),
|
|
188
226
|
checkLogsDir(container),
|
|
189
227
|
checkClaudeCli(),
|
package/dist/cli/install.js
CHANGED
|
@@ -160,8 +160,8 @@ export async function install() {
|
|
|
160
160
|
else {
|
|
161
161
|
console.log(` Found claude CLI at: ${claudePath}`);
|
|
162
162
|
}
|
|
163
|
-
// Resolve
|
|
164
|
-
|
|
163
|
+
// Resolve poller path. Node is resolved at runtime via /usr/bin/env from
|
|
164
|
+
// EnvironmentVariables.PATH below, so we do not pin a specific node binary.
|
|
165
165
|
let pollerPath = path.resolve(process.argv[1] ?? '.');
|
|
166
166
|
// Detect if running via npx (temporary cache path) — need a stable path for launchd
|
|
167
167
|
const isNpx = pollerPath.includes('/_npx/') || pollerPath.includes('\\_npx\\');
|
|
@@ -188,8 +188,8 @@ export async function install() {
|
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
-
console.log(` Node path: ${nodePath}`);
|
|
192
191
|
console.log(` Poller path: ${pollerPath}`);
|
|
192
|
+
console.log(` Node: resolved at runtime via /usr/bin/env from launchd PATH`);
|
|
193
193
|
// Create directories
|
|
194
194
|
fs.mkdirSync(TEAMVIBE_DIR, { recursive: true });
|
|
195
195
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
@@ -231,7 +231,7 @@ export async function install() {
|
|
|
231
231
|
PATH: Array.from(pathDirs).join(':'),
|
|
232
232
|
};
|
|
233
233
|
// Write plist
|
|
234
|
-
const plistContent = generatePlist(
|
|
234
|
+
const plistContent = generatePlist(pollerPath, plistEnvVars);
|
|
235
235
|
const launchAgentsDir = path.dirname(PLIST_PATH);
|
|
236
236
|
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
237
237
|
fs.writeFileSync(PLIST_PATH, plistContent);
|
package/dist/cli/plist.d.ts
CHANGED
|
@@ -7,4 +7,10 @@ export declare const LOGS_DIR: string;
|
|
|
7
7
|
* In containers, env vars are injected directly — no ~/.teamvibe directory exists.
|
|
8
8
|
*/
|
|
9
9
|
export declare function isContainerRuntime(): boolean;
|
|
10
|
-
export declare function generatePlist(
|
|
10
|
+
export declare function generatePlist(pollerPath: string, envVars?: Record<string, string>): string;
|
|
11
|
+
/**
|
|
12
|
+
* Heuristic: does this plist content reference a Node binary path that can
|
|
13
|
+
* disappear on `brew upgrade node`? The legacy installer wrote
|
|
14
|
+
* /opt/homebrew/Cellar/node/<version>/bin/node directly into ProgramArguments.
|
|
15
|
+
*/
|
|
16
|
+
export declare function plistHasStaleNodePath(plistContent: string): boolean;
|
package/dist/cli/plist.js
CHANGED
|
@@ -22,7 +22,12 @@ export function isContainerRuntime() {
|
|
|
22
22
|
return true;
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
// `/usr/bin/env` is part of macOS base install and resolves `node` from
|
|
26
|
+
// EnvironmentVariables.PATH. This avoids hardcoding a Node binary path that
|
|
27
|
+
// can disappear after `brew upgrade node` (e.g. /opt/homebrew/Cellar/node/<version>/bin/node).
|
|
28
|
+
const ENV_BIN = '/usr/bin/env';
|
|
29
|
+
const NODE_BIN = 'node';
|
|
30
|
+
export function generatePlist(pollerPath, envVars) {
|
|
26
31
|
let envSection = '';
|
|
27
32
|
if (envVars && Object.keys(envVars).length > 0) {
|
|
28
33
|
const entries = Object.entries(envVars)
|
|
@@ -43,7 +48,8 @@ ${entries}
|
|
|
43
48
|
|
|
44
49
|
<key>ProgramArguments</key>
|
|
45
50
|
<array>
|
|
46
|
-
<string>${
|
|
51
|
+
<string>${ENV_BIN}</string>
|
|
52
|
+
<string>${NODE_BIN}</string>
|
|
47
53
|
<string>${escapeXml(pollerPath)}</string>
|
|
48
54
|
</array>
|
|
49
55
|
|
|
@@ -68,6 +74,14 @@ ${entries}
|
|
|
68
74
|
</plist>
|
|
69
75
|
`;
|
|
70
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Heuristic: does this plist content reference a Node binary path that can
|
|
79
|
+
* disappear on `brew upgrade node`? The legacy installer wrote
|
|
80
|
+
* /opt/homebrew/Cellar/node/<version>/bin/node directly into ProgramArguments.
|
|
81
|
+
*/
|
|
82
|
+
export function plistHasStaleNodePath(plistContent) {
|
|
83
|
+
return /\/Cellar\/node\/[^/<]+\/bin\/node/.test(plistContent);
|
|
84
|
+
}
|
|
71
85
|
function escapeXml(str) {
|
|
72
86
|
return str
|
|
73
87
|
.replace(/&/g, '&')
|
package/dist/cli/update.js
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
-
import { generatePlist, PLIST_PATH, SERVICE_LABEL } from './plist.js';
|
|
3
|
+
import { generatePlist, plistHasStaleNodePath, PLIST_PATH, SERVICE_LABEL } from './plist.js';
|
|
4
|
+
function parseEnvVars(plistContent) {
|
|
5
|
+
const envVars = {};
|
|
6
|
+
const envMatch = plistContent.match(/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/);
|
|
7
|
+
if (envMatch?.[1]) {
|
|
8
|
+
const pairs = envMatch[1].matchAll(/<key>(.*?)<\/key>\s*<string>(.*?)<\/string>/g);
|
|
9
|
+
for (const pair of pairs) {
|
|
10
|
+
envVars[pair[1]] = pair[2];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return envVars;
|
|
14
|
+
}
|
|
15
|
+
function resolveGlobalPollerPath() {
|
|
16
|
+
try {
|
|
17
|
+
const binPath = execSync('which poller', { encoding: 'utf-8' }).trim();
|
|
18
|
+
if (binPath)
|
|
19
|
+
return fs.realpathSync(binPath);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Not in PATH
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function regeneratePlist(reason) {
|
|
27
|
+
const plistContent = fs.readFileSync(PLIST_PATH, 'utf-8');
|
|
28
|
+
console.log(`\n${reason}, regenerating plist...`);
|
|
29
|
+
const globalPollerPath = resolveGlobalPollerPath();
|
|
30
|
+
if (!globalPollerPath) {
|
|
31
|
+
console.error(' Could not resolve global poller binary. Run: npm install -g @teamvibe/poller');
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const envVars = parseEnvVars(plistContent);
|
|
35
|
+
// Ensure PATH covers common Node install locations so /usr/bin/env node resolves.
|
|
36
|
+
if (!envVars['PATH']) {
|
|
37
|
+
envVars['PATH'] = '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin';
|
|
38
|
+
}
|
|
39
|
+
const newPlist = generatePlist(globalPollerPath, envVars);
|
|
40
|
+
try {
|
|
41
|
+
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' });
|
|
42
|
+
}
|
|
43
|
+
catch { /* ignore */ }
|
|
44
|
+
fs.writeFileSync(PLIST_PATH, newPlist);
|
|
45
|
+
console.log(` Updated plist; poller binary: ${globalPollerPath}`);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
4
48
|
export function update() {
|
|
5
49
|
console.log('TeamVibe Poller - Update\n');
|
|
6
50
|
console.log('Updating @teamvibe/poller...');
|
|
@@ -11,66 +55,30 @@ export function update() {
|
|
|
11
55
|
console.error('Failed to update. Try running: npm install -g @teamvibe/poller@latest');
|
|
12
56
|
process.exit(1);
|
|
13
57
|
}
|
|
14
|
-
|
|
15
|
-
if (process.platform === 'darwin' && fs.existsSync(PLIST_PATH)) {
|
|
16
|
-
const plistContent = fs.readFileSync(PLIST_PATH, 'utf-8');
|
|
17
|
-
if (plistContent.includes('/_npx/')) {
|
|
18
|
-
console.log('\nDetected stale npx path in service plist, fixing...');
|
|
19
|
-
// Resolve the globally installed poller path
|
|
20
|
-
let globalPollerPath = '';
|
|
21
|
-
try {
|
|
22
|
-
const binPath = execSync('which poller', { encoding: 'utf-8' }).trim();
|
|
23
|
-
if (binPath) {
|
|
24
|
-
globalPollerPath = fs.realpathSync(binPath);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// Fall back to current process
|
|
29
|
-
}
|
|
30
|
-
if (globalPollerPath) {
|
|
31
|
-
// Extract existing env vars from plist
|
|
32
|
-
const envVars = {};
|
|
33
|
-
const envMatch = plistContent.match(/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/);
|
|
34
|
-
if (envMatch) {
|
|
35
|
-
const pairs = envMatch[1].matchAll(/<key>(.*?)<\/key>\s*<string>(.*?)<\/string>/g);
|
|
36
|
-
for (const pair of pairs) {
|
|
37
|
-
envVars[pair[1]] = pair[2];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const nodePath = process.execPath;
|
|
41
|
-
const newPlist = generatePlist(nodePath, globalPollerPath, envVars);
|
|
42
|
-
// Unload, write, reload
|
|
43
|
-
try {
|
|
44
|
-
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' });
|
|
45
|
-
}
|
|
46
|
-
catch { /* ignore */ }
|
|
47
|
-
fs.writeFileSync(PLIST_PATH, newPlist);
|
|
48
|
-
console.log(`Updated plist to use: ${globalPollerPath}`);
|
|
49
|
-
try {
|
|
50
|
-
execSync(`launchctl load "${PLIST_PATH}"`);
|
|
51
|
-
console.log('Service restarted.');
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
console.error(`Failed to restart: ${error instanceof Error ? error.message : error}`);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// Normal restart (no npx fix needed)
|
|
60
|
-
console.log(`\nRestarting service '${SERVICE_LABEL}'...`);
|
|
61
|
-
try {
|
|
62
|
-
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' });
|
|
63
|
-
}
|
|
64
|
-
catch { /* ignore */ }
|
|
65
|
-
try {
|
|
66
|
-
execSync(`launchctl load "${PLIST_PATH}"`);
|
|
67
|
-
console.log('Service restarted.');
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
console.error(`Failed to restart: ${error instanceof Error ? error.message : error}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
58
|
+
if (process.platform !== 'darwin' || !fs.existsSync(PLIST_PATH)) {
|
|
74
59
|
console.log('\nUpdate complete. Restart the poller to use the new version.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const plistContent = fs.readFileSync(PLIST_PATH, 'utf-8');
|
|
63
|
+
const hasNpxPath = plistContent.includes('/_npx/');
|
|
64
|
+
const hasStaleNode = plistHasStaleNodePath(plistContent);
|
|
65
|
+
if (hasNpxPath || hasStaleNode) {
|
|
66
|
+
const reason = hasNpxPath
|
|
67
|
+
? 'Detected stale npx path in service plist'
|
|
68
|
+
: 'Detected hardcoded brew Cellar Node path in service plist (will break on `brew upgrade node`)';
|
|
69
|
+
if (!regeneratePlist(reason))
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(`\nRestarting service '${SERVICE_LABEL}'...`);
|
|
73
|
+
try {
|
|
74
|
+
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'ignore' });
|
|
75
|
+
}
|
|
76
|
+
catch { /* ignore */ }
|
|
77
|
+
try {
|
|
78
|
+
execSync(`launchctl load "${PLIST_PATH}"`);
|
|
79
|
+
console.log('Service restarted.');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`Failed to restart: ${error instanceof Error ? error.message : error}`);
|
|
75
83
|
}
|
|
76
84
|
}
|