neoagent 2.1.15 → 2.1.16

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 CHANGED
@@ -1,24 +1,33 @@
1
- <div align="center">
1
+ <h1 align="center">NeoAgent</h1>
2
2
 
3
- # NeoAgent
3
+ <p align="center"><strong>Your agent. Your server. Your rules.</strong></p>
4
4
 
5
- **Your agent. Your server. Your rules.**
5
+ <p align="center">
6
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-18+-5fa04e?style=flat-square&logo=node.js&logoColor=white" alt="Node.js"></a>
7
+ <a href="https://sqlite.org"><img src="https://img.shields.io/badge/SQLite-WAL-003b57?style=flat-square&logo=sqlite&logoColor=white" alt="SQLite"></a>
8
+ <a href="https://flutter.dev"><img src="https://img.shields.io/badge/Flutter-web%20%2B%20android-02569B?style=flat-square&logo=flutter&logoColor=white" alt="Flutter"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-a855f7?style=flat-square" alt="License"></a>
10
+ </p>
6
11
 
7
- [![Node.js](https://img.shields.io/badge/Node.js-18+-5fa04e?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org)
8
- [![SQLite](https://img.shields.io/badge/SQLite-WAL-003b57?style=flat-square&logo=sqlite&logoColor=white)](https://sqlite.org)
9
- [![Flutter](https://img.shields.io/badge/Flutter-web%20%2B%20android-02569B?style=flat-square&logo=flutter&logoColor=white)](https://flutter.dev)
10
- [![License](https://img.shields.io/badge/License-MIT-a855f7?style=flat-square)](LICENSE)
12
+ <p align="center">
13
+ A self-hosted, proactive AI agent with a Flutter client for web and Android.<br>
14
+ Connects to OpenAI, xAI, Google, MiniMax Code, and local Ollama.<br>
15
+ Runs tasks on a schedule, controls a browser, manages files, and talks to you over Telegram, Discord, or WhatsApp.
16
+ </p>
11
17
 
12
- A self-hosted, proactive AI agent with a Flutter client for web and Android.
13
- Connects to OpenAI, xAI, Google, MiniMax Code, and local Ollama.
14
- Runs tasks on a schedule, controls a browser, manages files, and talks to you over Telegram, Discord, or WhatsApp.
18
+ | | |
19
+ | --- | --- |
20
+ | <img alt="WebUI" src="https://github.com/user-attachments/assets/3c76d59a-b6e3-4698-929b-9c94741ccf1e" height="420"> | <img alt="Mobile Telegram" src="https://github.com/user-attachments/assets/1fd41a9b-5452-4aa4-9478-888c8ad7363a" height="420"> |
21
+
22
+ ## Install
15
23
 
16
24
  ```bash
17
25
  npm install -g neoagent
18
26
  neoagent install
19
27
  ```
20
28
 
21
- Manage the service:
29
+ ## Manage the Service
30
+
22
31
  ```bash
23
32
  neoagent status
24
33
  neoagent channel beta
@@ -28,12 +37,13 @@ neoagent logs
28
37
  ```
29
38
 
30
39
  Use `neoagent fix` if a self-edit or broken local install leaves NeoAgent in a bad state. On git installs it backs up runtime data, saves local tracked changes, resets tracked source files, reinstalls dependencies, and restarts the service.
31
- ---
40
+
41
+ ## Links
32
42
 
33
43
  [⚙️ Configuration](docs/configuration.md) · [🧰 Skills](docs/skills.md) · [🐛 Issues](https://github.com/NeoLabs-Systems/NeoAgent/issues)
34
44
 
35
45
  ---
36
46
 
37
- *Made with ❤️ by [Neo](https://github.com/neooriginal) · [NeoLabs Systems](https://github.com/NeoLabs-Systems)*
38
-
39
- </div>
47
+ <p align="center">
48
+ Made with ❤️ by <a href="https://github.com/neooriginal">Neo</a> · <a href="https://github.com/NeoLabs-Systems">NeoLabs Systems</a>
49
+ </p>
package/lib/manager.js CHANGED
@@ -25,9 +25,11 @@ const {
25
25
  parseReleaseChannel,
26
26
  getReleaseChannelBranch,
27
27
  getReleaseChannelDistTag,
28
- getReleaseChannelLabel,
29
28
  readConfiguredReleaseChannel,
30
29
  writeReleaseChannelToEnvFile,
30
+ describeReleaseChannelPolicy,
31
+ choosePreferredBranchForChannel,
32
+ choosePreferredNpmTagForChannel,
31
33
  } = require('../runtime/release_channel');
32
34
 
33
35
  const APP_NAME = 'NeoAgent';
@@ -197,8 +199,7 @@ function currentReleaseChannel() {
197
199
  }
198
200
 
199
201
  function releaseChannelSummary(channel) {
200
- const normalized = parseReleaseChannel(channel) || currentReleaseChannel();
201
- return `${getReleaseChannelLabel(normalized)} (branch ${getReleaseChannelBranch(normalized)}, npm ${getReleaseChannelDistTag(normalized)})`;
202
+ return describeReleaseChannelPolicy(parseReleaseChannel(channel) || currentReleaseChannel());
202
203
  }
203
204
 
204
205
  function gitWorkingTreeDirty() {
@@ -214,6 +215,62 @@ function gitRemoteBranchExists(branch) {
214
215
  return runQuiet('git', ['ls-remote', '--exit-code', '--heads', 'origin', branch]).status === 0;
215
216
  }
216
217
 
218
+ function latestGitTagVersion(pattern) {
219
+ const res = runQuiet('git', ['tag', '--list', pattern, '--sort=-v:refname']);
220
+ if (res.status !== 0) return null;
221
+ const tag = res.stdout
222
+ .split('\n')
223
+ .map((value) => value.trim())
224
+ .find(Boolean);
225
+ return tag ? tag.replace(/^v/, '') : null;
226
+ }
227
+
228
+ function resolvePreferredGitBranch(channel) {
229
+ const normalized = parseReleaseChannel(channel) || currentReleaseChannel();
230
+ if (normalized === 'stable') {
231
+ return getReleaseChannelBranch(normalized);
232
+ }
233
+
234
+ const stableVersion = latestGitTagVersion('v[0-9]*.[0-9]*.[0-9]*');
235
+ const betaVersion = latestGitTagVersion('v[0-9]*.[0-9]*.[0-9]*-beta.*');
236
+ const preferred = choosePreferredBranchForChannel(normalized, {
237
+ stable: stableVersion,
238
+ beta: betaVersion,
239
+ });
240
+
241
+ if (preferred === 'beta' && !gitRemoteBranchExists('beta')) {
242
+ return 'main';
243
+ }
244
+ return preferred;
245
+ }
246
+
247
+ function resolvePreferredNpmTag(channel) {
248
+ const normalized = parseReleaseChannel(channel) || currentReleaseChannel();
249
+ if (normalized === 'stable') {
250
+ return getReleaseChannelDistTag(normalized);
251
+ }
252
+
253
+ const distTags = {};
254
+ const tagsRes = runQuiet('npm', ['view', 'neoagent', 'dist-tags', '--json'], {
255
+ env: withInstallEnv(),
256
+ });
257
+ if (tagsRes.status === 0) {
258
+ try {
259
+ const parsed = JSON.parse(tagsRes.stdout || '{}');
260
+ if (parsed && typeof parsed === 'object') {
261
+ Object.assign(distTags, parsed);
262
+ }
263
+ } catch {
264
+ // Ignore parse failures and fall back to the beta tag.
265
+ }
266
+ }
267
+
268
+ return choosePreferredNpmTagForChannel(normalized, {
269
+ latest: distTags.latest,
270
+ beta: distTags.beta,
271
+ });
272
+ }
273
+
217
274
  function ensureGitBranchForReleaseChannel(targetBranch) {
218
275
  const branchRes = runQuiet('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
219
276
  const currentBranch = branchRes.status === 0 ? branchRes.stdout.trim() : '';
@@ -692,15 +749,15 @@ function cmdUpdate(args = []) {
692
749
  process.env.NEOAGENT_RELEASE_CHANNEL = releaseChannel;
693
750
  logOk(`Release channel set to ${releaseChannelSummary(releaseChannel)}`);
694
751
  }
695
- const targetBranch = getReleaseChannelBranch(releaseChannel);
696
- const npmTag = getReleaseChannelDistTag(releaseChannel);
697
752
  const versionBefore = currentInstalledVersionLabel();
698
753
  let versionAfter = versionBefore;
699
754
 
700
755
  if (fs.existsSync(path.join(APP_DIR, '.git')) && commandExists('git')) {
701
756
  const current = runQuiet('git', ['rev-parse', '--short', 'HEAD']);
702
757
 
703
- runOrThrow('git', ['fetch', 'origin', targetBranch]);
758
+ runOrThrow('git', ['fetch', 'origin', '--tags']);
759
+ const targetBranch = resolvePreferredGitBranch(releaseChannel);
760
+ logInfo(`Using git branch ${targetBranch} for the ${releaseChannel} channel.`);
704
761
  ensureGitBranchForReleaseChannel(targetBranch);
705
762
  runOrThrow('git', ['pull', '--rebase', '--autostash', 'origin', targetBranch]);
706
763
 
@@ -714,6 +771,7 @@ function cmdUpdate(args = []) {
714
771
  buildBundledWebClientIfPossible();
715
772
  }
716
773
  } else {
774
+ const npmTag = resolvePreferredNpmTag(releaseChannel);
717
775
  logWarn(`No git repo detected; attempting npm global update from ${npmTag}.`);
718
776
  if (commandExists('npm')) {
719
777
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.1.15",
3
+ "version": "2.1.16",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -14,6 +14,14 @@ const RELEASE_CHANNEL_DIST_TAGS = Object.freeze({
14
14
  stable: 'latest',
15
15
  beta: 'beta',
16
16
  });
17
+ const RELEASE_CHANNEL_BRANCH_POLICIES = Object.freeze({
18
+ stable: 'main only',
19
+ beta: 'newest of beta or main',
20
+ });
21
+ const RELEASE_CHANNEL_NPM_POLICIES = Object.freeze({
22
+ stable: 'latest only',
23
+ beta: 'newest of beta or latest',
24
+ });
17
25
 
18
26
  function parseEnv(raw) {
19
27
  const map = new Map();
@@ -62,6 +70,116 @@ function getReleaseChannelLabel(channel) {
62
70
  return normalizeReleaseChannel(channel) === 'beta' ? 'Beta' : 'Stable';
63
71
  }
64
72
 
73
+ function parseSemver(version) {
74
+ const match = String(version || '')
75
+ .trim()
76
+ .replace(/^v/, '')
77
+ .match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
78
+ if (!match) {
79
+ return null;
80
+ }
81
+
82
+ return {
83
+ major: Number(match[1]),
84
+ minor: Number(match[2]),
85
+ patch: Number(match[3]),
86
+ prerelease: match[4] ? match[4].split('.') : [],
87
+ raw: match[0],
88
+ };
89
+ }
90
+
91
+ function comparePrereleasePart(left, right) {
92
+ const leftNumeric = /^\d+$/.test(left);
93
+ const rightNumeric = /^\d+$/.test(right);
94
+
95
+ if (leftNumeric && rightNumeric) {
96
+ return Number(left) - Number(right);
97
+ }
98
+ if (leftNumeric) return -1;
99
+ if (rightNumeric) return 1;
100
+ return left.localeCompare(right);
101
+ }
102
+
103
+ function compareVersions(leftVersion, rightVersion) {
104
+ const left = parseSemver(leftVersion);
105
+ const right = parseSemver(rightVersion);
106
+
107
+ if (!left && !right) return 0;
108
+ if (!left) return -1;
109
+ if (!right) return 1;
110
+
111
+ for (const key of ['major', 'minor', 'patch']) {
112
+ if (left[key] !== right[key]) {
113
+ return left[key] - right[key];
114
+ }
115
+ }
116
+
117
+ const leftPre = left.prerelease;
118
+ const rightPre = right.prerelease;
119
+ if (leftPre.length === 0 && rightPre.length === 0) return 0;
120
+ if (leftPre.length === 0) return 1;
121
+ if (rightPre.length === 0) return -1;
122
+
123
+ const length = Math.max(leftPre.length, rightPre.length);
124
+ for (let i = 0; i < length; i++) {
125
+ const leftPart = leftPre[i];
126
+ const rightPart = rightPre[i];
127
+ if (leftPart == null) return -1;
128
+ if (rightPart == null) return 1;
129
+ const diff = comparePrereleasePart(leftPart, rightPart);
130
+ if (diff !== 0) {
131
+ return diff;
132
+ }
133
+ }
134
+
135
+ return 0;
136
+ }
137
+
138
+ function maxVersion(leftVersion, rightVersion) {
139
+ return compareVersions(leftVersion, rightVersion) >= 0 ? leftVersion : rightVersion;
140
+ }
141
+
142
+ function describeReleaseChannelPolicy(channel) {
143
+ const normalized = normalizeReleaseChannel(channel);
144
+ return `${getReleaseChannelLabel(normalized)} (git ${RELEASE_CHANNEL_BRANCH_POLICIES[normalized]}, npm ${RELEASE_CHANNEL_NPM_POLICIES[normalized]})`;
145
+ }
146
+
147
+ function getReleaseChannelBranchPolicy(channel) {
148
+ return RELEASE_CHANNEL_BRANCH_POLICIES[normalizeReleaseChannel(channel)];
149
+ }
150
+
151
+ function getReleaseChannelNpmPolicy(channel) {
152
+ return RELEASE_CHANNEL_NPM_POLICIES[normalizeReleaseChannel(channel)];
153
+ }
154
+
155
+ function choosePreferredBranchForChannel(channel, versions = {}) {
156
+ const normalized = normalizeReleaseChannel(channel);
157
+ if (normalized === 'stable') {
158
+ return 'main';
159
+ }
160
+
161
+ const stableVersion = versions.stable;
162
+ const betaVersion = versions.beta;
163
+ if (compareVersions(betaVersion, stableVersion) > 0) {
164
+ return 'beta';
165
+ }
166
+ return 'main';
167
+ }
168
+
169
+ function choosePreferredNpmTagForChannel(channel, versions = {}) {
170
+ const normalized = normalizeReleaseChannel(channel);
171
+ if (normalized === 'stable') {
172
+ return 'latest';
173
+ }
174
+
175
+ const stableVersion = versions.latest;
176
+ const betaVersion = versions.beta;
177
+ if (compareVersions(betaVersion, stableVersion) > 0) {
178
+ return 'beta';
179
+ }
180
+ return 'latest';
181
+ }
182
+
65
183
  function readReleaseChannelFromRaw(raw) {
66
184
  const env = parseEnv(raw);
67
185
  return normalizeReleaseChannel(env.get(RELEASE_CHANNEL_ENV_KEY));
@@ -116,6 +234,14 @@ module.exports = {
116
234
  getReleaseChannelBranch,
117
235
  getReleaseChannelDistTag,
118
236
  getReleaseChannelLabel,
237
+ parseSemver,
238
+ compareVersions,
239
+ maxVersion,
240
+ describeReleaseChannelPolicy,
241
+ getReleaseChannelBranchPolicy,
242
+ getReleaseChannelNpmPolicy,
243
+ choosePreferredBranchForChannel,
244
+ choosePreferredNpmTagForChannel,
119
245
  readReleaseChannelFromRaw,
120
246
  readReleaseChannelFromEnvFile,
121
247
  readConfiguredReleaseChannel,
@@ -40,6 +40,7 @@ db.exec(`
40
40
  total_tokens INTEGER DEFAULT 0,
41
41
  prompt_metrics TEXT,
42
42
  error TEXT,
43
+ final_response TEXT,
43
44
  created_at TEXT DEFAULT (datetime('now')),
44
45
  updated_at TEXT DEFAULT (datetime('now')),
45
46
  completed_at TEXT,
@@ -389,6 +390,7 @@ for (const col of [
389
390
  "ALTER TABLE scheduled_tasks ADD COLUMN run_at TEXT",
390
391
  "ALTER TABLE scheduled_tasks ADD COLUMN one_time INTEGER DEFAULT 0",
391
392
  "ALTER TABLE agent_runs ADD COLUMN prompt_metrics TEXT",
393
+ "ALTER TABLE agent_runs ADD COLUMN final_response TEXT",
392
394
  "ALTER TABLE conversations ADD COLUMN summary TEXT",
393
395
  "ALTER TABLE conversations ADD COLUMN summary_message_count INTEGER DEFAULT 0",
394
396
  "ALTER TABLE conversations ADD COLUMN last_summary TEXT",
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"052f31d115eceda8cbff1b3481fcde4330c4ae
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1964138140" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "4059413374" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });