@intent-systems/nexus 2026.1.5-3 → 2026.1.5-5
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/agents/agent-id.js +41 -0
- package/dist/agents/auth-profiles.js +114 -25
- package/dist/agents/identity-state.js +79 -0
- package/dist/agents/model-auth.js +1 -0
- package/dist/agents/model-fallback.js +15 -9
- package/dist/agents/model-selection.js +1 -1
- package/dist/agents/models-config.js +17 -11
- package/dist/agents/pi-embedded-runner.js +101 -9
- package/dist/agents/sandbox.js +12 -3
- package/dist/agents/skill-runner.js +29 -4
- package/dist/agents/skill-usage.js +114 -11
- package/dist/agents/skills-status.js +4 -4
- package/dist/agents/skills.js +18 -7
- package/dist/agents/subagent-registry.js +25 -11
- package/dist/agents/system-prompt.js +16 -0
- package/dist/agents/tool-policy.js +19 -3
- package/dist/agents/tools/browser-tool.js +5 -2
- package/dist/agents/tools/image-tool.js +93 -8
- package/dist/agents/tools/sessions-announce-target.js +5 -1
- package/dist/agents/workspace.js +55 -46
- package/dist/auto-reply/command-detection.js +2 -1
- package/dist/auto-reply/reply/directive-handling.js +153 -28
- package/dist/auto-reply/reply/directives.js +17 -2
- package/dist/auto-reply/reply/model-selection.js +8 -3
- package/dist/auto-reply/reply/queue.js +2 -2
- package/dist/auto-reply/reply.js +1 -1
- package/dist/auto-reply/thinking.js +15 -0
- package/dist/browser/chrome.js +1 -1
- package/dist/browser/client.js +2 -0
- package/dist/browser/config.js +6 -2
- package/dist/browser/pw-tools-core.js +3 -0
- package/dist/browser/routes/agent.js +14 -0
- package/dist/canvas-host/server.js +1 -1
- package/dist/capabilities/detector.js +245 -0
- package/dist/capabilities/registry.js +99 -0
- package/dist/channels/location.js +44 -0
- package/dist/channels/web/index.js +2 -0
- package/dist/cli/cloud-cli.js +12 -7
- package/dist/cli/credential-cli.js +139 -17
- package/dist/cli/gateway-cli.js +1 -1
- package/dist/cli/log-cli.js +25 -0
- package/dist/cli/pairing-cli.js +1 -1
- package/dist/cli/program.js +58 -6
- package/dist/cli/run-main.js +1 -1
- package/dist/cli/skills-cli.js +144 -21
- package/dist/cli/skills-hub-cli.js +59 -29
- package/dist/cli/tool-connector-cli.js +99 -24
- package/dist/cli/upstream-sync-cli.js +253 -96
- package/dist/cli/usage-cli.js +14 -0
- package/dist/commands/auth-choice-options.js +6 -1
- package/dist/commands/auth-choice.js +157 -5
- package/dist/commands/bootstrap-preset.js +10 -6
- package/dist/commands/capabilities.js +33 -6
- package/dist/commands/claude-md.js +3 -2
- package/dist/commands/config-view.js +1 -1
- package/dist/commands/configure.js +4 -4
- package/dist/commands/credential.js +497 -36
- package/dist/commands/cursor-rules.js +39 -19
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/identity.js +28 -31
- package/dist/commands/init.js +15 -18
- package/dist/commands/log.js +134 -0
- package/dist/commands/models/fallbacks.js +1 -1
- package/dist/commands/models/image-fallbacks.js +1 -1
- package/dist/commands/models/list.js +1 -1
- package/dist/commands/models/scan.js +1 -1
- package/dist/commands/onboard-auth.js +27 -2
- package/dist/commands/onboard-eve-identity.js +7 -8
- package/dist/commands/onboard-non-interactive.js +4 -2
- package/dist/commands/onboard-quickstart.js +18 -11
- package/dist/commands/quest-state.js +271 -0
- package/dist/commands/quest.js +53 -13
- package/dist/commands/reset.js +1 -1
- package/dist/commands/sessions-ingest.js +5 -4
- package/dist/commands/setup.js +4 -2
- package/dist/commands/skills-manifest.js +2 -2
- package/dist/commands/status.js +179 -61
- package/dist/commands/suggestions.js +1 -1
- package/dist/commands/usage-tracking.js +32 -0
- package/dist/commands/usage-upload.js +6 -1
- package/dist/config/defaults.js +1 -3
- package/dist/config/includes.js +5 -7
- package/dist/config/io.js +88 -16
- package/dist/config/legacy.js +4 -2
- package/dist/config/paths.js +16 -0
- package/dist/config/sessions.js +9 -5
- package/dist/config/zod-schema.js +4 -3
- package/dist/control-plane/broker/broker.js +1022 -0
- package/dist/control-plane/compaction.js +282 -0
- package/dist/control-plane/factory.js +31 -0
- package/dist/control-plane/index.js +10 -0
- package/dist/control-plane/odu/agents.js +192 -0
- package/dist/control-plane/odu/interaction-tools.js +208 -0
- package/dist/control-plane/odu/prompt-loader.js +95 -0
- package/dist/control-plane/odu/runtime.js +479 -0
- package/dist/control-plane/odu/types.js +6 -0
- package/dist/control-plane/odu-control-plane.js +316 -0
- package/dist/control-plane/single-agent.js +249 -0
- package/dist/control-plane/types.js +11 -0
- package/dist/credentials/store.js +449 -0
- package/dist/gateway/server-browser.js +5 -4
- package/dist/gateway/server-methods/cron.js +11 -1
- package/dist/gateway/server.js +14 -7
- package/dist/infra/bonjour.js +1 -1
- package/dist/infra/event-log.js +8 -2
- package/dist/infra/path-env.js +1 -2
- package/dist/infra/provider-usage.auth.js +5 -3
- package/dist/infra/provider-usage.fetch.claude.js +16 -6
- package/dist/infra/provider-usage.fetch.minimax.js +8 -3
- package/dist/infra/provider-usage.js +9 -5
- package/dist/infra/restart.js +2 -2
- package/dist/infra/usage-settings.js +78 -0
- package/dist/infra/usage-suggestions.js +17 -5
- package/dist/infra/usage-upload.js +38 -1
- package/dist/infra/voicewake.js +2 -2
- package/dist/logging/redact.js +109 -0
- package/dist/markdown/fences.js +58 -0
- package/dist/media/image-ops.js +3 -1
- package/dist/memory/embeddings.js +146 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/internal.js +163 -0
- package/dist/pairing/pairing-store.js +218 -0
- package/dist/plugins/cli.js +42 -0
- package/dist/plugins/discovery.js +253 -0
- package/dist/plugins/install.js +181 -0
- package/dist/plugins/loader.js +290 -0
- package/dist/plugins/registry.js +105 -0
- package/dist/plugins/status.js +29 -0
- package/dist/plugins/tools.js +39 -0
- package/dist/plugins/types.js +1 -0
- package/dist/providers/github-copilot-auth.js +1 -1
- package/dist/routing/resolve-route.js +144 -0
- package/dist/routing/session-key.js +65 -0
- package/dist/sessions/send-policy.js +5 -5
- package/dist/slack/monitor.js +22 -1
- package/dist/telegram/reaction-level.js +2 -1
- package/dist/utils/provider-utils.js +28 -0
- package/dist/utils.js +4 -3
- package/dist/wizard/onboarding.js +29 -7
- package/package.json +4 -29
- package/patches/@mariozechner__pi-ai.patch +215 -0
- package/patches/playwright-core@1.57.0.patch +13 -0
- package/patches/qrcode-terminal.patch +12 -0
- package/scripts/postinstall.js +202 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
|
2
2
|
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
|
3
3
|
function resolveClaudeWebSessionKey() {
|
|
4
|
-
const direct = process.env.CLAUDE_AI_SESSION_KEY?.trim() ??
|
|
4
|
+
const direct = process.env.CLAUDE_AI_SESSION_KEY?.trim() ??
|
|
5
|
+
process.env.CLAUDE_WEB_SESSION_KEY?.trim();
|
|
5
6
|
if (direct?.startsWith("sk-ant-"))
|
|
6
7
|
return direct;
|
|
7
8
|
const cookieHeader = process.env.CLAUDE_WEB_COOKIE?.trim();
|
|
@@ -33,14 +34,18 @@ async function fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn) {
|
|
|
33
34
|
windows.push({
|
|
34
35
|
label: "5h",
|
|
35
36
|
usedPercent: clampPercent(data.five_hour.utilization),
|
|
36
|
-
resetAt: data.five_hour.resets_at
|
|
37
|
+
resetAt: data.five_hour.resets_at
|
|
38
|
+
? new Date(data.five_hour.resets_at).getTime()
|
|
39
|
+
: undefined,
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
if (data.seven_day?.utilization !== undefined) {
|
|
40
43
|
windows.push({
|
|
41
44
|
label: "Week",
|
|
42
45
|
usedPercent: clampPercent(data.seven_day.utilization),
|
|
43
|
-
resetAt: data.seven_day.resets_at
|
|
46
|
+
resetAt: data.seven_day.resets_at
|
|
47
|
+
? new Date(data.seven_day.resets_at).getTime()
|
|
48
|
+
: undefined,
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
51
|
const modelWindow = data.seven_day_sonnet || data.seven_day_opus;
|
|
@@ -82,7 +87,8 @@ export async function fetchClaudeUsage(token, timeoutMs, fetchFn) {
|
|
|
82
87
|
// Claude CLI setup-token yields tokens that can be used for inference, but may not
|
|
83
88
|
// include user:profile scope required by the OAuth usage endpoint. When a claude.ai
|
|
84
89
|
// browser sessionKey is available, fall back to the web API.
|
|
85
|
-
if (res.status === 403 &&
|
|
90
|
+
if (res.status === 403 &&
|
|
91
|
+
message?.includes("scope requirement user:profile")) {
|
|
86
92
|
const sessionKey = resolveClaudeWebSessionKey();
|
|
87
93
|
if (sessionKey) {
|
|
88
94
|
const web = await fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn);
|
|
@@ -104,14 +110,18 @@ export async function fetchClaudeUsage(token, timeoutMs, fetchFn) {
|
|
|
104
110
|
windows.push({
|
|
105
111
|
label: "5h",
|
|
106
112
|
usedPercent: clampPercent(data.five_hour.utilization),
|
|
107
|
-
resetAt: data.five_hour.resets_at
|
|
113
|
+
resetAt: data.five_hour.resets_at
|
|
114
|
+
? new Date(data.five_hour.resets_at).getTime()
|
|
115
|
+
: undefined,
|
|
108
116
|
});
|
|
109
117
|
}
|
|
110
118
|
if (data.seven_day?.utilization !== undefined) {
|
|
111
119
|
windows.push({
|
|
112
120
|
label: "Week",
|
|
113
121
|
usedPercent: clampPercent(data.seven_day.utilization),
|
|
114
|
-
resetAt: data.seven_day.resets_at
|
|
122
|
+
resetAt: data.seven_day.resets_at
|
|
123
|
+
? new Date(data.seven_day.resets_at).getTime()
|
|
124
|
+
: undefined,
|
|
115
125
|
});
|
|
116
126
|
}
|
|
117
127
|
const modelWindow = data.seven_day_sonnet || data.seven_day_opus;
|
|
@@ -178,8 +178,12 @@ export async function fetchMinimaxUsage(apiKey, timeoutMs, fetchFn) {
|
|
|
178
178
|
error: "Invalid JSON",
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
|
-
const baseResp = isRecord(data.base_resp)
|
|
182
|
-
|
|
181
|
+
const baseResp = isRecord(data.base_resp)
|
|
182
|
+
? data.base_resp
|
|
183
|
+
: undefined;
|
|
184
|
+
if (baseResp &&
|
|
185
|
+
typeof baseResp.status_code === "number" &&
|
|
186
|
+
baseResp.status_code !== 0) {
|
|
183
187
|
return {
|
|
184
188
|
provider: "minimax",
|
|
185
189
|
displayName: PROVIDER_LABELS.minimax,
|
|
@@ -197,7 +201,8 @@ export async function fetchMinimaxUsage(apiKey, timeoutMs, fetchFn) {
|
|
|
197
201
|
error: "Unsupported response shape",
|
|
198
202
|
};
|
|
199
203
|
}
|
|
200
|
-
const resetAt = parseEpoch(pickString(payload, RESET_KEYS)) ??
|
|
204
|
+
const resetAt = parseEpoch(pickString(payload, RESET_KEYS)) ??
|
|
205
|
+
parseEpoch(pickNumber(payload, RESET_KEYS));
|
|
201
206
|
const windows = [
|
|
202
207
|
{
|
|
203
208
|
label: deriveWindowLabel(payload),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { resolveProviderAuths } from "./provider-usage.auth.js";
|
|
1
|
+
import { resolveProviderAuths, } from "./provider-usage.auth.js";
|
|
2
2
|
import { fetchClaudeUsage } from "./provider-usage.fetch.claude.js";
|
|
3
3
|
import { fetchMinimaxUsage } from "./provider-usage.fetch.minimax.js";
|
|
4
|
-
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
|
5
4
|
import { fetchJson } from "./provider-usage.fetch.shared.js";
|
|
5
|
+
import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
|
|
6
6
|
function resolveProviders(params) {
|
|
7
7
|
if (params.providers && params.providers.length > 0) {
|
|
8
8
|
return params.providers;
|
|
@@ -50,7 +50,9 @@ async function fetchZaiUsage(apiKey, timeoutMs, fetchFn) {
|
|
|
50
50
|
const limit = data?.data?.limits?.find((entry) => entry?.type === "TOKENS_LIMIT") ??
|
|
51
51
|
data?.data?.limits?.[0];
|
|
52
52
|
const usedPercent = clampPercent(limit?.percentage ?? 0);
|
|
53
|
-
const resetAt = limit?.nextResetTime
|
|
53
|
+
const resetAt = limit?.nextResetTime
|
|
54
|
+
? Date.parse(limit.nextResetTime)
|
|
55
|
+
: undefined;
|
|
54
56
|
const windows = [];
|
|
55
57
|
if (Number.isFinite(usedPercent)) {
|
|
56
58
|
windows.push({
|
|
@@ -102,7 +104,7 @@ export async function loadProviderUsageSummary(params) {
|
|
|
102
104
|
}
|
|
103
105
|
return { updatedAt: now, providers: snapshots };
|
|
104
106
|
}
|
|
105
|
-
export function formatUsageSummaryLine(summary,
|
|
107
|
+
export function formatUsageSummaryLine(summary, _opts = {}) {
|
|
106
108
|
const providers = summary.providers ?? [];
|
|
107
109
|
let best;
|
|
108
110
|
for (const provider of providers) {
|
|
@@ -133,7 +135,9 @@ export function formatUsageReportLines(summary, opts = {}) {
|
|
|
133
135
|
}
|
|
134
136
|
for (const window of provider.windows) {
|
|
135
137
|
const remaining = Math.max(0, 100 - window.usedPercent);
|
|
136
|
-
const reset = window.resetAt
|
|
138
|
+
const reset = window.resetAt
|
|
139
|
+
? formatResetLabel(window.resetAt, now)
|
|
140
|
+
: null;
|
|
137
141
|
const extras = [provider.plan ? `plan ${provider.plan}` : null, reset]
|
|
138
142
|
.filter(Boolean)
|
|
139
143
|
.join(", ");
|
package/dist/infra/restart.js
CHANGED
|
@@ -4,7 +4,7 @@ const DEFAULT_SYSTEMD_UNIT = "nexus-gateway.service";
|
|
|
4
4
|
export function triggerNexusRestart() {
|
|
5
5
|
if (process.platform !== "darwin") {
|
|
6
6
|
if (process.platform === "linux") {
|
|
7
|
-
const unit = process.env.NEXUS_SYSTEMD_UNIT ||
|
|
7
|
+
const unit = process.env.NEXUS_SYSTEMD_UNIT || DEFAULT_SYSTEMD_UNIT;
|
|
8
8
|
const userRestart = spawnSync("systemctl", ["--user", "restart", unit], {
|
|
9
9
|
stdio: "ignore",
|
|
10
10
|
});
|
|
@@ -21,7 +21,7 @@ export function triggerNexusRestart() {
|
|
|
21
21
|
}
|
|
22
22
|
return "supervisor";
|
|
23
23
|
}
|
|
24
|
-
const label = process.env.NEXUS_LAUNCHD_LABEL ||
|
|
24
|
+
const label = process.env.NEXUS_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL;
|
|
25
25
|
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
26
26
|
const target = uid !== undefined ? `gui/${uid}/${label}` : label;
|
|
27
27
|
spawnSync("launchctl", ["kickstart", "-k", target], { stdio: "ignore" });
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const SETTINGS_URL_ENV = "NEXUS_USAGE_SETTINGS_URL";
|
|
2
|
+
const UPLOAD_URL_ENV = "NEXUS_USAGE_UPLOAD_URL";
|
|
3
|
+
const UPLOAD_TOKEN_ENV = "NEXUS_USAGE_UPLOAD_TOKEN";
|
|
4
|
+
export function resolveUsageSettingsUrl(env = process.env) {
|
|
5
|
+
const override = env[SETTINGS_URL_ENV]?.trim();
|
|
6
|
+
if (override)
|
|
7
|
+
return override;
|
|
8
|
+
const uploadUrl = env[UPLOAD_URL_ENV]?.trim();
|
|
9
|
+
if (!uploadUrl)
|
|
10
|
+
return null;
|
|
11
|
+
return uploadUrl.replace(/\/api\/usage\/upload\/?$/, "/api/usage/settings");
|
|
12
|
+
}
|
|
13
|
+
function resolveAuthHeaders(env = process.env) {
|
|
14
|
+
const token = env[UPLOAD_TOKEN_ENV]?.trim();
|
|
15
|
+
if (!token)
|
|
16
|
+
return {};
|
|
17
|
+
return { Authorization: `Bearer ${token}` };
|
|
18
|
+
}
|
|
19
|
+
function parseSettingsResponse(payload) {
|
|
20
|
+
if (!payload || typeof payload !== "object")
|
|
21
|
+
return null;
|
|
22
|
+
const data = payload;
|
|
23
|
+
if (typeof data.canOptOut !== "boolean" || typeof data.optOut !== "boolean") {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
ok: typeof data.ok === "boolean" ? data.ok : true,
|
|
28
|
+
canOptOut: data.canOptOut,
|
|
29
|
+
optOut: data.optOut,
|
|
30
|
+
planName: typeof data.planName === "string" ? data.planName : undefined,
|
|
31
|
+
error: typeof data.error === "string" ? data.error : undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function fetchUsageTrackingSettings(env = process.env) {
|
|
35
|
+
const url = resolveUsageSettingsUrl(env);
|
|
36
|
+
if (!url)
|
|
37
|
+
return null;
|
|
38
|
+
const headers = resolveAuthHeaders(env);
|
|
39
|
+
if (!headers.Authorization)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(url, { headers });
|
|
43
|
+
const payload = await res.json().catch(() => null);
|
|
44
|
+
const parsed = parseSettingsResponse(payload);
|
|
45
|
+
if (!parsed)
|
|
46
|
+
return null;
|
|
47
|
+
return { ...parsed, ok: res.ok && parsed.ok };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function updateUsageTrackingSettings(optOut, env = process.env) {
|
|
54
|
+
const url = resolveUsageSettingsUrl(env);
|
|
55
|
+
if (!url)
|
|
56
|
+
return null;
|
|
57
|
+
const headers = {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
...resolveAuthHeaders(env),
|
|
60
|
+
};
|
|
61
|
+
if (!headers.Authorization)
|
|
62
|
+
return null;
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(url, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers,
|
|
67
|
+
body: JSON.stringify({ optOut }),
|
|
68
|
+
});
|
|
69
|
+
const payload = await res.json().catch(() => null);
|
|
70
|
+
const parsed = parseSettingsResponse(payload);
|
|
71
|
+
if (!parsed)
|
|
72
|
+
return null;
|
|
73
|
+
return { ...parsed, ok: res.ok && parsed.ok };
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -58,17 +58,26 @@ export async function loadUsageEvents(opts = {}) {
|
|
|
58
58
|
catch {
|
|
59
59
|
continue;
|
|
60
60
|
}
|
|
61
|
-
const sessionId =
|
|
61
|
+
const sessionId = typeof raw.session_id === "string" || typeof raw.session_id === "number"
|
|
62
|
+
? String(raw.session_id)
|
|
63
|
+
: "";
|
|
62
64
|
const seq = Number(raw.seq ?? 0);
|
|
63
65
|
const ts = Number(raw.ts ?? 0);
|
|
64
|
-
const source =
|
|
65
|
-
|
|
66
|
+
const source = typeof raw.source === "string" || typeof raw.source === "number"
|
|
67
|
+
? String(raw.source)
|
|
68
|
+
: "";
|
|
69
|
+
if (!sessionId ||
|
|
70
|
+
!Number.isFinite(seq) ||
|
|
71
|
+
!Number.isFinite(ts) ||
|
|
72
|
+
!source) {
|
|
66
73
|
continue;
|
|
67
74
|
}
|
|
68
75
|
if (opts.sinceMs && ts < opts.sinceMs) {
|
|
69
76
|
continue;
|
|
70
77
|
}
|
|
71
|
-
if (opts.sources &&
|
|
78
|
+
if (opts.sources &&
|
|
79
|
+
opts.sources.length > 0 &&
|
|
80
|
+
!opts.sources.includes(source)) {
|
|
72
81
|
continue;
|
|
73
82
|
}
|
|
74
83
|
const evt = {
|
|
@@ -76,7 +85,10 @@ export async function loadUsageEvents(opts = {}) {
|
|
|
76
85
|
seq,
|
|
77
86
|
ts,
|
|
78
87
|
source,
|
|
79
|
-
eventType:
|
|
88
|
+
eventType: typeof raw.event_type === "string" ||
|
|
89
|
+
typeof raw.event_type === "number"
|
|
90
|
+
? String(raw.event_type)
|
|
91
|
+
: "",
|
|
80
92
|
commandPath: typeof raw.command_path === "string" ? raw.command_path : undefined,
|
|
81
93
|
stream: typeof raw.stream === "string" ? raw.stream : undefined,
|
|
82
94
|
data: raw.data && typeof raw.data === "object"
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { resolveStateDir } from "../config/paths.js";
|
|
6
6
|
import { resolveEventLogDir } from "./event-log.js";
|
|
7
|
+
import { fetchUsageTrackingSettings } from "./usage-settings.js";
|
|
7
8
|
const UPLOAD_URL_ENV = "NEXUS_USAGE_UPLOAD_URL";
|
|
8
9
|
const UPLOAD_TOKEN_ENV = "NEXUS_USAGE_UPLOAD_TOKEN";
|
|
9
10
|
const OUTBOX_DIR_ENV = "NEXUS_USAGE_OUTBOX_DIR";
|
|
@@ -39,9 +40,34 @@ function writeJSON(pathname, data) {
|
|
|
39
40
|
fs.mkdirSync(path.dirname(pathname), { recursive: true });
|
|
40
41
|
fs.writeFileSync(pathname, JSON.stringify(data, null, 2), "utf-8");
|
|
41
42
|
}
|
|
43
|
+
function updateTrackingState(state, settings) {
|
|
44
|
+
if (!settings)
|
|
45
|
+
return;
|
|
46
|
+
state.opted_out = settings.canOptOut && settings.optOut;
|
|
47
|
+
state.settings_checked_at = Date.now();
|
|
48
|
+
writeJSON(stateFilePath(), state);
|
|
49
|
+
}
|
|
50
|
+
function isTrackingDisabled(state, settings) {
|
|
51
|
+
if (settings)
|
|
52
|
+
return settings.canOptOut && settings.optOut;
|
|
53
|
+
return Boolean(state.opted_out);
|
|
54
|
+
}
|
|
55
|
+
function clearUsageOutbox() {
|
|
56
|
+
const outboxDir = resolveOutboxDir();
|
|
57
|
+
if (!fs.existsSync(outboxDir))
|
|
58
|
+
return 0;
|
|
59
|
+
const files = fs
|
|
60
|
+
.readdirSync(outboxDir)
|
|
61
|
+
.filter((name) => name.endsWith(".json"))
|
|
62
|
+
.map((name) => path.join(outboxDir, name));
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
fs.unlinkSync(file);
|
|
65
|
+
}
|
|
66
|
+
return files.length;
|
|
67
|
+
}
|
|
42
68
|
function getOrCreateUsageState() {
|
|
43
69
|
const existing = readJSON(stateFilePath());
|
|
44
|
-
if (existing
|
|
70
|
+
if (existing?.anon_id && existing.anon_salt) {
|
|
45
71
|
return existing;
|
|
46
72
|
}
|
|
47
73
|
const anonId = crypto.randomUUID();
|
|
@@ -260,6 +286,17 @@ export async function flushUsageOutbox() {
|
|
|
260
286
|
}
|
|
261
287
|
export async function runUsageUpload(opts = {}) {
|
|
262
288
|
const limit = opts.limit && opts.limit > 0 ? opts.limit : 1000;
|
|
289
|
+
const state = getOrCreateUsageState();
|
|
290
|
+
const settings = await fetchUsageTrackingSettings();
|
|
291
|
+
const resolvedSettings = settings?.ok ? settings : null;
|
|
292
|
+
updateTrackingState(state, resolvedSettings);
|
|
293
|
+
if (isTrackingDisabled(state, resolvedSettings)) {
|
|
294
|
+
const cleared = clearUsageOutbox();
|
|
295
|
+
return {
|
|
296
|
+
status: "disabled",
|
|
297
|
+
cleared,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
263
300
|
if (!opts.onlyFlush) {
|
|
264
301
|
const { batch, nextState, count } = await buildUsageUploadBatch(limit);
|
|
265
302
|
if (opts.dryRun) {
|
package/dist/infra/voicewake.js
CHANGED
|
@@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveStateDir } from "../config/paths.js";
|
|
6
6
|
const DEFAULT_TRIGGERS = ["nexus", "claude", "computer"];
|
|
7
7
|
function defaultBaseDir() {
|
|
8
|
-
return
|
|
8
|
+
return resolveStateDir(process.env, os.homedir);
|
|
9
9
|
}
|
|
10
10
|
function resolvePath(baseDir) {
|
|
11
11
|
const root = baseDir ?? defaultBaseDir();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { loadConfig } from "../config/config.js";
|
|
2
|
+
const DEFAULT_REDACT_MODE = "tools";
|
|
3
|
+
const DEFAULT_REDACT_MIN_LENGTH = 18;
|
|
4
|
+
const DEFAULT_REDACT_KEEP_START = 6;
|
|
5
|
+
const DEFAULT_REDACT_KEEP_END = 4;
|
|
6
|
+
const DEFAULT_REDACT_PATTERNS = [
|
|
7
|
+
// ENV-style assignments.
|
|
8
|
+
String.raw `\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1`,
|
|
9
|
+
// JSON fields.
|
|
10
|
+
String.raw `"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"`,
|
|
11
|
+
// CLI flags.
|
|
12
|
+
String.raw `--(?:api[-_]?key|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1`,
|
|
13
|
+
// Authorization headers.
|
|
14
|
+
String.raw `Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)`,
|
|
15
|
+
String.raw `\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
|
|
16
|
+
// PEM blocks.
|
|
17
|
+
String.raw `-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
|
|
18
|
+
// Common token prefixes.
|
|
19
|
+
String.raw `\b(sk-[A-Za-z0-9_-]{8,})\b`,
|
|
20
|
+
String.raw `\b(ghp_[A-Za-z0-9]{20,})\b`,
|
|
21
|
+
String.raw `\b(github_pat_[A-Za-z0-9_]{20,})\b`,
|
|
22
|
+
String.raw `\b(xox[baprs]-[A-Za-z0-9-]{10,})\b`,
|
|
23
|
+
String.raw `\b(xapp-[A-Za-z0-9-]{10,})\b`,
|
|
24
|
+
String.raw `\b(gsk_[A-Za-z0-9_-]{10,})\b`,
|
|
25
|
+
String.raw `\b(AIza[0-9A-Za-z\-_]{20,})\b`,
|
|
26
|
+
String.raw `\b(pplx-[A-Za-z0-9_-]{10,})\b`,
|
|
27
|
+
String.raw `\b(npm_[A-Za-z0-9]{10,})\b`,
|
|
28
|
+
String.raw `\b(\d{6,}:[A-Za-z0-9_-]{20,})\b`,
|
|
29
|
+
];
|
|
30
|
+
function normalizeMode(value) {
|
|
31
|
+
return value === "off" ? "off" : DEFAULT_REDACT_MODE;
|
|
32
|
+
}
|
|
33
|
+
function parsePattern(raw) {
|
|
34
|
+
if (!raw.trim())
|
|
35
|
+
return null;
|
|
36
|
+
const match = raw.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
37
|
+
try {
|
|
38
|
+
if (match) {
|
|
39
|
+
const flags = match[2].includes("g") ? match[2] : `${match[2]}g`;
|
|
40
|
+
return new RegExp(match[1], flags);
|
|
41
|
+
}
|
|
42
|
+
return new RegExp(raw, "gi");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function resolvePatterns(value) {
|
|
49
|
+
const source = value?.length ? value : DEFAULT_REDACT_PATTERNS;
|
|
50
|
+
return source.map(parsePattern).filter((re) => Boolean(re));
|
|
51
|
+
}
|
|
52
|
+
function maskToken(token) {
|
|
53
|
+
if (token.length < DEFAULT_REDACT_MIN_LENGTH)
|
|
54
|
+
return "***";
|
|
55
|
+
const start = token.slice(0, DEFAULT_REDACT_KEEP_START);
|
|
56
|
+
const end = token.slice(-DEFAULT_REDACT_KEEP_END);
|
|
57
|
+
return `${start}…${end}`;
|
|
58
|
+
}
|
|
59
|
+
function redactPemBlock(block) {
|
|
60
|
+
const lines = block.split(/\r?\n/).filter(Boolean);
|
|
61
|
+
if (lines.length < 2)
|
|
62
|
+
return "***";
|
|
63
|
+
return `${lines[0]}\n…redacted…\n${lines[lines.length - 1]}`;
|
|
64
|
+
}
|
|
65
|
+
function redactMatch(match, groups) {
|
|
66
|
+
if (match.includes("PRIVATE KEY-----"))
|
|
67
|
+
return redactPemBlock(match);
|
|
68
|
+
const token = groups
|
|
69
|
+
.filter((value) => typeof value === "string" && value.length > 0)
|
|
70
|
+
.at(-1) ?? match;
|
|
71
|
+
const masked = maskToken(token);
|
|
72
|
+
if (token === match)
|
|
73
|
+
return masked;
|
|
74
|
+
return match.replace(token, masked);
|
|
75
|
+
}
|
|
76
|
+
function redactText(text, patterns) {
|
|
77
|
+
let next = text;
|
|
78
|
+
for (const pattern of patterns) {
|
|
79
|
+
next = next.replace(pattern, (...args) => redactMatch(args[0], args.slice(1, args.length - 2)));
|
|
80
|
+
}
|
|
81
|
+
return next;
|
|
82
|
+
}
|
|
83
|
+
function resolveConfigRedaction() {
|
|
84
|
+
const cfg = loadConfig().logging;
|
|
85
|
+
return {
|
|
86
|
+
mode: normalizeMode(cfg?.redactSensitive),
|
|
87
|
+
patterns: cfg?.redactPatterns,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function redactSensitiveText(text, options) {
|
|
91
|
+
if (!text)
|
|
92
|
+
return text;
|
|
93
|
+
const resolved = options ?? resolveConfigRedaction();
|
|
94
|
+
if (normalizeMode(resolved.mode) === "off")
|
|
95
|
+
return text;
|
|
96
|
+
const patterns = resolvePatterns(resolved.patterns);
|
|
97
|
+
if (!patterns.length)
|
|
98
|
+
return text;
|
|
99
|
+
return redactText(text, patterns);
|
|
100
|
+
}
|
|
101
|
+
export function redactToolDetail(detail) {
|
|
102
|
+
const resolved = resolveConfigRedaction();
|
|
103
|
+
if (normalizeMode(resolved.mode) !== "tools")
|
|
104
|
+
return detail;
|
|
105
|
+
return redactSensitiveText(detail, resolved);
|
|
106
|
+
}
|
|
107
|
+
export function getDefaultRedactPatterns() {
|
|
108
|
+
return [...DEFAULT_REDACT_PATTERNS];
|
|
109
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function parseFenceSpans(buffer) {
|
|
2
|
+
const spans = [];
|
|
3
|
+
let open;
|
|
4
|
+
let offset = 0;
|
|
5
|
+
while (offset <= buffer.length) {
|
|
6
|
+
const nextNewline = buffer.indexOf("\n", offset);
|
|
7
|
+
const lineEnd = nextNewline === -1 ? buffer.length : nextNewline;
|
|
8
|
+
const line = buffer.slice(offset, lineEnd);
|
|
9
|
+
const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);
|
|
10
|
+
if (match) {
|
|
11
|
+
const indent = match[1];
|
|
12
|
+
const marker = match[2];
|
|
13
|
+
const markerChar = marker[0];
|
|
14
|
+
const markerLen = marker.length;
|
|
15
|
+
if (!open) {
|
|
16
|
+
open = {
|
|
17
|
+
start: offset,
|
|
18
|
+
markerChar,
|
|
19
|
+
markerLen,
|
|
20
|
+
openLine: line,
|
|
21
|
+
marker,
|
|
22
|
+
indent,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
else if (open.markerChar === markerChar &&
|
|
26
|
+
markerLen >= open.markerLen) {
|
|
27
|
+
const end = nextNewline === -1 ? buffer.length : nextNewline + 1;
|
|
28
|
+
spans.push({
|
|
29
|
+
start: open.start,
|
|
30
|
+
end,
|
|
31
|
+
openLine: open.openLine,
|
|
32
|
+
marker: open.marker,
|
|
33
|
+
indent: open.indent,
|
|
34
|
+
});
|
|
35
|
+
open = undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (nextNewline === -1)
|
|
39
|
+
break;
|
|
40
|
+
offset = nextNewline + 1;
|
|
41
|
+
}
|
|
42
|
+
if (open) {
|
|
43
|
+
spans.push({
|
|
44
|
+
start: open.start,
|
|
45
|
+
end: buffer.length,
|
|
46
|
+
openLine: open.openLine,
|
|
47
|
+
marker: open.marker,
|
|
48
|
+
indent: open.indent,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return spans;
|
|
52
|
+
}
|
|
53
|
+
export function findFenceSpanAt(spans, index) {
|
|
54
|
+
return spans.find((span) => index > span.start && index < span.end);
|
|
55
|
+
}
|
|
56
|
+
export function isSafeFenceBreak(spans, index) {
|
|
57
|
+
return !findFenceSpanAt(spans, index);
|
|
58
|
+
}
|
package/dist/media/image-ops.js
CHANGED
|
@@ -7,7 +7,9 @@ function isBun() {
|
|
|
7
7
|
}
|
|
8
8
|
function prefersSips() {
|
|
9
9
|
return (process.env.NEXUS_IMAGE_BACKEND === "sips" ||
|
|
10
|
-
(process.env.NEXUS_IMAGE_BACKEND !== "sharp" &&
|
|
10
|
+
(process.env.NEXUS_IMAGE_BACKEND !== "sharp" &&
|
|
11
|
+
isBun() &&
|
|
12
|
+
process.platform === "darwin"));
|
|
11
13
|
}
|
|
12
14
|
async function loadSharp() {
|
|
13
15
|
const mod = (await import("sharp"));
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { resolveApiKeyForProvider } from "../agents/model-auth.js";
|
|
2
|
+
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
3
|
+
const DEFAULT_LOCAL_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
|
|
4
|
+
function normalizeOpenAiModel(model) {
|
|
5
|
+
const trimmed = model.trim();
|
|
6
|
+
if (!trimmed)
|
|
7
|
+
return "text-embedding-3-small";
|
|
8
|
+
if (trimmed.startsWith("openai/"))
|
|
9
|
+
return trimmed.slice("openai/".length);
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
async function createOpenAiEmbeddingProvider(options) {
|
|
13
|
+
const remote = options.remote;
|
|
14
|
+
const remoteApiKey = remote?.apiKey?.trim();
|
|
15
|
+
const remoteBaseUrl = remote?.baseUrl?.trim();
|
|
16
|
+
const { apiKey } = remoteApiKey
|
|
17
|
+
? { apiKey: remoteApiKey }
|
|
18
|
+
: await resolveApiKeyForProvider({
|
|
19
|
+
provider: "openai",
|
|
20
|
+
cfg: options.config,
|
|
21
|
+
agentDir: options.agentDir,
|
|
22
|
+
});
|
|
23
|
+
const providerConfig = options.config.models?.providers?.openai;
|
|
24
|
+
const baseUrl = remoteBaseUrl || providerConfig?.baseUrl?.trim() || DEFAULT_OPENAI_BASE_URL;
|
|
25
|
+
const url = `${baseUrl.replace(/\/$/, "")}/embeddings`;
|
|
26
|
+
const headerOverrides = Object.assign({}, providerConfig?.headers, remote?.headers);
|
|
27
|
+
const headers = {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
Authorization: `Bearer ${apiKey}`,
|
|
30
|
+
...headerOverrides,
|
|
31
|
+
};
|
|
32
|
+
const model = normalizeOpenAiModel(options.model);
|
|
33
|
+
const embed = async (input) => {
|
|
34
|
+
if (input.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
const res = await fetch(url, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers,
|
|
39
|
+
body: JSON.stringify({ model, input }),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
throw new Error(`openai embeddings failed: ${res.status} ${text}`);
|
|
44
|
+
}
|
|
45
|
+
const payload = (await res.json());
|
|
46
|
+
const data = payload.data ?? [];
|
|
47
|
+
return data.map((entry) => entry.embedding ?? []);
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
id: "openai",
|
|
51
|
+
model,
|
|
52
|
+
embedQuery: async (text) => {
|
|
53
|
+
const [vec] = await embed([text]);
|
|
54
|
+
return vec ?? [];
|
|
55
|
+
},
|
|
56
|
+
embedBatch: embed,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function createLocalEmbeddingProvider(options) {
|
|
60
|
+
const modelPath = options.local?.modelPath?.trim() || DEFAULT_LOCAL_MODEL;
|
|
61
|
+
const modelCacheDir = options.local?.modelCacheDir?.trim();
|
|
62
|
+
// Lazy-load node-llama-cpp to keep startup light unless local is enabled.
|
|
63
|
+
const moduleName = "node-llama-cpp";
|
|
64
|
+
const { getLlama, resolveModelFile, LlamaLogLevel } = (await import(moduleName));
|
|
65
|
+
let llama = null;
|
|
66
|
+
let embeddingModel = null;
|
|
67
|
+
let embeddingContext = null;
|
|
68
|
+
const ensureContext = async () => {
|
|
69
|
+
if (!llama) {
|
|
70
|
+
llama = await getLlama({ logLevel: LlamaLogLevel.error });
|
|
71
|
+
}
|
|
72
|
+
if (!embeddingModel) {
|
|
73
|
+
const resolved = await resolveModelFile(modelPath, modelCacheDir || undefined);
|
|
74
|
+
embeddingModel = await llama.loadModel({ modelPath: resolved });
|
|
75
|
+
}
|
|
76
|
+
if (!embeddingContext) {
|
|
77
|
+
embeddingContext = await embeddingModel.createEmbeddingContext();
|
|
78
|
+
}
|
|
79
|
+
return embeddingContext;
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
id: "local",
|
|
83
|
+
model: modelPath,
|
|
84
|
+
embedQuery: async (text) => {
|
|
85
|
+
const ctx = await ensureContext();
|
|
86
|
+
const embedding = await ctx.getEmbeddingFor(text);
|
|
87
|
+
return Array.from(embedding.vector);
|
|
88
|
+
},
|
|
89
|
+
embedBatch: async (texts) => {
|
|
90
|
+
const ctx = await ensureContext();
|
|
91
|
+
const embeddings = await Promise.all(texts.map(async (text) => {
|
|
92
|
+
const embedding = await ctx.getEmbeddingFor(text);
|
|
93
|
+
return Array.from(embedding.vector);
|
|
94
|
+
}));
|
|
95
|
+
return embeddings;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export async function createEmbeddingProvider(options) {
|
|
100
|
+
const requestedProvider = options.provider;
|
|
101
|
+
if (options.provider === "local") {
|
|
102
|
+
try {
|
|
103
|
+
const provider = await createLocalEmbeddingProvider(options);
|
|
104
|
+
return { provider, requestedProvider };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const reason = formatLocalSetupError(err);
|
|
108
|
+
if (options.fallback === "openai") {
|
|
109
|
+
try {
|
|
110
|
+
const provider = await createOpenAiEmbeddingProvider(options);
|
|
111
|
+
return {
|
|
112
|
+
provider,
|
|
113
|
+
requestedProvider,
|
|
114
|
+
fallbackFrom: "local",
|
|
115
|
+
fallbackReason: reason,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (fallbackErr) {
|
|
119
|
+
throw new Error(`${reason}\n\nFallback to OpenAI failed: ${formatError(fallbackErr)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw new Error(reason);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const provider = await createOpenAiEmbeddingProvider(options);
|
|
126
|
+
return { provider, requestedProvider };
|
|
127
|
+
}
|
|
128
|
+
function formatError(err) {
|
|
129
|
+
if (err instanceof Error)
|
|
130
|
+
return err.message;
|
|
131
|
+
return String(err);
|
|
132
|
+
}
|
|
133
|
+
function formatLocalSetupError(err) {
|
|
134
|
+
const detail = formatError(err);
|
|
135
|
+
return [
|
|
136
|
+
"Local embeddings unavailable.",
|
|
137
|
+
detail ? `Reason: ${detail}` : undefined,
|
|
138
|
+
"To enable local embeddings:",
|
|
139
|
+
"1) pnpm approve-builds",
|
|
140
|
+
"2) select node-llama-cpp",
|
|
141
|
+
"3) pnpm rebuild node-llama-cpp",
|
|
142
|
+
'Or set agent.memorySearch.provider = "openai" (remote).',
|
|
143
|
+
]
|
|
144
|
+
.filter(Boolean)
|
|
145
|
+
.join("\n");
|
|
146
|
+
}
|