@analyticscli/growth-engineer 0.1.1-preview.7 → 0.1.1-preview.9
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/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/discord-openclaw-bridge.mjs +309 -0
- package/dist/runtime/openclaw-growth-runner.mjs +156 -3
- package/dist/runtime/openclaw-growth-runner.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-shared.mjs +2 -0
- package/dist/runtime/openclaw-growth-shared.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-wizard.mjs +3 -1
- package/dist/runtime/openclaw-growth-wizard.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -458,6 +458,7 @@ function runShellCommand(command, timeoutMs = 120_000, options = {}) {
|
|
|
458
458
|
cwd: options.cwd,
|
|
459
459
|
env: {
|
|
460
460
|
...process.env,
|
|
461
|
+
...(options.env || {}),
|
|
461
462
|
DEBIAN_FRONTEND: 'noninteractive',
|
|
462
463
|
SUDO_ASKPASS: '/bin/false',
|
|
463
464
|
SUDO_PROMPT: '',
|
|
@@ -900,6 +901,8 @@ function notificationChannelKey(channel) {
|
|
|
900
901
|
return `slack:${channel?.label || channel?.webhookEnv || 'slack'}`;
|
|
901
902
|
if (type === 'webhook')
|
|
902
903
|
return `webhook:${channel?.label || channel?.urlEnv || channel?.webhookEnv || 'webhook'}`;
|
|
904
|
+
if (type === 'discord')
|
|
905
|
+
return `discord:${channel?.label || channel?.command || 'discord'}`;
|
|
903
906
|
if (type === 'command')
|
|
904
907
|
return `command:${channel?.label || channel?.command || 'command'}`;
|
|
905
908
|
return `${type}:${channel?.label || type}`;
|
|
@@ -956,7 +959,7 @@ function getDeliveryNotificationChannels(config, kind) {
|
|
|
956
959
|
}
|
|
957
960
|
if (deliveries.discord?.enabled) {
|
|
958
961
|
channels.push({
|
|
959
|
-
type: '
|
|
962
|
+
type: 'discord',
|
|
960
963
|
label: deliveries.discord.label || 'discord',
|
|
961
964
|
command: deliveries.discord.command || '',
|
|
962
965
|
});
|
|
@@ -1050,7 +1053,23 @@ async function sendCommandConnectorHealthAlert(channel, message) {
|
|
|
1050
1053
|
sent: result.ok,
|
|
1051
1054
|
external: true,
|
|
1052
1055
|
target: channel.label || 'command',
|
|
1053
|
-
detail: result.ok ?
|
|
1056
|
+
detail: result.ok ? 'sent' : result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
async function sendDiscordConnectorHealthAlert(channel, message, statusPayload, unhealthyConnectors, fingerprint) {
|
|
1060
|
+
if (!channel.command) {
|
|
1061
|
+
return { sent: false, target: channel.label || 'discord', detail: 'discord command not configured' };
|
|
1062
|
+
}
|
|
1063
|
+
const payload = buildDiscordConnectorHealthPayload(message, statusPayload, unhealthyConnectors, fingerprint);
|
|
1064
|
+
const result = await runShellCommand(String(channel.command), 60_000, {
|
|
1065
|
+
input: JSON.stringify(payload),
|
|
1066
|
+
env: { OPENCLAW_DISCORD_DELIVERY_FORMAT: 'embed' },
|
|
1067
|
+
});
|
|
1068
|
+
return {
|
|
1069
|
+
sent: result.ok,
|
|
1070
|
+
external: true,
|
|
1071
|
+
target: channel.label || 'discord',
|
|
1072
|
+
detail: result.ok ? 'sent' : result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`,
|
|
1054
1073
|
};
|
|
1055
1074
|
}
|
|
1056
1075
|
function hasExternalNotificationChannel(channels) {
|
|
@@ -1059,6 +1078,57 @@ function hasExternalNotificationChannel(channels) {
|
|
|
1059
1078
|
function hasSuccessfulExternalDelivery(results) {
|
|
1060
1079
|
return results.some((result) => result?.sent === true && result?.external === true);
|
|
1061
1080
|
}
|
|
1081
|
+
function discordTruncate(value, maxLength) {
|
|
1082
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
1083
|
+
if (text.length <= maxLength)
|
|
1084
|
+
return text;
|
|
1085
|
+
return `${text.slice(0, Math.max(0, maxLength - 1)).trim()}…`;
|
|
1086
|
+
}
|
|
1087
|
+
function discordField(name, value, inline = false) {
|
|
1088
|
+
return {
|
|
1089
|
+
name: discordTruncate(name, 256) || 'Detail',
|
|
1090
|
+
value: discordTruncate(value, 1024) || '-',
|
|
1091
|
+
inline,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function connectorStatusColor(unhealthyConnectors) {
|
|
1095
|
+
return unhealthyConnectors.some((entry) => String(entry?.status || '').toLowerCase() === 'blocked')
|
|
1096
|
+
? 0xd92d20
|
|
1097
|
+
: 0xf79009;
|
|
1098
|
+
}
|
|
1099
|
+
function buildDiscordConnectorHealthPayload(message, statusPayload, unhealthyConnectors, fingerprint) {
|
|
1100
|
+
const fields = unhealthyConnectors.slice(0, 10).map((entry) => {
|
|
1101
|
+
const command = buildConnectorWizardCommand(statusPayload?.configPath || DEFAULT_CONFIG_PATH, entry);
|
|
1102
|
+
const parts = [
|
|
1103
|
+
`Status: ${entry.status || 'blocked'}`,
|
|
1104
|
+
conciseConnectorDetail(entry),
|
|
1105
|
+
command ? `Fix: \`${command}\`` : null,
|
|
1106
|
+
isAscWebAuthIssue(entry)
|
|
1107
|
+
? 'ASC web-auth only: `ASC_WEB_APPLE_ID="<apple-id>" asc web auth login --apple-id "$ASC_WEB_APPLE_ID"`'
|
|
1108
|
+
: null,
|
|
1109
|
+
].filter(Boolean);
|
|
1110
|
+
return discordField(humanConnectorName(entry.key), parts.join('\n'));
|
|
1111
|
+
});
|
|
1112
|
+
if (unhealthyConnectors.length > 10) {
|
|
1113
|
+
fields.push(discordField('More issues', `${unhealthyConnectors.length - 10} additional connector(s) need attention.`));
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
content: '',
|
|
1117
|
+
embeds: [
|
|
1118
|
+
{
|
|
1119
|
+
title: `OpenClaw connector health: ${unhealthyConnectors.length} issue(s)`,
|
|
1120
|
+
description: 'Secrets stay in the host terminal or secret store.',
|
|
1121
|
+
color: connectorStatusColor(unhealthyConnectors),
|
|
1122
|
+
fields,
|
|
1123
|
+
footer: {
|
|
1124
|
+
text: `CONNECTOR_HEALTH_ALERT • ${String(fingerprint || '').slice(0, 12)}`,
|
|
1125
|
+
},
|
|
1126
|
+
timestamp: statusPayload?.generatedAt || new Date().toISOString(),
|
|
1127
|
+
},
|
|
1128
|
+
],
|
|
1129
|
+
fallbackText: message,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1062
1132
|
function truncateMessageText(value, maxLength = 96) {
|
|
1063
1133
|
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
1064
1134
|
if (text.length <= maxLength)
|
|
@@ -1428,6 +1498,9 @@ async function deliverConnectorHealthAlert({ config, configPath, message, status
|
|
|
1428
1498
|
else if (channel.type === 'webhook') {
|
|
1429
1499
|
results.push(await sendWebhookConnectorHealthAlert(channel, message, statusPayload, unhealthyConnectors, fingerprint));
|
|
1430
1500
|
}
|
|
1501
|
+
else if (channel.type === 'discord') {
|
|
1502
|
+
results.push(await sendDiscordConnectorHealthAlert(channel, message, statusPayload, unhealthyConnectors, fingerprint));
|
|
1503
|
+
}
|
|
1431
1504
|
else if (channel.type === 'command') {
|
|
1432
1505
|
results.push(await sendCommandConnectorHealthAlert(channel, message));
|
|
1433
1506
|
}
|
|
@@ -1557,6 +1630,67 @@ function buildGrowthRunSummaryMessage({ issuesPayload, activeCadences, sourceFil
|
|
|
1557
1630
|
: 'No secrets were included.');
|
|
1558
1631
|
return `${lines.join('\n')}\n`;
|
|
1559
1632
|
}
|
|
1633
|
+
function growthRunTitle(activeCadences) {
|
|
1634
|
+
if (isShortOperationalCadence(activeCadences)) {
|
|
1635
|
+
return activeCadences.some((cadence) => String(cadence?.key) === 'healthcheck')
|
|
1636
|
+
? 'OpenClaw healthcheck'
|
|
1637
|
+
: 'OpenClaw daily';
|
|
1638
|
+
}
|
|
1639
|
+
if (isDeepAnalysisCadence(activeCadences))
|
|
1640
|
+
return 'OpenClaw growth review';
|
|
1641
|
+
return 'OpenClaw growth run';
|
|
1642
|
+
}
|
|
1643
|
+
function buildDiscordGrowthRunPayload(message, issuesPayload, activeCadences, sourceFiles, fingerprint, createdGitHubArtifact, charts = []) {
|
|
1644
|
+
const issues = Array.isArray(issuesPayload?.issues) ? issuesPayload.issues : [];
|
|
1645
|
+
const issueCount = Number(issuesPayload?.issue_count || 0);
|
|
1646
|
+
const fields = [
|
|
1647
|
+
discordField('Cadence', activeCadences.length > 0
|
|
1648
|
+
? activeCadences.map((cadence) => cadence.title || cadence.key).join(', ')
|
|
1649
|
+
: 'ad-hoc growth pass', false),
|
|
1650
|
+
discordField('Sources', Object.keys(sourceFiles || {}).sort().join(', ') || 'none', true),
|
|
1651
|
+
discordField('Findings', String(issueCount), true),
|
|
1652
|
+
];
|
|
1653
|
+
if (createdGitHubArtifact) {
|
|
1654
|
+
fields.push(discordField('Action', 'GitHub artifact creation was attempted.', true));
|
|
1655
|
+
}
|
|
1656
|
+
const suppressedIssueCount = Number(issuesPayload?.suppressed_issue_count || 0);
|
|
1657
|
+
if (suppressedIssueCount > 0) {
|
|
1658
|
+
fields.push(discordField('Suppressed today', `${suppressedIssueCount} previously reported finding(s).`, true));
|
|
1659
|
+
}
|
|
1660
|
+
if (charts.length > 0) {
|
|
1661
|
+
fields.push(discordField('Charts', String(charts.length), true));
|
|
1662
|
+
}
|
|
1663
|
+
const groupedIssues = isShortOperationalCadence(activeCadences)
|
|
1664
|
+
? groupIssuesByProject(issues, 4).map(([project, projectIssues]) => ({
|
|
1665
|
+
name: project,
|
|
1666
|
+
value: projectIssues.map((issue) => formatIssueSummaryLine(issue, 84)).filter(Boolean).join('\n'),
|
|
1667
|
+
}))
|
|
1668
|
+
: issues.slice(0, isDeepAnalysisCadence(activeCadences) ? 5 : 3).map((issue) => ({
|
|
1669
|
+
name: `${issue.priority || 'medium'} • ${issue.area || 'general'}`,
|
|
1670
|
+
value: formatIssueSummaryLine(issue, 96),
|
|
1671
|
+
}));
|
|
1672
|
+
for (const entry of groupedIssues) {
|
|
1673
|
+
if (entry.value)
|
|
1674
|
+
fields.push(discordField(entry.name, entry.value));
|
|
1675
|
+
}
|
|
1676
|
+
const summary = String(issuesPayload?.summary || '').trim();
|
|
1677
|
+
return {
|
|
1678
|
+
content: '',
|
|
1679
|
+
embeds: [
|
|
1680
|
+
{
|
|
1681
|
+
title: `${growthRunTitle(activeCadences)}: ${issueCount > 0 ? `${issueCount} finding(s)` : 'OK'}`,
|
|
1682
|
+
description: discordTruncate(summary || 'No secrets were included.', 500),
|
|
1683
|
+
color: issueCount > 0 ? 0xf79009 : 0x12b76a,
|
|
1684
|
+
fields: fields.slice(0, 20),
|
|
1685
|
+
footer: {
|
|
1686
|
+
text: `GROWTH_RUN • ${String(fingerprint || '').slice(0, 12)}`,
|
|
1687
|
+
},
|
|
1688
|
+
timestamp: new Date().toISOString(),
|
|
1689
|
+
},
|
|
1690
|
+
],
|
|
1691
|
+
fallbackText: message,
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1560
1694
|
async function writeConfiguredOpenClawChatGrowthSummary(configPath, channel, message, issuesPayload, activeCadences, fingerprint, charts) {
|
|
1561
1695
|
const markdownPath = resolveOpenClawChatDeliveryPath(channel.markdownPath, '.openclaw/chat/growth-summary.md');
|
|
1562
1696
|
const jsonPath = resolveOpenClawChatDeliveryPath(channel.jsonPath, '.openclaw/chat/growth-summary.json');
|
|
@@ -1646,7 +1780,23 @@ async function sendCommandGrowthSummary(channel, message) {
|
|
|
1646
1780
|
sent: result.ok,
|
|
1647
1781
|
external: true,
|
|
1648
1782
|
target: channel.label || 'command',
|
|
1649
|
-
detail: result.ok ?
|
|
1783
|
+
detail: result.ok ? 'sent' : result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`,
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
async function sendDiscordGrowthSummary(channel, message, issuesPayload, activeCadences, sourceFiles, fingerprint, createdGitHubArtifact, charts) {
|
|
1787
|
+
if (!channel.command) {
|
|
1788
|
+
return { sent: false, target: channel.label || 'discord', detail: 'discord command not configured' };
|
|
1789
|
+
}
|
|
1790
|
+
const payload = buildDiscordGrowthRunPayload(message, issuesPayload, activeCadences, sourceFiles, fingerprint, createdGitHubArtifact, charts);
|
|
1791
|
+
const result = await runShellCommand(String(channel.command), 60_000, {
|
|
1792
|
+
input: JSON.stringify(payload),
|
|
1793
|
+
env: { OPENCLAW_DISCORD_DELIVERY_FORMAT: 'embed' },
|
|
1794
|
+
});
|
|
1795
|
+
return {
|
|
1796
|
+
sent: result.ok,
|
|
1797
|
+
external: true,
|
|
1798
|
+
target: channel.label || 'discord',
|
|
1799
|
+
detail: result.ok ? 'sent' : result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`,
|
|
1650
1800
|
};
|
|
1651
1801
|
}
|
|
1652
1802
|
async function deliverGrowthRunSummary({ config, configPath, issuesPayload, activeCadences, sourceFiles, fingerprint, createdGitHubArtifact, chartManifestPath, }) {
|
|
@@ -1677,6 +1827,9 @@ async function deliverGrowthRunSummary({ config, configPath, issuesPayload, acti
|
|
|
1677
1827
|
else if (channel.type === 'webhook') {
|
|
1678
1828
|
results.push(await sendWebhookGrowthSummary(channel, message, issuesPayload, activeCadences, fingerprint, charts));
|
|
1679
1829
|
}
|
|
1830
|
+
else if (channel.type === 'discord') {
|
|
1831
|
+
results.push(await sendDiscordGrowthSummary(channel, message, issuesPayload, activeCadences, sourceFiles, fingerprint, createdGitHubArtifact, charts));
|
|
1832
|
+
}
|
|
1680
1833
|
else if (channel.type === 'command') {
|
|
1681
1834
|
results.push(await sendCommandGrowthSummary(channel, message));
|
|
1682
1835
|
}
|