neoagent 2.3.1-beta.113 → 2.3.1-beta.115

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/lib/manager.js CHANGED
@@ -5,6 +5,7 @@ const net = require('net');
5
5
  const crypto = require('crypto');
6
6
  const readline = require('readline');
7
7
  const { spawn, spawnSync } = require('child_process');
8
+ const { CLAUDE_CODE_SCOPES } = require('../server/services/ai/providers/claudeCode');
8
9
  const {
9
10
  buildBundledWebClientIfPossible: buildWebClient,
10
11
  commandExists: sharedCommandExists,
@@ -815,7 +816,7 @@ async function cmdLoginClaudeCode() {
815
816
  const { URL: NodeURL } = require('url');
816
817
 
817
818
  const clientId = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
818
- const SCOPES = 'user:inference user:profile org:create_api_key user:sessions:claude_code user:mcp_servers';
819
+ const SCOPES = CLAUDE_CODE_SCOPES;
819
820
 
820
821
  // The registered redirect URIs are http://localhost/callback and http://127.0.0.1/callback
821
822
  // (port 80). The OAuth server validates the URI exactly, so we must use port 80.
@@ -919,6 +920,7 @@ async function cmdLoginClaudeCode() {
919
920
  redirect_uri: redirectUri,
920
921
  client_id: clientId,
921
922
  code_verifier: codeVerifier,
923
+ scope: SCOPES,
922
924
  state,
923
925
  }),
924
926
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.113",
3
+ "version": "2.3.1-beta.115",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- 0c62ef7d28ad8b7fe9751b4f055e1afb
1
+ 1924fa6bc28b6ba8af56dfa6c7146c4b
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "699610273" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "138973699" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -129338,7 +129338,7 @@ r===$&&A.b()
129338
129338
  o.push(A.ii(p,A.iY(!1,new A.a3(B.tM,A.dT(new A.cI(B.he,new A.a5V(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
129339
129339
  if(!s.ay)if(!s.ch){r=s.e
129340
129340
  r===$&&A.b()
129341
- r=B.b.t("mpb8ba09-f0b0778").length!==0&&r.b}if(r){r=s.d
129341
+ r=B.b.t("mpb9d6tg-0a6c4fd").length!==0&&r.b}if(r){r=s.d
129342
129342
  r===$&&A.b()
129343
129343
  r=r.ag&&!r.V?84:0
129344
129344
  q=s.e
@@ -134146,7 +134146,7 @@ $S:236}
134146
134146
  A.Ys.prototype={}
134147
134147
  A.Rr.prototype={
134148
134148
  mT(a){var s=this
134149
- if(B.b.t("mpb8ba09-f0b0778").length===0||s.a!=null)return
134149
+ if(B.b.t("mpb9d6tg-0a6c4fd").length===0||s.a!=null)return
134150
134150
  s.A5()
134151
134151
  s.a=A.q1(B.PP,new A.b58(s))},
134152
134152
  A5(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -134164,7 +134164,7 @@ if(!t.f.b(k)){s=1
134164
134164
  break}i=J.Z(k,"buildId")
134165
134165
  h=i==null?null:B.b.t(J.r(i))
134166
134166
  j=h==null?"":h
134167
- if(J.bm(j)===0||J.d(j,"mpb8ba09-f0b0778")){s=1
134167
+ if(J.bm(j)===0||J.d(j,"mpb9d6tg-0a6c4fd")){s=1
134168
134168
  break}n.b=!0
134169
134169
  n.D()
134170
134170
  p=2
@@ -134181,7 +134181,7 @@ case 2:return A.i(o.at(-1),r)}})
134181
134181
  return A.k($async$A5,r)},
134182
134182
  vb(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
134183
134183
  var $async$vb=A.h(function(a2,a3){if(a2===1){o.push(a3)
134184
- s=p}for(;;)switch(s){case 0:if(B.b.t("mpb8ba09-f0b0778").length===0||n.c){s=1
134184
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mpb9d6tg-0a6c4fd").length===0||n.c){s=1
134185
134185
  break}n.c=!0
134186
134186
  n.D()
134187
134187
  p=4
@@ -9,25 +9,40 @@ const CLAUDE_CODE_BASE_URL = 'https://api.anthropic.com';
9
9
  const CLAUDE_CODE_VERSION = process.env.CLAUDE_CODE_VERSION || '2.1.75';
10
10
  const CLAUDE_CODE_OAUTH_BETA = 'claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14';
11
11
  const CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
12
+ const CLAUDE_CODE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
13
+ const CLAUDE_CODE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
14
+ const CLAUDE_CODE_SCOPES = 'user:inference user:profile org:create_api_key user:sessions:claude_code user:mcp_servers';
15
+
16
+ function readTokenRecord(data) {
17
+ const tokens = data?.claudeAiOauthTokens || data?.claudeAiOauth || {};
18
+ const access = tokens.accessToken || tokens.access;
19
+ const refresh = tokens.refreshToken || tokens.refresh;
20
+ const expires = tokens.expiresAt || tokens.expires;
21
+ return {
22
+ access: typeof access === 'string' && access ? access : null,
23
+ refresh: typeof refresh === 'string' && refresh ? refresh : null,
24
+ expires: typeof expires === 'number' && Number.isFinite(expires) ? expires : null,
25
+ };
26
+ }
12
27
 
13
28
  function readTokenValue(data) {
14
- return data?.claudeAiOauthTokens?.accessToken
15
- || data?.claudeAiOauth?.accessToken
16
- || data?.claudeAiOauth?.access
17
- || null;
29
+ return readTokenRecord(data).access;
18
30
  }
19
31
 
20
- function readClaudeCliToken() {
32
+ function readClaudeCliTokenRecord() {
21
33
  try {
22
34
  const raw = fs.readFileSync(CLAUDE_CLI_CREDS_PATH, 'utf8');
23
35
  const data = JSON.parse(raw);
24
- const token = readTokenValue(data);
25
- return typeof token === 'string' && token ? token : null;
36
+ return readTokenRecord(data);
26
37
  } catch {
27
- return null;
38
+ return { access: null, refresh: null, expires: null };
28
39
  }
29
40
  }
30
41
 
42
+ function readClaudeCliToken() {
43
+ return readClaudeCliTokenRecord().access;
44
+ }
45
+
31
46
  function mergeAnthropicBeta(existing) {
32
47
  if (!existing) return CLAUDE_CODE_OAUTH_BETA;
33
48
  const seen = new Set();
@@ -43,6 +58,110 @@ function mergeAnthropicBeta(existing) {
43
58
  .join(',');
44
59
  }
45
60
 
61
+ function normalizeExpiresAt(data) {
62
+ if (typeof data.expires_at === 'number' && Number.isFinite(data.expires_at)) {
63
+ return data.expires_at > 10_000_000_000 ? data.expires_at : data.expires_at * 1000;
64
+ }
65
+ if (typeof data.expiresAt === 'number' && Number.isFinite(data.expiresAt)) {
66
+ return data.expiresAt > 10_000_000_000 ? data.expiresAt : data.expiresAt * 1000;
67
+ }
68
+ if (typeof data.expires_in === 'number' && Number.isFinite(data.expires_in)) {
69
+ return Date.now() + (data.expires_in * 1000);
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function sanitizeEnvKey(key) {
75
+ return String(key).replace(/[\r\n]/g, '');
76
+ }
77
+
78
+ function sanitizeEnvValue(value) {
79
+ return String(value).replace(/[\r\n]/g, '');
80
+ }
81
+
82
+ function persistEnvValue(key, value) {
83
+ if (!value) return;
84
+ try {
85
+ const { ENV_FILE } = require('../../../../runtime/paths');
86
+ const safeKey = sanitizeEnvKey(key);
87
+ const safeValue = sanitizeEnvValue(value);
88
+ const raw = fs.existsSync(ENV_FILE) ? fs.readFileSync(ENV_FILE, 'utf8') : '';
89
+ const lines = raw ? raw.split('\n') : [];
90
+ let replaced = false;
91
+ for (let i = 0; i < lines.length; i++) {
92
+ if (lines[i].startsWith(`${safeKey}=`)) {
93
+ lines[i] = `${safeKey}=${safeValue}`;
94
+ replaced = true;
95
+ break;
96
+ }
97
+ }
98
+ if (!replaced) lines.push(`${safeKey}=${safeValue}`);
99
+ const output = lines.filter((_, idx, arr) => idx !== arr.length - 1 || arr[idx] !== '').join('\n') + '\n';
100
+ fs.mkdirSync(path.dirname(ENV_FILE), { recursive: true });
101
+ fs.writeFileSync(ENV_FILE, output, { mode: 0o600 });
102
+ } catch { }
103
+ }
104
+
105
+ async function refreshClaudeCodeAccessToken(refreshToken, fetchImpl = fetch) {
106
+ if (!refreshToken) return null;
107
+ const response = await fetchImpl(CLAUDE_CODE_TOKEN_URL, {
108
+ method: 'POST',
109
+ headers: {
110
+ 'Content-Type': 'application/json',
111
+ 'Accept': 'application/json',
112
+ 'anthropic-version': '2023-06-01',
113
+ },
114
+ body: JSON.stringify({
115
+ grant_type: 'refresh_token',
116
+ refresh_token: refreshToken,
117
+ client_id: CLAUDE_CODE_CLIENT_ID,
118
+ scope: CLAUDE_CODE_SCOPES,
119
+ }),
120
+ });
121
+
122
+ const text = await response.text();
123
+ let data = {};
124
+ try {
125
+ data = text ? JSON.parse(text) : {};
126
+ } catch {
127
+ data = {};
128
+ }
129
+
130
+ if (!response.ok) {
131
+ const detail = data?.error?.message || data?.error_description || data?.error || text || 'Unknown error';
132
+ throw new Error(`Claude Code OAuth refresh failed: HTTP ${response.status} ${detail}`);
133
+ }
134
+ if (!data.access_token) {
135
+ throw new Error('Claude Code OAuth refresh succeeded but no access_token was returned.');
136
+ }
137
+
138
+ return {
139
+ access: data.access_token,
140
+ refresh: data.refresh_token || refreshToken,
141
+ expires: normalizeExpiresAt(data),
142
+ };
143
+ }
144
+
145
+ function isAuthenticationError(err) {
146
+ return err?.status === 401 || err?.error?.type === 'authentication_error';
147
+ }
148
+
149
+ function isInferenceScopeError(err) {
150
+ const message = String(err?.error?.message || err?.message || '');
151
+ const type = String(err?.error?.type || err?.type || '');
152
+ return err?.status === 403
153
+ && (type === 'permission_error' || message.includes('"permission_error"'))
154
+ && message.includes('scope requirement')
155
+ && message.includes('user:inference');
156
+ }
157
+
158
+ function formatClaudeCodeCredentialError(err) {
159
+ if (isInferenceScopeError(err)) {
160
+ return new Error(`Claude Code OAuth token is missing inference scope. Re-run \`neoagent login claude-code\` to create a token with ${CLAUDE_CODE_SCOPES}.`);
161
+ }
162
+ return err;
163
+ }
164
+
46
165
  class ClaudeCodeProvider extends AnthropicProvider {
47
166
  constructor(config = {}) {
48
167
  super(config);
@@ -58,12 +177,17 @@ class ClaudeCodeProvider extends AnthropicProvider {
58
177
  'claude-haiku-4-5-20251001': 200000,
59
178
  };
60
179
 
61
- const authToken = config.apiKey || process.env.CLAUDE_CODE_OAUTH_TOKEN || readClaudeCliToken();
180
+ const cliTokenRecord = readClaudeCliTokenRecord();
181
+ const authToken = config.apiKey || process.env.CLAUDE_CODE_OAUTH_TOKEN || cliTokenRecord.access;
62
182
  if (!authToken) {
63
183
  console.warn('[ClaudeCode] No access token. Run `neoagent login claude-code` to authenticate.');
64
184
  }
65
185
 
66
- const defaultHeaders = {
186
+ this.authToken = authToken || null;
187
+ this.refreshToken = config.refreshToken || process.env.CLAUDE_CODE_REFRESH_TOKEN || cliTokenRecord.refresh || null;
188
+ this.fetchImpl = config.fetch || fetch;
189
+ this.baseURL = config.baseUrl || CLAUDE_CODE_BASE_URL;
190
+ this.defaultHeaders = {
67
191
  ...(config.defaultHeaders || {}),
68
192
  accept: 'application/json',
69
193
  'anthropic-dangerous-direct-browser-access': 'true',
@@ -72,16 +196,35 @@ class ClaudeCodeProvider extends AnthropicProvider {
72
196
  'x-app': 'cli',
73
197
  };
74
198
 
199
+ this.client = this.createClient(this.authToken, config);
200
+ }
201
+
202
+ createClient(authToken, config = this.config) {
75
203
  // OAuth tokens use Authorization: Bearer. Claude Code subscription inference
76
204
  // also requires the Claude Code beta surface headers used by the official CLI.
77
- this.client = new Anthropic({
205
+ return new Anthropic({
78
206
  authToken: authToken || undefined,
79
- baseURL: config.baseUrl || CLAUDE_CODE_BASE_URL,
80
- defaultHeaders: authToken ? defaultHeaders : config.defaultHeaders,
81
- ...(config.fetch ? { fetch: config.fetch } : {}),
207
+ baseURL: this.baseURL,
208
+ defaultHeaders: authToken ? this.defaultHeaders : config.defaultHeaders,
209
+ ...(this.fetchImpl ? { fetch: this.fetchImpl } : {}),
82
210
  });
83
211
  }
84
212
 
213
+ async refreshClient() {
214
+ const refreshed = await refreshClaudeCodeAccessToken(this.refreshToken, this.fetchImpl);
215
+ if (!refreshed?.access) return false;
216
+ this.authToken = refreshed.access;
217
+ this.refreshToken = refreshed.refresh || this.refreshToken;
218
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = this.authToken;
219
+ persistEnvValue('CLAUDE_CODE_OAUTH_TOKEN', this.authToken);
220
+ if (this.refreshToken) {
221
+ process.env.CLAUDE_CODE_REFRESH_TOKEN = this.refreshToken;
222
+ persistEnvValue('CLAUDE_CODE_REFRESH_TOKEN', this.refreshToken);
223
+ }
224
+ this.client = this.createClient(this.authToken);
225
+ return true;
226
+ }
227
+
85
228
  convertMessages(messages) {
86
229
  const converted = super.convertMessages(messages);
87
230
  if (!converted.system) {
@@ -94,6 +237,38 @@ class ClaudeCodeProvider extends AnthropicProvider {
94
237
  ];
95
238
  return converted;
96
239
  }
240
+
241
+ async chat(messages, tools = [], options = {}) {
242
+ try {
243
+ return await super.chat(messages, tools, options);
244
+ } catch (err) {
245
+ if ((!isAuthenticationError(err) && !isInferenceScopeError(err)) || !this.refreshToken) {
246
+ throw formatClaudeCodeCredentialError(err);
247
+ }
248
+ await this.refreshClient();
249
+ try {
250
+ return await super.chat(messages, tools, options);
251
+ } catch (retryErr) {
252
+ throw formatClaudeCodeCredentialError(retryErr);
253
+ }
254
+ }
255
+ }
256
+
257
+ async *stream(messages, tools = [], options = {}) {
258
+ try {
259
+ yield* super.stream(messages, tools, options);
260
+ } catch (err) {
261
+ if ((!isAuthenticationError(err) && !isInferenceScopeError(err)) || !this.refreshToken) {
262
+ throw formatClaudeCodeCredentialError(err);
263
+ }
264
+ await this.refreshClient();
265
+ try {
266
+ yield* super.stream(messages, tools, options);
267
+ } catch (retryErr) {
268
+ throw formatClaudeCodeCredentialError(retryErr);
269
+ }
270
+ }
271
+ }
97
272
  }
98
273
 
99
- module.exports = { ClaudeCodeProvider, readClaudeCliToken };
274
+ module.exports = { ClaudeCodeProvider, readClaudeCliToken, refreshClaudeCodeAccessToken, CLAUDE_CODE_SCOPES };
@@ -79,33 +79,45 @@ function normalizeContent(content) {
79
79
  return String(content);
80
80
  }
81
81
 
82
- function normalizeInputContent(content) {
82
+ function normalizeMessageContent(content, role = 'user') {
83
83
  if (content == null) return [];
84
+ const isAssistant = role === 'assistant';
85
+ const textType = isAssistant ? 'output_text' : 'input_text';
84
86
 
85
87
  if (typeof content === 'string') {
86
- return [{ type: 'input_text', text: content }];
88
+ return [{ type: textType, text: content, ...(isAssistant ? { annotations: [] } : {}) }];
87
89
  }
88
90
 
89
91
  if (!Array.isArray(content)) {
90
92
  const text = String(content);
91
- return text ? [{ type: 'input_text', text }] : [];
93
+ return text ? [{ type: textType, text, ...(isAssistant ? { annotations: [] } : {}) }] : [];
92
94
  }
93
95
 
94
96
  const parts = [];
95
97
  for (const part of content) {
96
98
  if (!part) continue;
97
99
  if (typeof part === 'string') {
98
- if (part.trim()) parts.push({ type: 'input_text', text: part });
100
+ if (part.trim()) parts.push({ type: textType, text: part, ...(isAssistant ? { annotations: [] } : {}) });
99
101
  continue;
100
102
  }
101
103
  if (part.type === 'text' && typeof part.text === 'string') {
102
- parts.push({ type: 'input_text', text: part.text });
104
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: [] } : {}) });
103
105
  continue;
104
106
  }
105
107
  if (part.type === 'input_text' && typeof part.text === 'string') {
106
- parts.push({ type: 'input_text', text: part.text });
108
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: [] } : {}) });
107
109
  continue;
108
110
  }
111
+ if (part.type === 'output_text' && typeof part.text === 'string') {
112
+ parts.push({ type: textType, text: part.text, ...(isAssistant ? { annotations: part.annotations || [] } : {}) });
113
+ continue;
114
+ }
115
+ if (isAssistant && part.type === 'refusal') {
116
+ const refusal = typeof part.refusal === 'string' ? part.refusal : part.text;
117
+ if (typeof refusal === 'string') parts.push({ type: 'refusal', refusal });
118
+ continue;
119
+ }
120
+ if (isAssistant) continue;
109
121
  if (part.type === 'image_url') {
110
122
  const imageUrl = typeof part.image_url === 'string' ? part.image_url : part.image_url?.url;
111
123
  if (imageUrl) {
@@ -278,11 +290,12 @@ class OpenAICodexProvider extends BaseProvider {
278
290
  continue;
279
291
  }
280
292
 
281
- const content = normalizeInputContent(msg.content);
293
+ const role = msg.role === 'assistant' ? 'assistant' : 'user';
294
+ const content = normalizeMessageContent(msg.content, role);
282
295
  if (content.length > 0) {
283
296
  input.push({
284
297
  type: 'message',
285
- role: msg.role === 'assistant' ? 'assistant' : 'user',
298
+ role,
286
299
  content,
287
300
  });
288
301
  }